DEV Community

Cover image for The Anatomy Of My Ideal React Component

The Anatomy Of My Ideal React Component

Antonin J. (they/them) on March 11, 2022

import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import styled from 'styled-components' import tw from 'twin.ma...
Collapse
 
antjanus profile image
Antonin J. (they/them)

Thanks for the perspective! I find it interesting how everyone does things differently and their reasoning behind it. I do agree with the ...props and taking advantage of the HTML element typing. I typically do it for internal UI library work.

I'd love to hear more about the separation between components that show data and components that fetch it.

How do you structure components that do data-fetching and keep state?
Do you specifically avoid any presentational elements in those?

Collapse
 
inviscidpixels profile image
aten

Styled Components are great.

While it might seem overkill, I find using useReducer (depending on how you implement it properly or not) (or in combination with your custom hook logic), because it conforms the code to a deterministic state machine (more than it doesn't) and setups UX as an event processing queue, is much cleaner and more than not leads to such.

Thanks for the article! Didn't know about Twin, best of both worlds!

Collapse
 
nevulo profile image
Nevulo

Great post, and completely agree with the 7 points on component structure you mentioned, makes for very readable components and higher quality code!

At the end you mention "not liking big components" and not being sure why, personally I try to follow the single-responsibility principle (I imagine following this principle is why splitting up components feels "right"), and making sure that any component or hook only has only one responsibility. Specifically, something like a Post component should only be worried about rendering the contents of a post, not things like the comments for simplicities sake.

The beautiful thing about components is the ability to use composition to build up bigger components from smaller ones, and having explicit, clear, simple components to build up more complicated ones makes the structure more understandable.

Collapse
 
well1791 profile image
Well

I love twin.macro but I'm more of a stitchesjs guy, which btw twin.macro already supports, and I'm going to provide an example for it.

Also, I believe there's a nicer approach to that repetitive conditional sequence if error then <Error /> else if isLoading then <Loading /> else if data then <Component /> else <Empty /> check redwoodjs approach redwoodjs.com/docs/cells, in short, you just have a wrapper smart enough to deal with all that logic.

I agreed with pretty much everything, but types must be moved in a separated file, also, the component tag type must be exported along with the component props (for a11y). And, when it comes to the folder structure, I prefer styles in a separated file over "styled-components" in the same file. Let me elaborate more on this.

/// folder structure
- src/components/Post/Post.tsx
- src/components/Post/PostStyles.ts
Enter fullscreen mode Exit fullscreen mode

where

/// src/components/Post/Post.tsx
import * as stl from './PostStyles'
import type { PostProps } from './PostTypes'

export const Post: React.FC<PostProps> = ({ data, ...p }: PostProps) => {
  return (
    <div {...p} className={stl.container({ className: p.className })}>
      ...
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Then all you have to do, is work your styles in a separated file!

/// src/components/Post/styles.ts
import { css } from 'src/shared/theme'

export const container = css({
  $$minHeight: 'inherit', // <- stitchesjs magic
  bg: '$blue100',
  h: '$$minHeight,
})
Enter fullscreen mode Exit fullscreen mode

Now! Where is the fun?

  1. Your Post component now displays only markup content! No need to have a bunch of "mini components" with styles listed in the same file.
  2. You can now see the html tag name instead of some random (and sometimes silly) name! Which means, now you have a better experience with a11y
  3. If for some reason you need to override the style of an inner component! You could just override it by using styles only! No need to pass some new props everywhere!
/// src/component/BigFun/styles.tsx
import * as postStl from 'src/components/Post/PostStyles'

export const container = css({
  [`.${postStl.container()}`]: {
    $$minHeight: '100%',
  },
})
Enter fullscreen mode Exit fullscreen mode

But! I know, I know, you're think "wtf? how do I change things programmatically in the PostStyles.ts file?" Aaaaand!! Here's where stitches shines: stitches.dev/docs/variants 🎉 🤷 everything is typed and everyone is happy! (for now... 🤨)

Collapse
 
antjanus profile image
Antonin J. (they/them)

After reading your comment about splitting everything up, I'm wondering if I'm mildly biased toward bigger components because I just legit use a bigger monitor 😂

Collapse
 
jerrylowm profile image
Jerry Low

This is amazing, because without any guidance or collaboration we’re almost doing the exact same thing. What many developers don’t see is the power of formatting, some small changes in grouping makes code so much more readable.

The only difference I have is that I like to deconstruct the props in the component, just in case it gets overwhelming (which hopefully it doesn’t.

Collapse
 
ceoshikhar profile image
Shikhar

I always read posts that Luke comments on. If he spent time on reading it, it probably is worth it.

This time, it's true again. Cheers!

 
antjanus profile image
Antonin J. (they/them)

thanks! :)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
antjanus profile image
Antonin J. (they/them) • Edited

If you’re up for it, I’d love a code review! How would you do things? What do you see as messy code?

A couple of reasons for TW + styled components —

Tailwind doesn’t handle 100% of situations. Having both gives us more flexibility. Having them interoperate feels cleaner to me on top of it. You can also used styled components to conditionally generate scoped tailwind

And then Personal preference but I prefer to not have classNames and naked HTML in my components unless they’re specifically in like a shared UI library. I find it so much nicer to write <StyledContainer> than <div className="w-full"> in general.

Like I said at the top of the article, this is what works for me and vibes with me 🙂

I’d love to hear why not both, tbh! I was so excited to find Twin and follow this pattern!

Collapse
 
kevinletchford profile image
Kevin Letchford

This is a totally unproductive comment. No insight, just belittling OP with out any reasoning.

Collapse
 
topolanekmartin profile image
Martin Topolanek

Since we use ReturnType type we barely create Return type manually.

Take a look at this typescriptlang.org/docs/handbook/u...

Collapse
 
antjanus profile image
Antonin J. (they/them)

I really like this utility! I love that TS has so many of them.

I found typing return types manually to be a great way to document the intent of the return and being clear around what id like to return as I write the hook.

But I like the utility when I don’t want to export the type.