React best practices
onoufriosm.medium.com
onoufriosm.medium.com
> We managed to abstract away the logic for translation into the Translation component.
A function call. You abstracted away a function call.
> Now there is a centralized translation component where we can change any UI or business logic if needed.
A plain old JS function still centralizes i18n functionality. As for the UI logic, all the component is doing is wrapping text, therefore your component needs to support anything anyone would ever want to do a p tag. Or an h1 tag. Or a li tag. Or every other tag that can contain a text node. At some point you're just reimplementing the DOM API. What specifically is gained in going from `<p>i18n('Hello!')</p>` to `<Translation text='Hello!'/>`?
> It’s now a lot easier to test files that use translation. Before refactoring we would have to mock the useTranslation somehow because we don’t want to test its implementation. With the refactored code we only need to check that we render the Translation component and that we pass the correct props.
I can't criticize this part because I genuinely don't understand where its coming from. "We only need to check that we render the Translation component" implies that the Translation component is rendered in the test. And for the Translation component to render, the `useTranslation` hook still has to be called, so we still have to mock it, right?
import someFunc from 'someFunc';
// ...
return someFunc();
is not the same as: import SomeComponent from 'SomeComponent';
// ...
return <SomeComponent />Why wouldn't you just write it as a function with data and context being passed through as arguments? Isn't it much simpler to write a pure function and test that function than render a component and test it? If you simply write a function that accepts the locale, the string and context, does the translator magic and returns the translated text, would you not then just need to write a unit test simply to make sure you're getting the expected output? Why the abstraction into hooks and then further into a separate component that you then have to render?
See https://news.ycombinator.com/item?id=27031847 for more reasons.
If you plan to reuse it in different environments, just give the user a `setConfiguration` and a `translateFunction` function. I prefer module-level variables for holding the i18n system state, but it could also be a class-based singleton, if that's your cup of tea. I still don't see how React plays into this.
Context is an application of the principles behind DI, sure, but that doesn't really address my point.
* By using a singleton you're not able to easily test different LocaleData contexts in parallel.
* By using a singleton you're not able to make your NodeJS server deal with users with different locale settings because you have only one global instance. Multiple requests arriving at the same time would corrupt each other. You could spawn a separate process for each locale but that's inconvenient.
Does that make sense?
One concrete reason: if the language is user-configurable, you can avoid an additional argument at the call site by putting that data in a context.
If part of that input is now bundled up in this large globally structure, then now testing it seems like it would that much more complicated. Then, I'd need to artificially construct these structures as a part of my tests, making them less transparent to readers of the codebase.
> making them less transparent to readers of the codebase
Sometimes that's exactly what you want. It's the same as hiding CSS styles behind a set of props like `<Button buttonStyle="primary" buttonSize="large">Continue</Button>`. It actually makes your codebase much safer by reducing the combination of props and values you're able to pass.
From a testing perspective, you just wrap the component being tested in a context provider that provides the necessary fixtures for your test case. However, I'd also note that this is probably not an ideal function to test since all that you could possibly be testing is that react context and i8n are working as expected, which is almost certainly outside the scope of your application's testing surface.
All of our tests that assert on text, assert using the i18n key. So we could re-run the suite in Arabic or Hebrew, for example, and be confident in the result.
There's no reason to mock it. It's an identity function if the translation doesn't exist.
Not to pile it on, but how did this make the front page?
Also, I think the author means that logic should as deep as it makes sense in the component tree which makes sense because then you can have better separation of concerns like he showed in his example.
This is not true, especially if you're following _actual_ best practices and using e.g. React.memo() and immutable state updates. React is explicitly built around the idea that you don't need to re-render every part of the DOM on every state update.
The advice to take a react hook and turn it back into an HOC is bizarre. This is not a best practice.
Any post proclaiming a best practice is suspect.
Some advice will definitely lead to overengineering though. Sometimes using refactor tools when needed is better than a complex abstraction.
There is nothing wrong in having separate components for "Button", "BigButton", "IconButton" and so on. It immediately tells you what the component does and the additional code is minimal.
1. Strongly agree. I'd add a note or two there about styled components or your favourite CSS library, and how thinking in components and the tree instead of html vs stylesheets is very useful.
2. Strongly disagree. This indeed seemed like a best practice when I was getting started, but that Action then keeps growing and growing until nobody knows exactly how it works or how to tame it. It's better to keep components small (point 1!) and focused, so let buttons be Buttons.
3. Cannot comment. I haven't worked on so many "just a CRUD app" so this might be useful for those. I can see the value of it if you have e.g. an admin panel or CMS editing a DB or similar. I would leave this repeating logic only to that though, not to the general logic of all the app.
4. Agree, but also use context to your advantage. e.g., instead of `useToggle()`, name it `useVisible()` or whatever is the appropriate name.
5. Strongly agree, it's very powerful to first write the high-level API you want by typing first the desired component tree, and then define the components themselves.
6. Yes we can try different things depending on our apps' needs, but then it's not a "React Best Practices", maybe just a useful tip? :)
Maybe the author just used a poor example, but his useToggle is actually less readable to me.
The initial example with just useState is super obvious to understand what's going on in that component.
With his useToggle example, if I'm in this codebase I'm now asking myself - does toggle() fire some hidden side effect? Now I need to go check out the hook to make sure.
I agree with you that in the author use case it's waaay too simple and the best case is to use it inline, but I believe what they meant to do (hopefully!) is use that as an example of a potentially more complex hook and how you can/should use custom hooks instead of shoving all the logic in the component.
I'll reiterate strong disagreement here. This approach is very tempting to reduce code duplication, but in my experience you end up essentially building a "JSX" component that takes an arcane combination of props and returns some specific bit of JSX. So you end up invoking <JSX type="button" buttonColor="blue" buttonLabel="Continue" />. Why not just invoke <Button color="blue">Continue</Button>?
That whole User/UserRead/ReacHOC thing could be much more readable if there was just a useUser() hook instead.
>Therefore every app should have an action component that could look like this:
export const Action = ({ onClick, iconProps, buttonProps, dropdownItemProps, renderProp }) => {
if (iconProps) {
return <Icon onClick={onClick} {...iconProps} />
} else if (buttonProps) {
return <Button onClick={onClick} {...buttonProps} />
} else if (dropdownItemProps) {
return <Dropdown.Item onClick={onClick} {...dropdownItemProps} />
} else {
return renderProp({ onClick });
}
}I've only done smaller projects in React, but in 15 years of writing programs, that instinctively looks like a pretty severe code smell.
In general, while some of the advice here could be useful, my experience has led me to very different opinions about structuring react.
I personally wasn't a fan of any advice in this article. But again, it's all just opinion and personal experience. I didn't feel anything covered fell in the area of best practices.
Techs that quickly end up with a general "best practice" code style are a lot easier to work with, and a lot, lot, lot, lot easier to maintain.
YMMV, but having been on both sides of this equation at companies large and small, I find the library (I.e. react) approach favorable over the framework (I.e. angular or ember) approach.
My point was that it doesn't matter if it's a framework, library, language, configuration, or whatever. If the design of that thing means that the community coalesces around a particular style of usage, it's much better for everyone in the long run.
Some would call this a blasphemy. Works great.
1. show that you start a http call, by showing a loading spinner and blocking a button
2. do the http call
3. show the result to the user (even errors)
4. unblock the button and hide the spinner
thus most often http requests are somehow required to be dependant on your ui. libraries like redux-saga help with that.You are already deciding that this component will take one of iconProps/buttonProps/dropdownItemProps, so you've already decided what type of component it is. You might as well just call <Icon />, <Button />, or <Dropdown.Item /> from the parent component and have more readable code.
Then give a renderButton prop to override the Button and use whatever <Icon />, <Dropdown.Item /> you want when you are using the <CommentDeleteButton />.
The most blatant signal for this is the else case here. The else case literally calls one of the props with on of its other props passed in. You could easily ask the question "Why couldn't the consumer of this component just do that instead?"—in this else case the Action component loses meaning by breaking the single responsibility principle. It becomes so overgeneralized that literally any component could be rendered through it.
Consider that this pattern contains N different properties that renders N different components (plus the default case), I don't know why you would want to put this into one component rather than having the consumer select the right one themselves. This even introduces the possibility of bugs - what happens if a consumer passes two sets of properties (which admittedly could be somewhat prevented by Typescript)? I don't really see the benefits of this approach for this particular example.
For example, useState leads to prop drilling, coupling, and cumbersome usage patterns.
React is a declarative rendering library, it should handle: A. What DOM elements are created? B. How are those DOM elements structured? C. What styles and attributes do those DOM elements have?
I swear the introduction of Hooks threw such a monkey wrench into the ecosystem. They're just powerful enough to seem like an okay thing to use 100% of the time...
I thought we all agreed that React's ultimate destination was obscurity while it quietly became the runtime target and developers just had to return dom structures as data from functions. But they got greedy, and didn't want to go down like that, and now we have a worse version of redux instead of building on top of react-redux.
Perhaps my perception is wrong, in which case apologies and I'm happy to learn and listen.
Hooks are fine. Using React’s built in Hooks [0] for nonlocal state management is a bad idea, but using, say, react-redux and useSelector/useDispatch is a different story.
Well, if you use useState for something other than local component state, but that’s pretty expressly not using it correctly.
[0] which are fine for local, intracomponent, state management, including within groups of components that are tightly logically coupled so that implementation coupling isn’t really adding an additional burden.
For example, a checkbox IMHO would be better considered a presentational component even if it manages the small bit of state it needs for it to work. Same with many of the state management that is around "visual" changes.
The point where I normally decide to split it up like you say into sub-components is just when the component becomes too complex.
Push all your callbacks into a separate component.
Now, wishful thinking is about making it work even if not perfect, production-ready. It could be about rendering a hard-coded "Loading..." or "Success" or "Error" string before you hand it over to your translation team. Or just calling your param names `a` and `b` before you talk to your domain experts about the proper terms. This comes with experience.
This is not something the author invented, it's a well-known technique.
for instance, points 2 and 3 basically suggest throwing YAGNI out the window in favor of premature abstraction. Combine it with the highly-decoupled style in point 1, and you have a nice spaghetti recipe.
I could keep going, but it looks like everyone is piling on this poor guy already.
Another benefit of components is lifecycle. Imagine a "RelativeTime" component; you could have called library functions directly to render "20s ago", but by making it a component you could tell it to update periodically. Cool stuff.
Now, I understand that having such granular components can lead to some performance overhead if you're working on a massive application. If only there was an easy way to do AST transformations that would hoist small stateless components/hooks. Damn, AST transformations could even hoist stateful ones! I'll keep dreaming about it :)
In regards to examples such as `Fetch`, `UserRead` and `EntityActionDelete` from the post, I'm kinda on the fence because, at the same time they are uglier than hooks, they're also more performant because updates would only re-render itself and its children, and not the entire parent component. There's no right or wrong here.