Common mistakes writing React components with hooks
lorenzweiss.de
lorenzweiss.de
The other tips are fine; effects should have a single responsibility and links and buttons should be accessible.
Yeah we're preventing re-renders there but what are we really preventing / saving, nothing significant?
Anytime I run into a situation where I didn't like how / when a component was re-rendering, it absolutely was not because there was a random bit of state like a counter was in state that didn't need to be... usually it was just a more complex situation unfolding.
It's a good illustrative example, but not a 'common mistake' IMO.
It is in a browser context, but less with React Native which relies way more on refs. It also never hurts (imho) to explain why refs exist and why/where to use them as they can easily be abused (and often are) by devs trying to replicate OOP patterns in React.
EDIT: "This is dangerous" is also the wrong label for the 2nd example. Should be "This is not the cleanest way"
EDIT2: "This is dangerous" is also the wrong label for the 3rd example. Should be "This is not the most readable". I'd also note that I'm surprised the author has seen this mistake a lot; the "solution" looks like the happy path most people would follow in the first place.
EDIT3: "This is dangerous" in the 4th example should be replaced with "This could be simplified"
EDIT4: "This is dangerous" in the 5th example should be "This makes redundant API calls". I'd also note that I'm surprised this example is even included. How could anyone miss that fetchData() is being called every time updateBreadcrumbs() is called?
Sure, in the case someone would alter say the function provided as props it should be included in the dependency array. Yet in most cases, such as the example #3 in the article, this would not happen (or be even desired). Rather, if it did it would be a bug and an appropriate error would better.
So if you wanted to adhere to the strict CRA linter's exhaustive dependencies rule, you should add the fetchData function as a dependency. Or if you moved the whole function inside the useEffect, then the onSuccess. Which makes even less sense now that I've written it down.
const fetchData = () => {
setLoading(true);
callApi()
.then((fetchedData) => {
setData(fetchedData);
onSuccess();
})
.catch((err) => setError(err))
.finally(() => setLoading(false));
};
useEffect(() => {
fetchData();
}, []);
becomes useEffect(() => {
setLoading(true);
callApi()
.then((fetchedData) => {
setData(fetchedData);
onSuccess();
})
.catch((err) => setError(err))
.finally(() => setLoading(false));
}, [onSuccess]);* I sometimes have to use an empty dependency array inside useEffect to run something only when component first loads. The linter yells at me, but it makes sense. I once read an article which told to put things in functions and wrap those functions in useCallback then that'd be proper, but can't imagine the value. I think most of the internet simply does empty dependency and ignores what creators of hooks suggest.
* I want to run things before the component mounts, like API calls.
* I often want my useEffect to trigger when only one of the state variables it references is updated. Again, linter screams at me and I add an exception.
* Because useEffect triggers after component mounts, and only then, it's sometimes difficult to avoid some flicker. For example I want to do something when a components prop (say "loading") changes from "true" to "false". Loading has finished, prop has been updated, component re-rendered, and only then I can trigger what I really wanted to do on that transition. I think "componentWillReceiveProps" would have solved this, but there is no functional equivalent.
Basically if there is a person who uses "useCallback" out there "correctly" I have not met them yet. My junior subordinates misuse useEffect quiet often. I often see (and use) empty dependency arrays. I see (and use) partial dependency arrays, often adding linter exceptions. Reading articles about "proper" ways of doing it sorta makes sense but it is easy to forget what the hooks authors really meant.
I think hooks is a good feature, but the authors made too many assumptions when developing them. There is a philosophy underlying them but that philosophy is somewhat incongruent with the philosophy of "I just want to get this done". React is just one tool, I have 20 more things to do every day and understanding the full philosophy of functional dependencies and when and how to properly use "useCallback" - I just do not have time, and junior devs get confused even more.
function useFirstRenderComplete() {
const renderCountRef = useRef(0);
useEffect(() => {
if (renderCountRef.current) {
return;
}
renderCountRef.current += 1;
}, []);
return renderCountRef.current !== 0;
} class Foo extends React.Component {
componentDidMount() {
fetchFoo(this.props.id)
.then(foo => this.setState({foo}))
}
render() { ... }
}
This component is fundamentally broken because there is no `componentDidUpdate` to re-call `fetchFoo` when `this.props.id` changes. I could easily be rendering a new id with an old id's foo. The developer is making an assumption that the initial value of `id` will never change, therefore making assumptions about how the parent component is instantiating `Foo`. Rewriting this using hooks makes this immediately obvious: const Foo = ({id}) => {
const [foo, setFoo] = React.useState(null)
React.useEfect(() => {
fetchFoo(id).then(setFoo)
, []) // Linter complains, and it should!
return ...
}
There's nothing wrong with using the empty array as your deps so long as it's actually what you want to do. If `fetchFoo()` didn't require any arguments and we only wanted to invoke it once [] would absolutely be the right dependency array. There are few cases I can think of where disabling the linter check is correct. The most common occurence in my code is if the effect uses a value not required to be fresh: e.g. something used for an optimization.> fetchData();
> updateBreadcrumbs();
> }, [location.pathname]);
> There are two use cases, the "data-fetching" and "displaying breadcrumbs". Both are updated with an useEffect hook. This single useEffect hooks will run when the fetchData and updateBreadcrumbs functions or the location changes.
Is this right? Wouldn't they only update when `location.pathname` changes? Also, there should be a whole discussion on `useCallback` which is not used here and it can be quite important, plus linters would complain of missing dependencies for the useEffect.
a much bigger problem with code like this is the fetching of data gets much more complicated when the second parameter of useEffect is not just an empty array
Firstly, the two examples aren't semantically equivalent. onSuccess can change between renders, so in the "wrong" version, the version of onSuccess that'll be executed is the latest one. Whilst in the "correct" version, it'll be the one defined during the render which was in play during the initial render.
If you always want to make use of the latest onSuccess in the "correct" version, you'll probably want to look into putting it on a ref.
Another issue with the "wrong" version is that onSuccess will be called a second time if its identity changes after it's already been called once, which is quite likely if its parent re-renders and doesn't make use of useCallback.
Ultimately I think the "correct" version is indeed better, but the question of which is the correct onSuccess handler is one that always needs to be seriously considered.
In the examples, if a function is only ever called within one hook callback, it should just be defined in that hook callback. Doing so would expose what you're missing in your dependency array.
So yes, these mistakes are very common. There's just quite a few ways to do things, and if you don't do any research, they all have the same outcome.
Providing convenient mechanisms for managing high-level/application state moves them squarely into the framework category, imo.
Angular feels fully fleshed out, adheres to MVC mostly and has nice separation of html, css and the UI TS code. I come from a native coding background so this feels logical and I can jump into a new project and get my bearings pretty quickly.
React on the other hand seems to be jQuery gone mad with CSS, JS and pseudo HTML all mixed together. Ternary statements in JSX is awful to look at.
To me it seems React is solely there to solve the V in MVC and then with a host of additional libraries (of the users choice) some kind of MVC system can be cobbled together if needs be.
I'm not saying React is bad, clearly it is very popular and has it's great use cases, but I don't think it's Angular vs React.
The other thing I can't stand about Angular is that it puts its proprietary templating language "in control" of components. Which means if you want to do something more complicated that isn't implemented in the templating language, then you just can't (or you end up fighting the framework). With React, it's as if the equivalent of Angular's controllers (components? - it's been a while since I used Angular) were in control. The templating is only for templating.
FWIW, I agree on the ternary operator issue. Although IMO this is actually a deficiency in JavaScript rather than in React/JSX: If if-else and switch in JavaScript were expressions then this problem would disappear completely.
with https://github.com/PatrickJS/angular-intercom/blob/master/an...
And that's the small one. There's also https://github.com/CaliStyle/ng-intercom
As another example, consider HTTP. Angular provides its own special HTTP class. With React I just use the standard Fetch API.
I've mixed plenty of html and js over the years and even html and php and it's nice to not have to anymore. Something about JSX rubs me the wrong way.
How data flows into that tree is not its concern. This is why there's a wide variety of state management libraries available. Empirically I agree teams often get state management wrong, because it requires a different skillset than developing reusable UI components.
Frameworks like Angular make a lot of hard choices outside the view layer for you.
As for mixing HTML, JS and CSS, I consider that a feature. Why do you want those separated? The reason for me to group code in a file, a folder or at a higher level, in a repository, is that I create a little mental context for what I am going to work on and I want the structure to enable that as much as possible. Now, when I am focusing on some feature it is much more likely that I will be switching back and forth between the HTML, CSS and JS of a component than switching between the CSS of one component to another.
It happens, of course, that I get into a mode where I want to edit many CSS files in one go for whatever reason, it's just much less common and thus I don't want to optimize for that case.
You don't, there was a time when separation of concerns got mistranslated into separation of technologies and it became an almost religious mantra. But if HTML, CSS, and JS all combined to create a single black box component, that is a single concern, separating the technologies for separations sake only serves to reduce the reasonably of the black box. There are still those that where taught in that time, that separation of technologies and separation of concerns where the same thing, but they are not and that is well evident in that react component are easy to reason about.
Most interesting thing in React for me is the ability to pass an element as an argument to another component - it's impossible in Angular. I’m not sure there is a lot of real-life cases for this feature, but still, I’m impressed by this level of flexibility.
React isn't just V of MVC, it's VC at least, letting you write your models wherever you want (pure js functions? classes - go ahead).
Some advantages of React are the weak points simultaneously - ngModel, ngIf, ngFor - they are removing a lot of boilerplate you have to write in React.
It's a framework which advertises itself as a library.
In reality, there is nothing library-like about bypassing the DOM for rendering and making developers use a custom programming language (JSX)... and please don't give me this tired old argument that JSX is not compulsory! Has anyone ever seen a real React application which does not use JSX? Exactly.
If 99.9999% of developers are using React as a framework, then for all general communication purposes, React is a framework.
I've built one without using JSX and I think it's superior. The problem is the tooling and type support just isn't there so going against the grain here is pretty much asking to be burnt.
react-router, redux etc. are all different libraries. Point still stands: You have to pick and assemble these different parts yourself. And then still all those libraries don't call into your code, but your code includes and wires them up.
Though I don't know if it still can be considered as framework or not.
A project is added to a framework.
So how is React a library? You can't just utilize part of React within an existing Angular app. I've built shims between the two frameworks and you can't just use React inside of Angular. When you utilize a React component inside of Angular, everything Angular about the application goes out the window and it becomes a mini React app starting from that component, just with the data originating from an Angular app.
The definition of a framework boils down to inversion of control, right? You define your application, and then it is run within the React "framework" context, which calls various predefined methods/functions.
All of the lifecycle methods in React are a good indicator, to me, that it is indeed a framework. You define these functions, and they are called by the framework. Inversion of control.
A framework calls your code.
Really great explanation here: https://stackoverflow.com/posts/15600924/revisions
Edit: another really nice exposé here: The Difference Between a Framework and a Library — https://www.freecodecamp.org/news/the-difference-between-a-f...
Let's say that if I want my own version of select2, I can develop it with react, though not easy.
But, as others have pointed out in this thread, a much more useful distinction is where the library's code gets called in the stack. If your code is at the top of the call stack with library code below it, then it's a framework. If their code is at the top, then it's a library. In short, you call a library, a framework calls you (inversion of control). So in that sense React is most definitely a framework.
Also, I'm willing to be wrong on this one if somebody can give me a meaningful and objective reason that React should be considered a library but Angular should be considered a framework.
- You hand it your code and it calls your code when it wants to
- Your code must conform to React's expectations.
React is _not_ a framework, because:
- It only focuses on one thing: defining a tree of UI components. It doesn't include anything for HTTP requests, module definitions, generating expected file structures, or any of the other stuff you'd see in Angular and Ember.
- You are responsible for initializing React in your app, and you can use it in a range of situations, from a full-bore SPA to adding some interactive widgets to an existing page.
All those are true simultaneously.
At least for me hooks dramatically clean up the code.
Outside of error boundaries (I think those still have to be classes) any new component for me is a function component and if it needs state, has hooks.
Granted I still maintain a lot of class components back from before the days of hooks.
For simple components hooks may be fine. For more complex ones I prefer classes.
https://reactjs.org/docs/hooks-reference.html#useref
The example even uses .focus()
During this time, I started playing around with React and I thought, I learned my lesson, it's class-based components all the way. Yet again, the community started going to "other" way with function-based components and I was steadfast in my attachment to classes - refusing to budge.
Of course, I eventually made the switch and wish I had done so sooner. Once you get the hang of hooks, they're so much easier to reason about and, as you said, make me much more productive.
However without hooks, only class based component has side effects and lifecycle, so you like it, then made switch to hooks because it's neat.
Keep your state externally and just grab what you need on each subcomponent with the `connect` function from `react-redux`. Easy peasy front-end development.
For example, you can have a date picker that just accepts variables to display the current state and parent component that reads data from the store and passes it into the date picker component.
This method is also great for debugging, since you can just replay the state transitions over again if there is an application error. If the component holds the state, then it can be cumbersome to reproduce the error.
if you have no state in the component that would not be possible. you can also not wrap this with an other component that can hold this state because you don't want components with state. that means all behaviour that requires state would have the state in the store this will just be crazy
Most of the time it mirrors the structure of a file system which is the exact way (almost) every program is written.
As with most software projects, keeping organized is like 80% of the battle.
In the end, they are just another tool -- knowing how to use the tool appropriately is the critical part.