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.
Would you be interested in joining LogRocket's developer community?
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 nowFix sticky positioning issues in CSS, from missing offsets to overflow conflicts in flex, grid, and container height constraints.
From basic syntax and advanced techniques to practical applications and error handling, here’s how to use node-cron.
The Angular tree view can be hard to get right, but once you understand it, it can be quite a powerful visual representation.
In this post, we’ll compare Babel and SWC based on setup, execution, and speed.
90 Replies to "Understanding common frustrations with React Hooks"
Firstly, thanks for the article, it’s contentful as always.
But let me try to give a different perspective because hooks design is dear to my heart.
The main problem I see with this usage of useFetch is that you seem to perceive hook as a service. But there is a profound difference between those.
Be it ordinary or custom, – hook is still an extension of your component behavior. Or at least it’s supposed to be one. You are literally “hooking” into your component lifecycle stages via the hooks abstraction. So you don’t control the hook end to end all the time. Every hook has mount and update phases aligning with the natural component lifecycles. So it’s all reactionary.
Services, on the other hand, are entirely controllable. You initiate the service, and you get the result any time you want. That’s why calling a hook from an event callback to me seems unnatural and breaks uniflow. Because you cannot control the component lifecycle directly. You cannot start a useEffect on demand. The same way you cannot call componentDidMount directly from an event callback. You need to initiate something that will cause the component to change its state and trigger effect callbacks eventually.
So in this sense, hooks are perfectly aligned with the general component unidirectional flow that existed before hooks.
Now about the shallow comparison of config.
You should treat config object the same way regardless of whether it’s a hook or not.
Let’s you don’t have hooks how would you go about reasoning about this useFetch in a pre hook setup
I guess it could’ve been something like this
“`
import React from “react”;
import ReactDOM from “react-dom”;
import “./styles.css”;
const axios = ({ url }) =>
Promise.resolve({
data: [
{ id: 1, url },
{ id: 2, url },
{ id: 3, url },
{ id: 4, url },
{ id: 5, url }
]
});
class UseFetchComponent extends React.PureComponent {
state = {
data: null
};
handleFetch() {
const { url, skip, take } = this.props.config;
const resource = `${url}?$skip=${skip}&take=${take}`;
axios({ url: resource }).then(response =>
this.setState({ data: response.data })
);
}
componentDidMount() {
this.handleFetch();
}
componentDidUpdate(prevProps) {
if (prevProps.config !== this.props.config) {
this.handleFetch();
}
}
render() {
const { data } = this.state;
console.log(this.props.config);
if (data) {
return data.map(d => {JSON.stringify(d)});
}
return null;
}
}
class App extends React.Component {
state = {
config: { url: “test”, skip: 20, take: 10 }
};
handleSetRandomConfig = () => {
this.setState(prevState => ({
config: {
…prevState.config,
skip: (Math.random() * 100) | 0,
take: (Math.random() * 100) | 0
}
}));
};
render() {
return (
Set random config
);
}
}
const rootElement = document.getElementById(“root”);
ReactDOM.render(, rootElement);
“`
In which case config is stored in some outer state and updated immutably, every time the prop changes, the object identity changes, thus you can rely on reference checks. To me this is good practice, and it sets you up on the path to the pit of success.
Let me know if this has been helpful.
Didn’t know there is no code format support, so here is the above example in codesandbox https://codesandbox.io/s/delicate-rgb-wjwcw
Correction
*To me this is good practice, and it puts you on the path to the pit of success.
Welcome to the oop landscape. Sucks, huh.
I agree, the reason ostensibly was because `this` issues are hard to understand for noobs and a frequent question on SO. However understanding `this` is really not that hard and as long as you use arrow methods for events and nested function callback you will rarely run into problems.
So they traded this issue for hugely increasing their API surface area and adding some really none trivial concepts like stale data/mem leaks in closures if depedencies aren’t listed. Sub optimal rendering through all functions being created and breaking any pure checks by default. Infinite loops if your hooks both reads and writes to a state value, since you depend on it but you also update it. Added complexity when using things like debounce. And scaling issues since you cannot break down a component as hooks cannot appear out of order as you mentioned.
The benefit I see is that hooks are cleaner looking than multiple wrapping HOCs, but I don’t think the trade offs were worth it.
I am curious if you have tried pushing side-effects out to a redux middleware library (and keeping the state in a redux store), and if so, how that compares to some of these effects/api calls in react hooks? I cannot say that I am huge fan of react hooks beyond updating trivial local state, though I have not had much experience with them.
I’ve done a lot of redux and I think this is partly why hooks exist. Things like loading and error states should not be in the global state but end up there in redux. Hooks definitely are an answer to this.
The problem is that we’re essentially writing what ought to be pure functions, a la A => B, and attempting to add to them effectful behavior, without changing their type. In reality, once we had effectful behavior to a component their type is more like A => F[B]. This is the reality we should work with. If we have multiple effects, the type is still A => F[F[B]]. Thankfully, if we do things right, we should be able to compose effects! I agree with you, Paul. To me, React’s hook system feels like kludgy.
great reply, A => F[F[B]] would be great.
great reply. I think I prefer this to HOCs, there just seem to me to be a hell of a lot of gotchas.
I seems most of your concerns can be solved by destructuring props and using the properties in your dependency array.
The dependency array is a declarative representation of what causes the effect to run. So this includes mount and any changes after… simply use “useState” and “useEffect” as intended in concert with destructured properties and the dependency array and you can easily run effect when and as desired.
https://codesandbox.io/embed/hardcore-shtern-m0ui3
I skirt around the problem of stale state by setting a ref at the top of the function that contains whatever the props are that render. Since ref references don’t change from render to render, referencing the ref-contained props inside my useEffect or useCallback always returns the most up to date props. That also means that the dependency arrays for these functions are always empty, the functions only ever created once, which prevents additional unnecessary rerendering of children due to changed function references. The only requirement is that the functions I use inside useEffect and friends have to use props through the ref reference.
Effectively, I created a managed “this” variable.
What do you mean by “ref references”?
Can you post a basic example?
Dwight, the ref at the top is actually a pattern that many are settling on. Thank you for bringing this to my attention.
Few other options:
1. use different hook: say Apollo provides both `useQuery` that executes immediately and `useLazyQuery` that will return “runner” inside array result. So you will be able to initiate request later(probably someone already created lazy version of useFetch).
2. move fetching into separate component. Not sure it looks fine but Apollo provided “ and “ component to use them like `{someFlag && <Mutation …` and people used them that way.
Sure, here: https://gist.github.com/dwighthouse/bef9d4161f4255a9f7a060fabd0a3240
maybe a beginner here but i dont understand why for this usefetch example
we cant memoize the data using usecallback / usememo and then change the state (current pagination) accordingly.
then we would fetch only if needed?
It’s funny how you had the right code all along, just didn’t used it.
The key is “setCurrentPage”.
setCurrentPage({ skip: 10 * n, take: 10 })}>
Nice and simple, everything works with no complicated “solutions” (sorry, I couldn’t follow the overly complicated solutions that followed).
Here is a codesandbox with the incredibly easy and elegant solution:
https://codesandbox.io/s/dependency-array-v2ehx
Hooks take a bit of getting used to.
In the beginning I thought they complicate the code unnecessarily.
Until I realized they can just be extracted into custom hooks.
The real power of hooks, besides the fact they are declarative, is composition and by means of composition they can be abstracted away as custom hooks.
React is all about declarative UI composition but lacked the “primitive” for having stateful logic composition in an elegant and declarative way. Until hooks.
I completely agree, and am happy to see I’m not alone.
React Hooks = Angular 2
Yup, I said it
This example is probably the best I’ve seen so far, to interests me in adopting hooks. 👍
Thanks!
I have been using React for more than four years and I’m considering switching to something else because of Hooks. At first I thought I would just not use them, but everyone in the React ecosystem seems to be adopting them.
Hooks are so wrong! I do not even want to spend time and energy to to argue against them.
So long React!
> I have been using React for more than four years and I’m considering switching to something else because of Hooks.
I’m in the same boat. I spent 2 days this week trying to get on board with them.
The re-usability they provide is super attractive, but the comprehension when reading the code, and the pitfalls are a huge turn off. Having to use closures and `useCallback` to avoid redefining event handlers on every render looks awful.
I also have no interest in switching to `useReducer` and making magic string, switch statements everywhere. I’ve avoided Redux for the last 4 years over that as well.
I feel like I’ve invested 4 years of my time in the wrong technology and I’m ready to find the next thing.
Between the original mixins debacle and moving to classes, and now this; I don’t trust the React maintainers to give me a solid technology to build a project that will last more than 2 years.
This is my problem with Hooks too. When I first started using React, I used class components, then I switched to hooks because my code at the time seemed to need them, and I read that “hooks are awesome!” over and over again. But when it came time to hook up my React code to REST calls, then I learned that Hooks result in unintuitive, magical looking code (useEffect), and I’m now in the process of switching my codebase back to class components. They’re more verbose yes, but they’re much easier to read.
So I guess for me the real question is, why hooks? Or more importantly, why is the extra cognitive load on the developer to figure out what hooks are doing worth it–what goodness do hooks bring over classes beyond simply a “cool factor”? I’m not saying they aren’t worth it, just that it hasn’t been made clear to me yet, and so I’m switching back, as I’m afraid Hooks are going to make my code harder to understand and maintain. At the end of the day, maintainability is really important.
what magic string do you mean?
> what magic string do you mean?
On the `useReducer` example:
https://reactjs.org/docs/hooks-reference.html#usereducer
I don’t want to pass around `increment` and `decrement` strings, and build switch statements to handle them.
Typescript can help eliminate the magic string issue, but still not a fan of switch statements. Would rather have concrete functions in a “store” to call.
“`
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case ‘increment’:
return {count: state.count + 1};
case ‘decrement’:
return {count: state.count – 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
Count: {state.count}
dispatch({type: ‘increment’})}>+
dispatch({type: ‘decrement’})}>-
);
}
“`
Actually, the same as with “regular redux” you may use constants for Action Type. This demo sample just concentrates on different things.
> Actually, the same as with “regular redux” you may use constants for Action Type. This demo sample just concentrates on different things.
Right, but that’s a bunch of boilerplate I don’t want to write.
I use Mobx so I can call actual functions on a class instead of passing strings/constants to giant switch statements.
Some of the negative comments after Sept. 17th didn’t pay attention to Ciprian’s post and fantastic example on CodeSandbox. This article massively over-complicated things due to a lack of understanding over how to use custom hooks with “stateful composition”.
Software development is evolving to functional programming (with composition in mind) for great reasons. It’s always hard to adjust because experienced programmers have a hard time with deviating from the familiar. This is just another paradigm shift, not unlike moving from sync-to-async, multi-threaded to single-threaded, CPS to promises, globals to modules, etc. But after adapting you’ll find a new comfort level and be glad you did.
I honestly have no idea how you can call this functional as Brian Pierce is saying, these functional components are about as impure as you can get and referential transparency is not maintained. This is a paradigm shift but it is a paradigm shift to something that exists nowhere else on the landscape and has a number of quirks and gotchas that trick a lot of people out.
modules made code way easier to understand and write…async/await made it way easier to read/write rest calls… none of these things are as confusing and unnecessary as hooks.
@Ciprian The code you posted seems to solve the very problem I posted about earlier today: https://stackoverflow.com/questions/58105791/best-practice-for-handling-consecutive-identical-usefetch-calls-with-react-hooks
It seems that pressing any one button repeatedly causes the [Fake] API Endpoint to be called repeatedly because your call of `setCurrentPage` is passing in an object, which is recreated each time. I’m hoping that is the key to unblock the fact that my `url` dependency isn’t allowing `useEffect` to be called with the same URL over & over.
Ugh, sometimes it’s crap like this that makes me want to leave software development all-together. I had started with Angular, tried Vue, (like it, but no one around here uses it & I need to pay the bills) settled on React. Tried Redux, *hated* it thoroughly, found MobX and all was good… And then Hooks came along. I use a mixture of class components and functional ones, but now more libraries and examples are going gang-busters for Hooks, so finding resources for integrating new libraries, or even MobX examples etc. are polluted with a feature I cannot implement consistently throughout my project. I mean from what I can take away from Hooks, what the hell is the point of props anymore?? props & local state in class components “made sense”, HOCs were an inconvenience but simple to understand… Now with hooks you’re expected to write stateless components that “access” state but need to dance around closures and once you embrace that silver hammer, who needs props?
A small problem with your solution:
Given: on page 1
When: I click button (1)
Then: Page re-fetches
I agree with you though, no need for complexity. In fact the solution is a combination of this and simply diffing only what is needed.
To fix your code sandbox, replace [config] with [config.skip, config.take]
We must think of reasons why they introduced hooks. One reason they said was “this” was confusing for beginners, come on, the number of gotchas that can be in hooks (my judgement is based on few days just reading about hooks) is nothing compared to “this”. Ok, they maybe looking nicer than HOCS, but was it worth it? I am not sure.
op here: I meant gotchas of hooks introduce more problems than ‘this’ issues
@Bob1234: I’ve felt your frustration! For two months I was completely confused by this new way of being software. Through trial & error, lots of reading, and lots of help from others online, things eventually made sense. I’m not at liberty right now to share my employer’s code but let me try to help regardless.
Working with React Hooks and Functional Components is all about “indirection”. More precisely, when you want to do something, rather than call it directly, instead you change a property value – in either the local state or in a connected Context – and then a useEffect Hook, which has this property as a dependency, is fired when the value changes. So, for example, say the user enters a value into an input element and presses Submit to check whether this value (ex. company name, e-mail address, user name, etc.) exists in the DB. Rather than making the async call from the Submit button’s event handler, instead you set a property value, which is monitored by a useEffect Hook, which in turn runs code within the useEffect construct. This code makes the call to the endpoint and waits for a response. When the response is returned, it populates the response data (often just a portion of it) into a different local state or Context property. Setting this second property then in turn often causes one or more React components (or functions therein) to be called. They use the new data to change some aspect of what the user sees on the page.
I appreciate the attempt to clarify, but being confused for 2 months is a big reason my interest in React Hooks has hit ZERO.
Indirection is also unappealing.
I think Hooks is a HUGE mistake, and for the most part will be avoiding using them.
Way too many respectable React developers have been pushing them hard. I feel like a lot of them are not involved with creating and maintaining large apps, and they’ve overlooked the value of being able to comprehend what code is doing. It’s a cool hack, but the downsides outweigh the upsides for me.
If Facebook wanted to do something completely different they should have made a new library.
I’m already running into a bunch of React NPMs that were popular 2 years ago, getting abandoned because they use deprecated lifecycle methods, and people are requesting they be rewritten in hooks. That’s basically just starting over. A bunch of the libraries are hard enough to update to not use the deprecated lifecycle methods. The React ecosystem is going to be a bit of dumpster fire for the next couple of years. 😞
FWIW, I’ve been using React everyday at work since 2014, and led the frontend development of Microsoft’s Visual Studio App Center; which is built with React and involved training dozens of new frontend developers. I’m so glad Hooks wasn’t around when we started building it.
Thanks for the reply @Robert Werner I kind of agree what you are saying but does this not lead to creating properties or worse flags that’s only reason for being is to trigger a useEffect update. I find the code much harder to read this way.
Paul, as I’ve mentioned to you before it’s the first message from Karen Grigoryan that got me thinking about this in a completely different way. Not saying that I know for sure that this approach is correct but I do know that doing things like calling async calls from an event handler is definitely the wrong thing to do.
I will say that I don’t think it’s a problem of not being comfortable with functional programming, at least I hope not. I tend to use the functional programming syntax in both Python and JavaScript a lot and love them, but I feel like Hooks code is a puzzle to decode (solvable but not obvious at first glance like lots of other functional programming code I’ve seen). Though for example Haskell code can be hard to understand at glance, so maybe it’s functional in that sense?
@PaulCowen FWIW there is a super-early / super-WIP set of React RFCs to help flush out non-FP ways of building hook-style abstractions:
https://github.com/reactjs/rfcs/pull/124
https://github.com/reactjs/rfcs/pull/125
Admittedly really early and I don’t think they’ve had any input / buy-in / champions from the React core team, but just mentioning fwiw.
I’d like to see how Facebook rebuild their Facebook platform using hooks and let them post saying that React Hook is their biggest mistake haha
One note from my side: `const Tag = `h${level}` as Taggable;` should be written as `const Tag = `h${level}` as React.ElementType;` otherwise TS is shouting about an error.
I am so glad I found this post, just when I began to think I’m the only person in the world with a serious dislike of hooks.
I share a lot of complaints that were shared in other comments, but let me add some more:
Based on my experience so far hooks are what I like to call “write only code” 🙂 ie. code that you write and it works great, you’re super proud of it, but nobody else can actually read it and reason about it. Neither do you after 1 year.
I may be old school and coming from OOP world, but I found class components very readable and easy to comprehend. I mean, passing an empty array as 2nd argument of useEffect to make it execute only once? How the hell is that clear?
After years of working on a few multi-year projects (4+) and onboarding many developers to them (including juniors) I learnt to appreciate verbosity. Magical shortcuts, one-liners and hidden features always add (unnecessary) overhead.
Do you have an idea of how to use one of these libs to fetch both on page load and using a button?
Also, I like how react-async-hook allows me to pass it a fetching function rather than a URL, since sometimes I want to:
(a) postprocess data coming from an API before passing it to my component for use, or
(b) use different parameters in my fetching function from those required by the API (e.g. the API requires formatting like query=”title:wildfires and author:joe” and my function accepts (title, author) rather than (query)
So, my ideal solution would be something that allows me to fetch on page load and via a button, and also allows me to use my own API functions for manipulating the result data and input parameters, not just passing in an URL, which seems restrictive to me. Thank you for this article.
@TinyUltimate: Here’s the successful approach I’ve used to do what you’re asking about:
// Define a component state variable
const [isFetching, setIsFetching] = useState(false);
// Define an event handler for your button
const handleSubmit = (event) => {
event.preventDefault();
setIsFetching(true);
}
// Define a useEffect to handle the async call
useEffect(() => {
if (isFetching) {
// Prepare whatever data you require here
request.get(`…API Endpoint URL…`)
.then(response => {
})
.catch(error => {
})
.finally(() ={
});
}
}, [isFetching]);
// In a separate file called requests.js, have a GET function
// Note: In your component file above, do this: import request from ‘…requests’;
export const get = async function(url: string, _options: any) {
const options = _options || await _default_options();
return axios.get(url, options);
};
I’d recommend everyone have a look at the followup artice to this “Solutions to frustrations with react hooks” https://blog.logrocket.com/solutions-to-frustrations-with-react-hooks/
Thanks Robert but I don’t see how this would fetch on page load since isFetching is initialized to false. Also I was hoping a package could obscure the try/catch boilerplate code for error handling.
@Dagda1, I skimmed your response post, and am not sure if it relates to my question. My react skills are not good enough to understand all the concepts there. I’m looking for a package that can let me do,
(1) fetch on page load
(2) fetch with a button click
(3) fetch via my own custom function (not just a URL)
(*) all in don’t-repeat-yourself fashion
I’m happy to hear hooks make sense to you now. It still hasn’t sunk in for me, and I think your original point, that they’re not intuitive and impure, still stands for those who don’t yet “get” hooks.
@TinyUltimate: I’ll just address your first paragraph. Every `useEffect` closure (aka construct) will run once upon startup. After that it will run again every time one of your dependencies changes. If you want it to run ONLY upon load then set the dependencies with an empty array like this:
useEffect(() => {
// Your code here
}, []);
I would STRONGLY recommend you watch some video tutorials on useEffect and React Hooks right away. They really helped me grasp the basic concepts. Some of my favourites are from: The Net Ninja, Ben Awad, Leigh Halliday, and Codevolution.
Alright thanks Robert, I will check these out!
PS what I meant in the above example is not that useEffect won’t run on page load, rather that the if statement within will evaluate to false on page load, and therefore won’t run on page load. Nonetheless I found another package that I like called react-query so I am going to try working with that. I appreciate your replies.
This is my way, hope it helps. It’s pretty simple
https://drive.google.com/file/d/1UZ7oqQmWg7cqGO0Q40edMVmPIoRXgPtM/view?usp=sharing
I am still not decided if React hooks are as broken as I think or I just don’t get the essence of them, but run into several troubles:
* they introduce completely new paradigm that no one is used to
* I understand why useEffect is using basic Object.is instead of some other more/less clever algorithm, but is it really good idea to put together Object.is (what is basically a reference comparison for non value types) and functional components where everything is fresh new on every render call? IMHO this two things just don’t fit well together
* reusability of hooks in case you want to apply them on multiple items instead of one eg. when you have hook for fetching data from server – ”
const useFetch = (url:string) => {….}
”
If you want to download data from 3 endpoints and do “[url1, url2, url3].each(url => useFetch(url))” React will say NO
but if you create component that actually only wraps your useFetch
”
function UseFetchWrapperComponent({url}) =>
[url1, url2, url3].map(url => )
” React will say yeah that’s OK I’m glad to do that
WTF?
It seems like there is something really broken in hooks design
I’ve recently been developing a React application. Hooks is very simple: it’s just another hack added onto a hack “language”… like extra marina on the spaghetti. But you get to crow that you’re a “full stack developer”, LOL.
THIS! i kept reading and i was like “why he just don’t set a new state with the new page?”
the component would re-render and the hook re-called with the new page
You know that this point is the one repeated the most: “One of the biggest complaints is that you often have to repeat logic in componentDidMount and componentDidUpdate.”
But at the same time that point is the stupidest one. It sounds like ‘For some reason react developers created 2 callbacks to react on properties changes instead of one and then when they realized how stupid that design is… instead of introducing the new one callback (exactly like useEffect) they decided to make things even more complicated’.