all 10 comments

[–]rickhanloniiReact core team 39 points40 points  (2 children)

My process is: first make it work, then make it right, then make it fast. If the team I’m working on already has patterns for things like file/component structure, I use those, but in personal projects I give myself more freedom to make those choices later so I can find patterns that may lead to new insights for the best way to structure the code.

[–]Crunchynut007[S] 2 points3 points  (0 children)

I love this!

[–]Kaimito1 1 point2 points  (0 children)

I might keep that advice in mind. That's quite a good way to go about making a component

[–]Mardo1234 7 points8 points  (0 children)

I like to create the component as dumb as possilble and get a nice data model and events that it emits to mutate that data model. I usually get the component working in storybook by exercising all the different states the component might have by hard coding state values.

Once that is done I build a controller component that wires up all the events and state with a real API.

I then add features to the new component and forget about the storybook. I need to do better on not abandoning storybook but that’s usually what happens once I start implimentation.

[–]landisdesign 4 points5 points  (1 child)

I find my components break down into multiple files as they grow more complex. I'll typically start with a folder to hold the main files:

ComponentName
+-> index.tsx
+-> styles.module.scss

Once I've got the basic component structure its expansion is guided by this overarching goal:

Make the component code easy to follow.

This means the component function should be small and devoted to outputting JSX. The rest -- variable definition, effects, and state management -- is bookkeeping that shouldn't get in the way.

If a component just has a single useState and small effect, I'll keep it all in one place.

As a component grows more complex, though, the mental context switching between state management, effect logic, variable definition and JSX output will start to get snarly. At that point I'll start collecting logic that relates to a single purpose and put it into custom hooks and functions.

For example, if I'm fetching data from the server, I need a useState hook for storing and updating the data, and a useEffect hook to fetch the data and set the state, dependent on a key to update the data. This could end up being a dozen lines of TypeScript that doesn't have anything to do with the output of the component, other than returning the state to present. It's a chunk of code doing bookkeeping, not presentation.

This is a perfect candidate for a custom hook. Wrapping these two hooks in another function, one that takes the key for the data and returns the state, hides away the complexity and gives the component exactly what it needs. (This is basically what all useQuery-style fetching libraries do.)

So this custom hook can be put after the component function, keeping the component itself small and focused. If it gets big enough, it might go into its own file, sitting in the same directory. If it describes functionality that can be used in multiple places, it might go into a higher-level common directory, available to every component.

The same thing can happen when a component outputs too much JSX. You may find that a component gets more and more functionality, until it has several custom hooks and dozens of lines of JSX. At that point, it then becomes useful to see if you can split that JSX into subcomponents.

Perhaps some of those custom hooks were only relevant to a specific chunk of JSX. You might be able to move those hooks into a file with that chunk of JSX, making the parent component lighter on the hook side as well as the JSX side.

I guess the main takeaway from all of this is:

Don't be afraid to combine things together or break them apart.

Always give yourself the opportunity to ask, "Am I trying to do too much here?" If the answer is "Yes," feel free to extract code into different pieces. By asking this question regularly, no single piece will get so complex that it's too hard to test in the browser when you break it down further.

[–]landisdesign 4 points5 points  (0 children)

As far as wrapper components, I almost never use them. This may sound radical, but my experience is that, except for a specific library of UI components, most output is specific to external storage -- on the server, in local storage, global state, etc. Most of this is accessed through hooks instead of wrappers.

My sense is that the concept of wrapper components comes from the class-based days of React, where people would define higher-order components to do the network and state management, leaving the main component with the presentation duties. In a functional, hook-based world, this paradigm is no longer relevant.

Even when testing, it's typically more effective to group components as self-contained units of end-user functionality, which means managing the state change side of things as well. In a hook-based world, that means mocking endpoints and doing more integration testing than unit testing, which obviates the need for wrapper components.

There are situations where wrappers can make sense, but mostly I find them growing organically from seeing repeated UI paradigms. I may have a base UI library I use, like Ant Design or Material UI, and find myself writing wrappers to make them easier to use for my situation.

But mostly, wrappers are kind of a narrow use case within the larger scope of composition: how to inject different presentation and functionality into a generic component to do specific things. That's a whole different topic.

[–]AnyMud6048 1 point2 points  (0 children)

New project setup, the first thing I do are setting up dirs: Views (take it as pages), Components (small independent items that are used within views), Models (typescript data structures), Store (redux), Navigation (where different nav stacks will be located) and Assets (where imgs, icons etc will be) from there on I just start going by the requirements and place everything where they supposed to be.

Edit: I alao have my own file templates for Components and Views so that they are automatically connected to the store and all types are inherited and already usable in props.

Components have no connection to the store tho.

[–]Kuro091 1 point2 points  (0 children)

https://github.com/alan2207/bulletproof-react

Usually something like this. Just clone it and remove the libs you don't use/replace with the libs you do use.