Editor’s note: This React Hooks article was last updated on 4 August 2022 to include a section about common error messages in React Hooks.
React Hooks launched through v16.8 in February of 2019. Though they aimed to allow users to use state and other React features without the need for writing a class. Though this seemed exciting at first, there are a few frustrations and pitfalls that have come with it.
In this article, we’ll go over the common frustrations with React Hooks. We’ll discuss the problem React Hooks intends to solve, what was wrong with the class component, as well as common error messages you may see in React Hooks and how to solve them. Let’s get started!
Before I detail my frustrations with React Hooks, I want to state for the record that I am, for the most part, a fan of React Hooks.
I often hear that the main reason for the existence of Hooks is to replace class components. Sadly, the main heading in the official React site’s post introducing Hooks undersells Hooks with this not-so-bold statement:
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
“This explanation does not give me a lot of motivation to use React Hooks apart from “Classes are not cool, man!””
For my money, React Hooks allow us to address cross-cutting concerns in a much more elegant way than the previous patterns such as mixins, higher-order components, and render props. Functionalities such as logging and authentication are not component-specific and React Hooks allow us to attach this type of reusable behavior to components.
There is something beautiful and pure about the notion of a stateless component that takes some props and returns a React element. It is a pure function and as such, side effect free.
export const Heading: React.FC = ({ level, className, tabIndex, children, ...rest }) => { const Tag = `h${level}` as Taggable; return ( {children} ); };
Unfortunately, the lack of side effects makes these stateless components a bit limited, and in the end, something somewhere must manipulate state. If the component needed to maintain state between render cycles, class components were the only show in town. These class components, often called container components, execute the side effects and pass props down to these pure stateless component functions.
There are several well-documented problems with class-based lifecycle events. One of the biggest complaints is that you often have to repeat logic in componentDidMount
and componentDidUpdate
.
async componentDidMount() { const response = await get(`/users`); this.setState({ users: response.data }); }; async componentDidUpdate(prevProps) { if (prevProps.resource !== this.props.resource) { const response = await get(`/users`); this.setState({ users: response.data }); } };
If you have used React for any length of time, you will have encountered this problem.
With React Hooks, this side effect code can be handled in one place using the effect Hook.
const UsersContainer: React.FC = () => { const [ users, setUsers ] = useState([]); const [ showDetails, setShowDetails ] = useState(false); const fetchUsers = async () => { const response = await get('/users'); setUsers(response.data); }; useEffect( () => { fetchUsers(users) }, [ users ] ); // etc.
The useEffect
Hook is a considerable improvement, but this is a big step away from the pure stateless functions we previously had. Which brings me to my first frustration.
For the record, I am a 50-year-old React fanboy. The one-way data flow will always have a place in my heart after working on an ember application with the insanity of observers and computed properties.
The problem with useEffect
and friends is that it exists nowhere else in the JavaScript landscape. It is unusual and has quirks, and the only way for me to become proficient and iron out these quirks is to use it in the real world and experience some pain.
No tutorial using counters is going to get me into the flow. I am a freelancer and use other frameworks apart from React, and this gives me fatigue. The fact that I need to set up the eslint-plugin-react-hooks
to keep me on the straight and narrow for this specific paradigm does make me feel a bit wary.
The useEffect
Hook can take an optional second argument called the dependencies array that allows you to optimize when React would execute the effect callback. React will make a comparison between each of the values via Object.is
to determine whether anything has changed. If any of the elements are different than the last render cycle, then the effect will be run against the new values.
The comparison works great for primitive JavaScript types, but problems can arise if one of the elements is an object or an array. Object.is
will compare objects and arrays by reference, and there is no way to override this functionality and supply a custom comparator.
Checking the equality of objects or functions by reference is a common gotcha, and I can illustrate this with the following scaled-down version of a problem I encountered:
const useFetch = (config: ApiOptions) => { const [data, setData] = useState(null); useEffect(() => { const { url, skip, take } = config; const resource = `${url}?$skip=${skip}&take=${take}`; axios({ url: resource }).then(response => setData(response.data)); }, [config]); // <-- will fetch on each render return data; }; const App: React.FC = () => { const data = useFetch({ url: "/users", take: 10, skip: 0 }); return <div>{data.map(d => <div>{d})}</div>; };
On line 14, a new object is passed into useFetch
on each render if we do not do something to ensure the same object is used each time. In this scenario, it would be preferable to check this object’s fields and not the object reference.
I do understand why React has not gone down the route of doing deep object compares. You can get into some serious performance problems if not careful. I do seem to revisit this problem a lot, and there are a number of fixes for this. The more dynamic your objects are, the more workarounds you start adding.
There is an ESLint plugin that you really should be using with the automatic –fix setup in your text editor of choice to apply ESLint fixes automatically. I do worry about any new feature that requires an external plugin to check correctness.
The fact that use-deep-object-compare
, use-memo-one
, and others exist is a testimony to this being a common enough problem, or, at the very least, a point of confusion.
Some of the first custom React Hooks to hit the shelves were several useFetch
implementations that use Hooks to query a remote API. Most skirt around the issue of calling the remote API from an event handler because Hooks can only be called from the start of a functional component.
What if the data we have has pagination links and we want to rerun the effect when the user clicks a link? Below is a simple useFetch
example:
const useFetch = (config: ApiOptions): [User[], boolean] => { const [data, setData] = useState<User[]>([]); const [loading, setLoading] = useState(true); useEffect(() => { const { skip, take } = config; api({ skip, take }).then(response => { setData(response); setLoading(false); }); }, [config]); return [data, loading]; }; const App: React.FC = () => { const [currentPage, setCurrentPage] = useState<ApiOptions>({ take: 10, skip: 0 }); const [users, loading] = useFetch(currentPage); if (loading) { return <div>loading....</div>; } return ( <> {users.map((u: User) => ( <div>{u.name}</div> ))} <ul> {[...Array(4).keys()].map((n: number) => ( <li> <button onClick={() => console.log('what do we do now?')}>{n + 1}</button> </li> ))} </ul> </> ); };
The useFetch
Hook will be called once on the first render with this code:
<ul> {[...Array(4).keys()].map((n: number) => ( <li> <button onClick={() => console.log('what do we do now?')}>{n + 1}</button> </li> ))} </ul>
But how would we call the useFetch
Hook from the event handlers of these buttons?
The rules of React Hooks clearly state:
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function.
React Hooks need to be called in the same order each time the component renders. Overreacted beautifully articulates several reasons why this is the case.
You definitely cannot do this:
<button onClick={() => useFetch({ skip: n + 1 * 10, take: 10 })}> {n + 1} </button>
Calling the useFetch
Hook from an event handler breaks the rules of Hooks because you would break the order in which the Hooks are called on each render.
Many Hooks now return a function that can be called from outside of the top-level declaration. The rules of React Hooks still hold and the call to /api/user/1
can be triggered from an event handler.
The code below is from a package I wrote called react-abortable-fetch
:
const { run, state } = useFetch(`/api/users/1`, { executeOnMount: false }); return ( <button disabled={state !== 'READY'} onClick={() => { run(); }} > DO IT </button> );
The call useFetch
returns an object with both a state property and a run
function. The run
function will actually execute the remote query.
React Hooks often throws some frustrating errors. Let’s go over some of these common error messages in React Hooks and discuss why they occur.
This error occurs when a React Hook is called in nested functions. Let’s see an example:
import React, { useState } from "react"; export const WithCounter = (WrappedComponent) => { return (props) => { const [count, setCount] = useState(0); const increment = () => setCount((count) => count + 1); return <WrappedComponent count={count} increment={increment} {...props} />; }; }; const ClickCounter = (props) => { return <button onClick={props.increment}>clicked {props.count} times</button>; }; export default function App() { const Comp = WithCounter(ClickCounter); return ( <div className="App"> <Comp /> </div> ); }
See that we have a high order calculation WithCounter
. In its callback function, notice that we are using useState
hook. This fails the rule of React Hook.
The above code will throw React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
error. In line 5, const [count, setCount] = useState(0);
, we are calling the React useState
Hook there. Even though the function is used as component in the App
component, React will treat the component as a callback function.
React Hooks should not be called in nested functions
This error is similar to the above error that we just learned. React Hooks must be called inside a React function component or a custom React hook.
Let’s say that we have a React component, App
:
function App() {}
We can call any React Hooks or custom Hooks here:
function App() { const [counter, setCounter] = useState(0); }
We just called a useState
hook in the App
component. This is in line with React’s rule of Hooks. But now, let’s say that we have a function in App
component like this:
function App() { const counterFn = () => { const [counter, setCounter] = useState(0); setCounter(counter++) } return ( <> <div> <button onClick={counterFn}>Incr Counter</button> </div> </> ) }
This is a bad way to use React Hooks. See that we are calling useState
inside a function. This will throw the error: React hooks must be called in a React function component or a custom React hook function
. This is because the onClick
function handler counterFn
is not a functional component.
We can call a React Hook inside a custom React Hook. Now, React knows that a function is a Hook with its name starting with use
and may call other React Hooks.
Let’s see an example:
function useCounter() { let counter = 0 const setCounter = () => { counter++ } return { counter, setCounter } }
The above function is a custom React Hook because its name starts with use
.
function useCounter() { const [counter, setCounter] = useState(0) return { counter, setCounter } }
This also is a custom React Hook. Not only does its name start with use
, but it calls the React useState
hook. As much as useCounter
is a function and not a React functional component, it is viable to call a React Hook inside it. In this case, React won’t give us the React hooks must be called in a React function component or a custom React hook function
error.
useEffect
has a missing dependency”This error occurs if we are miss adding a necessary dependency to the useEffect
hook dependency array. Let’s see an example:
function List(props) { const [counter, setCounter] = useState(0); useEffect(() => { setCounter(counter) }, []); return <div>Counter: {counter} </div>; }
We have a useEffect
here. In this circumstance, the useEffect
will run once on initial mount and will not run again. Now, looking at the callback inside the useEffect
, we see that it calls setCounter
with the counter
state as a parameter.
We can see that useEffect
is dependent on the counter
state. In this case, React will show the warning: Either include it or remove the dependency array. eslintreact-hooks/exhaustive-deps
.
To avoid this error, we will need to either remove the dependency array or include counter
state in the dependency array. Let’s include the counter
state in the useEffect
dependency array:
function List(props) { const [counter, setCounter] = useState(0); useEffect(() => { setCounter(counter) }, [counter]); return <div>Counter: {counter} </div>; }
Now, this will clear the warning in our console.
React Hooks have problems, and there is no getting around this fact. I have always loved React’s declarative approach, where state changes and the UI updates. The dependency arrays of useEffect
and friends sound like a declarative way of making this happen.
If the dependency array only contains primitives, then this is great. Unfortunately, problems arise when you have objects and functions in the dependency array. Reference checking is how objects and functions are compared in JavaScript natively. An ill-placed arrow function will lead to a useEffect
Hook spinning into an infinite loop. Developers are then left to ponder the choice between useCallback
, useRef
, useMemo
, etc., to come up with the winning formula.
The fact that React Hooks can only be called from the top-level leads to workarounds that should be handled at the framework level.
React Hooks also force you into working with closures, and I have many scars from unexpected things happening when working with closures. Stale state due to code executing in a closure is one of the problems the Hooks linter sets out to cure.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowFrom basic syntax and advanced techniques to practical applications and error handling, here’s how to use node-cron.
Build a fast, real-time app with Relay 17 to leverage features like optimistic UI updates, GraphQL subscriptions, and seamless data syncing.
Simplify component interaction and dynamic theming in Vue 3 with defineExpose and for better control and flexibility.
Explore how to integrate TypeScript into a Node.js and Express application, leveraging ts-node, nodemon, and TypeScript path aliases.
90 Replies to "Understanding common frustrations with React Hooks"
Exactly! They are admitting a design mistake and the fix is…. to create an entirely new paradigm?? Come on now.
And the idea that code is duplicated between lifecycle methods has to be the flimsiest of excuses possible. Who are these people complaining about this? People that don’t know how to refactor code into a shared function? People that walk into doors because they forgot they had to open them first?
Hooks seem like an odd form of bandwagoning to me. OOP is passe, and FP is the new hotness. But hooks now introduce state into functions. These are no longer the functions of FP. They are not referentially transparent pure functions. They are merely the procedures or subroutines of, say, Visual Basic, with some weird state mechanism grafted on. Side-effects everywhere!
But you know what’s really great for state and side-effects? You know what paradigm we’ve had since the 1980s and earlier for this? OBJECTS AND CLASSES!
Wow.. I didn’t realize there were others not happy with Hooks. I haven’t jumped on the Hooks bandwagon yet, hopefully i can hold off for a bit longer. I am happy to keep writing Class components as they are easy to read and understand; Readability and clarity are more important to me than some fancy new hotness; just the hooks syntax in itself is confusing the crap out of me, let alone all the gotchas that I will encounter later when I finally use hooks. Hopefully the gurus in the React team don’t find an excuse to remove Class components in React v17 !
I’m not scared to say it… hooks suck!
Hopefully more people do the same. I think a lot of people are scared to admit they don’t like hooks. Classes are more verbose, in a good way.
Hooks sintax looks clean but there are many issues when we try to imitate lifecycles,so,i hope in next releases react team will solve better this dependency array,for now,hooks only do well job with CDM && CDU.
I feel your pain as an oldtimer who has travelled widely in dev circles for over 20 years. Having spent time in Erlang and Haskell, I thought React Hooks would be the reason to get excited about React.
However, it took me almost 30 minutes of experimentation and digging through the docs, blog posts, etc to finally understand why this didn’t trigger a re-render:
“`
const [flavors, setFlavorSelection] = useState(initFlavorsObj)
setFlavorSelection( Object.assign( …flavorSelection, {[key]: (flavorSelection[key] += 1)}))
“`
This is the answer ( shallow comparison but I should have thought about that first! )
“`
setFlavorSelection( Object.assign({}, …flavorSelection, {[key]: (flavorSelection[key] += 1)}))
“`
I’m also starting to see folks use Hooks where better functional concepts such as curries, composition, etc are better suited. I also see custom hooks which are an awful amalgamation of noodle code. Devs who devote 80 hours to learning hooks would be better off learning something else. Let’s face it, React is really showing it’s age and Hooks do not get React closer to functional programming paradigms/frameworks like Elm or Reason
After trying to use function components and hooks in a real-world project several times, I always returned to class components. Unless your function components are incredibly simple things can get very messy and complex very quickly. I do like the idea of function components and hooks, but I certainly don’t enjoy working with them in their current form.
The React team’s reasoning for the introduction of hooks is also sketchy. Reusing code? Surely that comes as part of sensible design decisions, e.g. DRY. Not understanding how this.* works? That’s something that doesn’t just affect class instances, it affects JS as a whole; if you don’t understand it then your career as a JS/TS programmer will be going nowhere fast.
The code base for a typical project also contains a lot more than components. Having one part of the code using one paradigm (function components), and another part of the code using a different paradigm (oop), is disconcerting at best.
I don’t believe the React team will deprecate class components for a long time, and when they do I will be taking a cut of their source code to preserve class components, but when the team do eventually deprecate class components I imagine that will be the beginning of the end for React as we know it. Vue may become the de-facto standard at that point unless something new and magical arrives in the meantime.
A new/separate React Component 2.0 would have been a better solution, in my opinion.
Deprecating componentWillReceiveProps and substituting it with the static getDerivedStateFromProps is big step towards deprecating class components.
Recently I was trying to use hooks in my project, ended giving up. It’s too painful to use, like writing multiple useEffect? I know it’s a ‘separate concern’ thing, but kinda verbose to me. Let alone the dependency array, messing up the lifescycle finally made me giving up, I’m sticking with class component for now.
Hey Chris, I totally appreciate your frustrations because it reminds me of myself last July/August/September when I was getting up to speed with React Hooks. In contrast though, last week I had to delve into a colleague’s older class component React code and I found it painful. I suppose I’ve gotten so used to doing things “the new way” that it’s now painful to go back.
Whenever I’m tasked with teaching someone React Hooks, I always encourage them to think about doing things “indirectly” rather than “directly”. So, for example, say we had an app where the user entered a UPC code off of a barcode and then pressed Submit to look up that product’s info.
In my app I would wire up the `onChange` event of the Input element to a `useState` variable like this:
“`
const [ upc, setUpc ] = useState(null);
setUpc(event.target.value)} />
“`
And then the Submit button would be wired up along with a `useState` variable like this:
“`
const [ isGetProductInfo, setIsGetProductInfo ] = useState(false);
setIsGetProductInfo(true)}>Submit
“`
Then there would be a `useEffect` like this to fetch and save the product’s info:
“`
const [ productInfo, setProductInfo ] = useState(null);
useEffect(() => {
if (isGetProductInfo) {
requests.get(`$(requests.API_ROOT())account_services/product_lookup/$(upc)`)
.then(response => {
setProductInfo(response.data);
}
.catch(error => { … }
);
setIsGetProductInfo(false);
}
}, [isGetProductInfo]);
“`
I realize this code is very basic but it illustrates a successful pattern of code to demonstrate using functional components, `useEffect`, and `useState` in React. Hope it helps!
IMHO hooks are a poor implementation of functional reactive programming (FRP)… But it is a welcome first step into that direction. At least it provides local behaviors, so no need to read all component code to understand what is going on.
Even better would be a real synchronous dataflow programming language for UIs, something like Lucid Synchrone did for DSPs. Unfortunately, earlier attempts like FlapJAX didn’t catch on, neither do new great libraries like SodiumFRP. RxJS seems to catch on, but is asynchronous, and not very suitable for UI programming due to its glitches. Maybe Svelte will do better. MobX already works for many people, but is limited compared to real FRP.
Again IMO when you look at user interfaces as a circuit of signals, many hard programming problems become easier, and more scalable. You actually don’t need a virtual DOM either for this, Functional Reactive Programming is about taming mutable state, not avoiding it. You just connect the mutable cells of data, and let the runtime sort the dataflow graph according to ranks, and you get perfectly deterministic, testable and reason-able behavior.
Too bad no popular frameworks takes such an approach, most likely because most of us are either imperative programmers, object oriented programmers, or functional programmers. Functional Reactive Programming is really different, it is actually dataflow programming, something that is very popular in spreadsheets, 3D software (Maya, Houdini, Substance Designer), and Digital Signal Processing. But not yet in the frontend world.
I absolutely love hooks. They make state management so much easier to understand and read. I can understand frustration as it is a different way to think especially when coming from classes. But there is so much more flexibility with using hooks and they do offer performance enhancements. They open up a pandoras box of possibilities especially in the global state management realm. There is always pain when learning new things but do not get stuck in the older ways. Hooks are the future and I am excited to see them mature.
Very good article…
I have also noticed the repition of code in Lifecycle blocks of React. That was a point of concern, sometime you would have to repeat code in componentWillRecieveProps also. That was a bit furstrating.
Wouldn’t it be easier just to make the useFetch depend on “skip”? Then in the click handler you could just update skip and React would handle refetching the data.
Well put. A paradigm shift require a different way of thinking. The frustration and struggle coming from this article stems from trying to shoehorn hooks into the old way of thinking.
I thought the same for a while, but now it’s clear to me this is fallacious. Yes, there is frustration as well but for beginners as they make the shift; After building a significant feature, I have been starting to hit these issued discussed by OP and I have scratched my head aplenty, this complexity cannot be blamed on incapability of the community at large in adaptation. A lot of people are even jumpshipping as the foresee the direction we are going, luckily for my main Product, I started out with Vue and it’s staying that way.
These issues certainly need to be incrementally addressed within minor version(s) rather than leaving progress to radical changes like Facebook has been doing. I am skeptical at this point, perhaps they like shocking the community to keep themselves relevant, and then leave half-assed documentation to promote discussions, courses and discourse around react in general.
^THIS
Hooks are really frustrating, it’s a step in the right direction, but 10 steps back in everything else!
I’ve been using xState and react, mostly with an HoC pattern and it’s been working beautifully. xState is a FSM library that allows independent state machines to communicate via events/messages. I’ve adopted an approach similar to Erlang/Elixir and it has worked wonders in the frontend world.
Components are simple functions: props in, html out. Every action send a signal/event and actors react to it, sometimes communicating with other actors, other times just triggering re renders. You get this nice conceptual graph that you can visualise and see how an action interacts with your whole app.
Adding new functionality is usually a case of simply spent routing events to the appropriate actors, with the occasional new actor when a new behaviour is required.
“and there is no way to override this functionality and supply a custom comparator.”
what would you like to use as a comparator?
you want to compare for example user.id it’s easy just give user.id in the dependency array instead of user…user wouldn’t make sense:
const x = {name: “john”, age: 30};
const y = {name: “john”, age: 30};
console.log(x === y); // false
console.log(Object.is(x, y)); // false
what’s wrong with the dependency array?
Sry but I think you didn’t get hooks correctly. Hooks have states, but the state in a hook is immutable, it doesn’t change!
Everytime you say setState(newState) in a hook, you tell React: call this function with the new value for this state (you can think of it more like function arguments).
Unlike in Class-Components, this.state is mutable which means it changes in TIME which means side-effects!
React will be eventually ruined by its hooks.
You can see that the fundamental concept of React is to organize the HTML components into classes / objects.
Hooks are like noobs in programming to put everything in the Global scope because they are lazy to know what is static functions, what is JS class object, how classes should be constructed, how the components should be organized, lazy to know about the mechanism in such frameworks but just do not want any restrictions in their coding to achieve their one-off results.
I can seldom find normal piece of JS works that is properly organized.
I am afraid that this profession has been a great volume of water inside nowadays.
I just want to say, JS has a reason to move from class-less to class.
I agree with the author that mostly I like hooks but there is hidden complexity. Maybe the complexity is just in plain site with classes but it does pose the same issues. The issue mainly are developers and their ideas on how things work based on their current understanding. With hooks it’s all too easy to place them with the component you are rendering this causes a coupling of the data to the presentation. I’ve hardly seen any code that passes the data retrieved into a pure component with no concerns with data. Hooks doesn’t lend to such a coding style as it’s not convenient to separate the “connected” component to the “non-connected” components.
Tools will always change and have disadvantages and advantages. I hope this is a stepping stone to a better React in the future and it won’t ruin React because the grass isn’t always greener on the other side.
In reality most of the complaints I see here in the comments are just people not actually using them the right way.
My only true complaint around hooks is that there are too many libraries that have bandwaggoned into *only* supporting hooks, so that I’m *required* to wrap my more complex class component (because there is definitely still a strong need for both) in a functional component, just so I can pass the hook result into the class component as a prop
When i first learnt ReactJS, i loved it the way class based component works and the easiest to understand. suddenly the hooks took away the limelight and forced us to learn react from scratch and shift it. now at this point of development I regret; why the hack was hooks needed? and if needed then why to put so much on it to replace the classes.. class method was a standard way of doing and i pray React gods to rethink.. Classes are more elegant way of doing things. Hooks are confusing not only developers but the end product. Better waste your time to improve the class based methods. Otherwise you won’t stand in competition for log run as hooks are not the way developers would like to approach.
My vote for Classes.
There’s a fundamental issue with using implementations like `useFetch` hook. Even though this is presented like feature of hooks.
For ex. you have simple TODO app. You get the list using `useFetch`. Everything is good so far. The problem arises when you save a new item. If you got the list using the `useFetch` hook, you will be unable to refresh the data, without using workarounds, which defeats the purpose of using that kind of hook in the first place.
Hooks can (and often should) be used inside other hooks. In your case, you should probably create a useTodoList hook, complete with data fetching (using useFetch) and state management. Then, from the useTodoList hook, you simply return the todoList state, along with functions to create/add/update data.
Yes, this is the way it is typically done and I’m yet to find a better way but imho such hooks have too many responsibilities, fetching, updating and maintaining state. It’s a lot of pack into one function and yet I often see people doing this because this is the path of least resistance when working with hooks. The alternative is to have separate hooks (one for fetching and one for updating) but then you have to manage sharing state between them.
Hooks seem to me to be the poster child of a leaky and bad abstraction. They look nice in simple examples, but as soon as you try to do anything with real world complexity, then you quickly end up going down a rabbit hole of dependency trees, deep properties, stale encapsulations and the like.
For example, to create an interval or timeout callback function which examines or changes the component state you have to consider that the state variable that existed at the time the callback function was created might be a stale version of the state, so you have to make sure that you clearInterval or clearTimeout to unregister the old callback and then recreate it with something that points at the newest version of the state variable or you have to use other techniques involving “useRef” or “useCallback”.
You end up having to spend a great deal of mental energy reasoning about how the hooks system works under the hood in order to use in all but the most basic and contrived cases. I can’t really fathom why the powers that be at Facebook thought that it would good to abstract state using immutable variables that are recreated each render, but then create a bunch of backdoors and workarounds rather than just have a simple mutable this.state. I suppose that it was probably a case of the React team needing to justify their own existence by releasing new and revolutionary features rather than just making incremental improvements.
I want to lay some points from my perspective as a dev who wrote big project with both classes and hooks.
1. useEffect
Pros:
– separating concerns
– adding clarity when something should be reactive (always use useEffect dependancy array linted)
– adding clarity where is the “logic” in the component
– tip: keep useEffects as small as possible. For bigger ones you can replace arrow function with normal one to use the name to describe the effect: useEffect(function thisIsProductDecision() { }, []);
– helps you to handle easily clean ups (timers, CRUD etc).
Cons:
– can get hard to understand if the dependancy array gets big. This is why you should strive to separate as much as possible.
2. useState
Pros:
– possibility to update a single state instance and not the entire component state
Cons:
– makes it a bit hard if you decide to work with one object state insteaed of separated onces
3. Class components
I read people saying class components are easier to read and understand. I can’t agree with this statement not only because I beleive it’s just a habit but because class components:
– Have more complex structure. You can write code which jumps trough time and space like Rick and Morty: didMount, willMount, willReceiveProps, didUpdate etc. All of those are too specific time events which can interfier with one another and puzzle the reader and the writer. I feel that useEffects and hook based React is more one diirectional and it makes you think not in “time” and “when” something will happen but in more reactive way as “why” something happened (state changed in dependancy array). Instead of mastering and deeply understand 10 lifecycle events, and understand how they can interact/interfere with eachother, with hooks you need to do that only once for only one thing: useEffect. If you master that, you mastered React.
– Sharing business logic you must agree is not easy, readable and the solutions like HOC are running away of the declarative nature to which we should strive. Custom hooks are more prediictable, strict and easy to understand then 5 nested HOCs.
– Classes gives the OOP feel into React, which React is not trying to do. This happened very clear with the Hook based React, where the functional path is layed in it’s roots. There is nothing wrong with OOP (probably) but if this is what you want, go to Angular.
In the end you can create good, readable and scalable project with both classes and hooks and you can also do the opposite with both of them. React is a tool and the people working with it are responsible of the outcome. Eeither way I think that Hooks based React is a more elegant, functional and advanced tool.
By the way I’ll appreciate if you give a go with my state manager package which is (I think) more powerfull then Redux and much much easier to use: https://www.npmjs.com/package/store-me
React hooks are a problem. Instead of micro updating state, you’re left with doing giant monolithic updates of ALL of the state. You can’t simply just setSomeState, then setMoreState in sequence. This causes components to unmount and things just break. The paradigm now is “gigantic global state updates everywhere”. How is this not a problem?
Ever try updating state within a recursive setTimeout function? Dan keeps on doubling down saying that React + hooks aren’t broken but it’s pretty obvious now that he just won’t admit he’s wrong (and he is).
I dislike hooks because my colleagues abuse them. They go too far with using them when a different approach would be better (objectively!).
And the article does not even talk about testing. Wow, how did this get so bad so quickly? Also, unnecessarily coupling business logic with the view layer makes it so much harder to refactor. I understand that in some cases the solution with hooks will be more elegant, but overall seeing what is happening in a large code base on which I am working on, I think it’s a step back from code evolution standpoint.