loading...

The 10 Component Commandments

selbekk on June 22, 2019

Written in collaboration with Caroline Odden. Based on the talk with the same name and people, held at the ReactJS Oslo Meetup in June 2019. Cre... [Read Full]
markdown guide
 

Thank you @selbekk for the thorough "10 component commandments".

What are some of your favorite tips for creating cool components?

I am not sure if this is a tip or a bad practice, but I've started seeing a lot of code with following structure in "return/render" methods.

{isError && <ErrorMessage error={error}}
{isLoading && <Loading />}
{!isLoading && (
  <OtherComponentRequringData data={data} />
)}

Would such a way of showing components a bad practice?
I found it more declarative then having "if/else" (or using a ternary operator).

But the downside I found is that, many people aren't familiar with how to interpret the "state && component".

What would you think?

 

It's the short-circuit operator. I think it's neat.

background-color: ${p =>
      (p.attendanceStatus === "in" && p.theme.color.primary.main) ||
      (p.attendanceStatus === "out" && p.theme.color.out) ||
      (p.attendanceStatus === "later" && p.theme.color.warning) ||
      (p.attendanceStatus === "late" && p.theme.color.error) ||
      (p.attendanceStatus === "absent" && p.theme.color.absent) ||
      (p.attendanceStatus === "off" && p.theme.color.grey.medium)};
 

also found useful this pattern as more declarative, but need to use it carefully. my early pitfall was to use array.length && ... instead of array.length === 0 && ...

 

What about just using
!!array.length && ...?
It looks smarter and shortly.

yep, it's shorter :) use !! many years while coding on perl. now I prefer Boolean(array.length)

 

This is great advice. Falsey and Thruthy values bit me in the same way. I do things like isEmpty(array) or if I am really defensive isNullOrEmpty(array)

 

What about just using !!array.length && ... ? It looks smarter and short.

 
 
 

One of the rules I try to follow is don’t make something a shared component until I’ve copied and pasted it four times. Too often I would create components too early, without any context beyond the current one. This would lead to components with awkward and bloated APIs from other developers trying to adapt it to their needs.

Also, if your component encapsulates complicated code structure, offer a way for developers to get to the individual components. For example, say you’ve created an Input component that renders a label and control, as well as an error message on invalid input. Give developers a way to access the individual components and allow them to completely recompose (or add to) the component’s structure if they have to.

Some library APIs give you render props for each component, but that quickly gets messy when you have to add a labelRender, a controlRender, and an errorRender. I personally like compound components exported from the original component (e.g. <Input.Label>), along with React Context providing the necessary linking state (e.g. the input’s ID for the label’s htmlFor prop).

 

I... don’t make something a shared component until I’ve copied and pasted it four times... This would lead to components with awkward and bloated APIs from other developers trying to adapt it to their needs.

The problems with this are:

  1. Your duplicated code likely has minor updates in each iteration, instead of refininements to a 'common component'. Now it is more difficult to unify them.
  2. If you needed the same functionality 4 times, it's likely other team devs did as well. Since there was no common component, each had to reinvent the wheel, each time with different API's. Now it's very difficult to unify all these components to use the same common one, along with all their tests. This is so much work that it usually is never done, and the duplicated code remains, complicating future enhancements and maintenance.

I find it best to create common components as early as possible, then encourage reuse rather that duplication, (DRY). Common components should evolve as new needs arise; this is agile coding! Even heavily used libraries like MaterialUI are updated regularly, so your own 'library' of components should be as well.

Good code reviews can prevent overcomplicating common components. Guidelines like the ones in this article, along with app standards, also help keep API's simple and consistent across an app. It's easier to refactor a common component occasionally than to replace totally different implementations of the same functionality.

 

I agree that good code reviews can prevent over complicating common components, but I’m not as concerned with reinventing the wheel, at least not at first. I believe components need to earn their right to exist, and by the time they have earned this right, you typically have a good starting point for an API, and a good reviewer can pick this out.

Also, in my experience, if I am having difficulty unifying components into a common component, there’s a good chance they aren’t as common as I might have originally thought. I may be better served in extracting out common related logic into a helper function or hook.

With the projects I work on at work, I tend to fall on the side of making code as disposable as possible, so take what I’m with a grain of salt. I am very hard on what makes its way into our shared common components.

Honestly, whatever works for you is great, especially since this is one of those areas where it depends a lot on the context of the work.

 

For Tip 2. Allow for contextual semantics

function Grid({ as: Element, ...props }) {
  return <Element className="grid" {...props} />
}
Grid.defaultProps = {
  as: 'div',
};

What would the propType be?

Something like this?

Grid.propTypes = {
  as: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType])
};
 

This was the one I preferred. But in a non-typescript world that would require a lot of conditions to return the right element type.

Am I right?

 

Good question! It’s not a very easy type to get right, especially if you want the correct props of the as element to be applied as well. It is doable though, with a drizzle of keyof and some other ts ninja tricks. I’m not a great TS dev yet, and I’m on my phone atm.

There is lots of prior art here - look at reach UI or styled components’types for possible impoementations

 

Yeah that would be it!

 

my favorite tip with redux connect & compose to reduce boilerplate (with HOCs):

const mapStateToProps = state => ({ ... });

export default compose(
  withRouter,
  withStyles,
  ...
  connect(mapStateToProps)
)(MyComponent);
 

Great article, thanks.

As I understand it, this is a small typo (tooltipp)?

className = {classNames ('tooltipp', props.className)}

I especially liked the eighth recommendation - about HTML attributes. Does anyone know if there is an eslint rule for this (for react)? And is it even possible to make such eslint rule? At first glance, this is possible.

 

Hi Ivan! Yeah, thanks, I’ll correct it.

I’m not sure about an eslint rule, bur it should be doable I guess :-)

 

Nice article, thanks. I'm not sure that the usage of magic strings is a good practice:

<Button variant="primary" size="large">
  I am primarily a large button
</Button>

Wouldn't it be better? :

<Button variant={Button.VARIANTS.PRIMARY} size={Button.SIZES.LARGE}>
  I am primarily a large button
</Button>
 

That’s a nice way to do it, too. I tend to use prop types or type annotations to make that assertion for me, which reads better to me.

 
 
 
 

Just a heads-up that Storybook will get an MDX documentation add-on in the next version. We're running the technical preview at our end. :)

 
code of conduct - report abuse