React Hook “Gotcha’s”

A quick overview of where I’ve gotten hung up while using React Hooks.

Paul Sherman
Nov 4, 2018 · 3 min read

I’ve been playing around with React Hooks a little bit (both with Curi and React Router). The experience has mostly been smooth, but there are a few places where I have gotten hung up:

General Note: React Hooks are experimental, only released in alpha, and are subject to changing. This article (originally published Nov. 4, 2018) may quickly become outdated.

Testing Effects

Problem:

Effects are functions that are run after React finishes rendering.

function MyComponent() {
React.useEffect(() => {
console.log("rendered MyComponent");
});
return <div>My Component</div>;
}

Unfortunately, testing them doesn’t appear to be straightforward. The effect has a low priority and will not be called until after a render has been flushed.

“Solution”:

The useLayoutEffect hook is roughly equivalent to a React class’s componentDidMount and componentDidUpdate functions. Substituting useEffect with useLayoutEffect gets the tests to pass.

function MyComponent() {
React.useLayoutEffect(() => {
console.log("rendered MyComponent");
});
return <div>My Component</div>;
}

This is a “solution”, not a solution, since useEffect is probably a better choice for many implementations. Once testing useEffect is easier, I will hopefully be able to switch back to it.

Functions as State

Problem:

I had a component with a bit of state. It sets up an observer that gets passed a function, which I want to store in state. Unfortunately, every time I tried storing the function, it would be called immediately.

function MyComponent() {
const [fn, setFn] = React.useState();
React.useLayoutEffect(() => {
return observe(newFn => {
// setting the new function will call the function
setFn(newFn);
});
});
return fn;
}

Solution:

If you want to store a function as state, you need to pass a function that returns the function to store (called “functional updates”). This was mostly an issue for me because I forgot that the “set” function returned by useState can be given a function.

function MyComponent() {
const [fn, setFn] = React.useState();
React.useLayoutEffect(() => {
return observe(newFn => {
// pass a function that returns the new function
setFn(() => newFn);
});
});
return fn;
}

This one was really my fault and I’m sure that the final documentation will make it more obvious how to store functions in state.

Setting State in Unmounted Components

Problem:

If you attempt to set a component’s state after it has unmounted, you will get an error message. This problem can crop up in asynchronous event handlers, which do not have a great way of knowing whether or not the component is still mounted.

function MyComponent() {
const [valid, setValid] = React.useState(true);
function validate(event) {
validateEvent(event)
.then(result => {
setValid(result);
});
}
return <Thing valid={valid} onChange={validate} />;
}

With a class component, you can use componentWillUnmount to set an instance variable and “guard” setState calls.

class Guarded extends React.Component {
state = { valid: true}
validate = event {
validateEvent(event)
.then(result => {
if (!this.removed) {
this.setState({ value: result });
}
});
}
render() {
return (
<Thing valid={this.state.valid} onChange={this.validate} />
);
}
componentWillUnmount() {
this.removed = true;
}
}

Solution:

Edit (Nov. 6): Instead of using state to store if a component is mounted, this should be done with a ref. Refs are closer to instance variables and don’t cause a re-render.

You can a ref and an effect to track if a component is mounted. An effect whose second argument is an empty array ([]) will only call its return method when the component unmounts. We can use this to set an “unmounted” state.

function MyComponent() {
const [valid, setValid] = React.useState(true);
const mounted = React.useState(true);
React.useEffect(() => {
return () => {
mounted.current = false;
}
}, []
function validate(event) {
validateEvent(event)
.then(result => {
if (mounted.current) {
setValid(result);
}
});
}
return <Thing valid={valid} onChange={validate} />;
}

Paul Sherman

Written by

Blog moved to https://blog.pshrmn.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade