Another week, another custom React hook for your hooks backpack. In this episode, we'll implement the useArray hook to make arrays management easier. Ready? Let's go! π
Motivation
As usual, let's first discover how this hook could be useful to you. Let's be original and creative: suppose you are building a To-Do list application with React. At some point, you'll have to manage the user's tasks: to do so, you'll use an array, along with the useState hook. The addTask function might look like this:
const addTask = (newTask) => {
  setTasks(oldTasks => [...oldTasks, newTasks])
}
Then, you'd have a removeTask function, that could look like this:
const removeTask = (index) => {
  setTasks(oldTasks => oldTasks.filter((_, i) => i !== index))
}
As you can see, this can quickly become a bit hard to read.
This is why we will create the useArray hook to simplify our code.
Implementation
First, let's create the hook's base structure.
const useArray = (initialValue = []) => {
  const [value, setValue] = useState(initialValue)
  return { value, setValue }
}
Then we'll add the push function to add an element at the end of the array.
const push = element => {
  setValue(oldValue => [...oldValue, element]);
};
Let's also create the remove function to remove an element at a given index.
const remove = index => {
  setValue(oldValue => oldValue.filter((_, i) => i !== index));
};
It can also be handy to add an isEmpty function to check for the array emptiness.
  const isEmpty = () => value.length === 0;
Combining all these functions together, here's how the final hook will look like:
const useArray = (initialValue = []) => {
  const [value, setValue] = useState(initialValue);
  const push = element => {
    setValue(oldValue => [...oldValue, element]);
  };
  const remove = index => {
    setValue(oldValue => oldValue.filter((_, i) => i !== index));
  };
  const isEmpty = () => value.length === 0;
  return { value, setValue, push, remove, isEmpty };
};
If you are working with large amount of data, feel free to optimize this hook by using useCallback (more info here).
Example:
const push = useCallback(element => { setValue(oldValue => [...oldValue, element]) }, [])
Also, if you need other array methods such as map or unshift, don't  hesitate to adapt it to your needs (you can even add custom functions).
Usage
Back to our To-Do list example. By using our brand new hook, this is how the component could now look like:
const TodoList = () => {
  const tasks = useArray([]);
  const [newTask, setNewTask] = useState("");
  // "Add" button clicked
  const handleSubmit = e => {
    e.preventDefault();
    tasks.push(newTask);
    setNewTask("");
  };
  const handleInputChange = e => setNewTask(e.target.value);
  return (
    <>
      <h1>Todo List</h1>
      <form onSubmit={handleSubmit}>
        <input type="text" value={newTask} onChange={handleInputChange} />
        <button>Add</button>
      </form>
      {tasks.isEmpty() ? (
        <p>No tasks to display</p>
      ) : (
        <ul>
          {tasks.value.map((task, index) => (
            <li key={index}>
              <input
                type="checkbox"
                onClick={() => tasks.remove(index)}
                checked={false}
              />
              {task}
            </li>
          ))}
        </ul>
      )}
    </>
  );
};
Notice that we don't even need the addTask and removeTask functions anymore, as our tasks.push and tasks.remove ones are already explicit and easy to read.
Improvement Ideas
To go further, here are some ideas of improvements to enhance this hook.
- Adding a 
reversefunction to reverse the array - Adding a 
sortfunction to sort the array - Adding a 
clearfunction to clear the array 
Conclusion
I hope this hook will be useful to you for your future (or existing) projects. If you have any questions, feel free to ask them in the comments section.
Thanks for reading me, and see you next time for a new custom hook. π€
Source code available on CodeSandbox.
Support Me
If you wish to support me, you can buy me a coffee with the following link (I will then probably turn that coffee into a new custom hook... β)
              
    
Top comments (11)
Nice simplification for the general create/delete -- but what about a simple update?
In a larger context I often see mistakes when in a cooersive/mutable case like react+js. For some people adding splice/slice into a case useArray might not come natural.
What about this? (in O(n), but in all cases, I guess we have no other choice, since making a copy of the original array to make the replacement in O(1) would already be in O(n) too)
Immer (and its useImmer hook) can be a lifesaver when dealing with arrays, especially nested ones. It lets you write much simpler update logic while managing all the immutability for you.
I made a similar todo example here using it (although a normal useState realistically would have sufficed): codesandbox.io/s/magical-ride-9l8d...
beta.reactjs.org/learn/updating-ob...
Interesting, thanks for sharing. But this looks like the default
useStateway to deal with arrays, doesn't it? (With just your example, I don't really understand the benefits of using it.)I agree it's not a very good example for showing the benefits of Immer, but basically it just allows you to write "mutating" syntax while not actually mutating the data. Try recreating my
toggleCompletedfunction with useState and you'll see the difference.Take this slightly more complex data structure:
If you wanted to update the quantity of a stock item with
useStateyou would maybe do something like this:With
useImmeryou can instead do this:Might not be a big deal, but for me the latter takes less mental effort.
codesandbox.io/s/heuristic-wiles-p...
Okay now I see the benefits, and I totally agree with you! Never had to deal with such complex examples though, but it's great to have this library in mind. π
But I can see this saving time again and again just due to its simplified mental model.
Writing nested spread after nested spread gets a little complicated after a while. Now I'm so far down the pattern that when I see syntax looking like it's mutating a variable out of scope I balk a little. All the same, it would be a nice tool.
Yeah, it definitely feels dirty writing it that way at first but it's a small price to pay for getting away from some nested spread abominations.
Superbe, merci beaucoup, pote!
Avec plaisir ! You're the most welcome.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.