DEV Community

Cover image for Little React Things: Cleaning up dependencies
Zeke Hernandez
Zeke Hernandez

Posted on • Originally published at zekehernandez.com

Little React Things: Cleaning up dependencies

Little React Things is a series of short, React-focused posts where I share some things I've learned over the past few years of developing with React. I hope you find them helpful. These posts might be most helpful for those who have at least a little experience with React already.

Let's kick this short one off with a little thing you may or may not know, but you definitely should know. The setState function identity does not change on rerenders. What this means is that you can (and should) omit setStates from the dependency lists of useEffect and useCallback. This behavior is noted in the official React docs.

On to the main topic. For this post I'm gonna roll with the tried and true Todo List example. We have a list of TodoItems and let's say we can have a bunch of them. Since there can be so many of them we wrap each TodoItem with memo so that each one only rerenders when its own props change.

Furthermore, each todo item can be cloned, so each TodoItem has a callback, onClone. And we have a little helper function cloneTodo that makes such a clone of an existing todo item. The cloned todo is appended to the todos state in handleClone (if you want to know why I wrapped handleClone with useCallback take a look at Your Guide to React.useCallback()).

Here's our React component:

function TodoList() {
    const [todos, setTodos] = useState([])

    const handleClone = useCallback((todo) => {
        setTodos([...todos, cloneTodo(todo)])
    }, [todos])

    return (
        <div>
            {todos.map(todo => (
                <TodoItem key={todo.id} todo={todo} onClone={handleClone} />
            ))}
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Pretty reasonable right? Ship it!

...a few minutes later...

Uh oh, users have started reporting that their todo lists are really slow, particularly when cloning todo items. What happened? We wrapped our TodoItems with memo, so why are they all rendering? Well, one of the props of TodoItem is onClone and we used useCallback for that. And... oh, one of the dependencies of handleClone is todos. And that todos state changes everytime we clone a todo (because we add a new todo to the array). So cloning a todo rerenders all the todos. Not great.

Let's make a small change to handleClone:

const handleClone = useCallback((todo) => {
    setTodos(currentTodos => [...currentTodos, cloneTodo(todo)])
}, [])
Enter fullscreen mode Exit fullscreen mode

As I mentioned at the beginning, setTodos does not need to be added to the dependency list. So now this new version of handleClone doesn't have any dependencies and its identity remains stable throughout the lifetime of the component! Now whenever we clone a todo, that new todo is added to the list without rerendering the already existing ones.

If you want to see these examples live, check them out here:

Top comments (0)