React Hooks - How to use a make a state dependant on another state?

Viewed 4262

I recently started using hooks in react and I am often having this problem: I create a first big state which is used by all my component, but some of the smaller parts of my component divide this state and create their own state for simplicity.

For exemple

import React, { useState } from "react";

const initialFilters = {
  name: "",
  code: ""
};

function Filter({ value, setFilters }) {
  const [tempValue, setTempValue] = useState(value);

  return (
    <input
      value={tempValue}
      onChange={e => setTempValue(e.target.value)}
      onBlur={() => setFilters(tempValue)}
    />
  );
}

function App() {
  const [filters, setFilters] = useState(initialFilters);
  const agents = [
    { name: "bob", code: "123" },
    { name: "burger", code: "3123" },
    { name: "sponge", code: "34" }
  ];

  return (
    <div>
      <label>Name filter</label>
      <Filter
        value={filters.name}
        setFilters={value =>
          setFilters(filters => ({ ...filters, name: value }))
        }
      />
      <label>Code filter</label>
      <Filter
        value={filters.code}
        setFilters={value =>
          setFilters(filters => ({ ...filters, code: value }))
        }
      />
      <button onClick={() => setFilters(initialFilters)}>Reset filters</button>
      <ul>
        {agents
          .filter(
            agent =>
              agent.name.includes(filters.name) &&
              agent.code.includes(filters.code)
          )
          .map((agent, i) => (
            <li key={i}>
              name: {agent.name} - code: {agent.code}
            </li>
          ))}
      </ul>
    </div>
  );
}

export default App;

CodeSandox available here

In this exemple the filters work fine, but their value are not emptied when we use the button Reset.

The filters create their own states to dispatch new state only on blur, and still be controlled. I guess I could use ref here, but I use this exemple to showcase a simple case of state dependant on another state (and therefore on props).

How should I go about implementing this in an idiomatic React way?

3 Answers

You can use a useEffect hook. The 1st argument is a function and the 2nd argument is an array of dependencies. When a dependency changes value, the function is executed again.

import { useEffect } from 'react';

// ....code removed....

  useEffect(() => {
    setTempValue(value);
  }, [value]);

// ....code removed....

Sandbox with changes: https://codesandbox.io/s/kind-bogdan-ljugv

As you can read in the documentation (https://reactjs.org/docs/hooks-state.html#declaring-a-state-variable), your state is created on the first render only and only then it is equal to your initial value. You could write a custom hook useFilter and expose your filter-reseter:

const useFilter = (value, setFilters) => {
  const [tempValue, setTempValue] = useState(value);
  const resetFilter = () => setTempValue(value)

  return {
      resetFilter,
      getInputProps: () => ({
          onChange: e => setTempValue(e.target.value),
          onBlur: () => setFilters(tempValue),
          value: tempValue,
      })
  }

and instead of doing:

      <Filter
        value={filters.name}
        setFilters={value =>
          setFilters(filters => ({ ...filters, name: value }))
        }
      />

do this:

const setFilters = value => setFilters(filters => ({ ...filters, name: value }))
const { resetTempFilter, getInputProps } = useFilter(value, setFilters)
...
<input {...getInputProps()} />

In this case you much easier to re-instantiate children with changing key prop(so named "reset state" technique based on how reconciliation works in React):

const [resetKey, setResetKey] = useState(0);
const doReset = setResetKey(key => key + 1);

      <Filter
         key={`name-filter-${resetKey}`}
         ...
      />
      <Filter
         key={`code-filter-${resetKey}`}
         ...
      />
      <button onClick={doReset}>Reset!</button>

It's not only easier to achieve. It also will work the same for any stateful components you cannot modify(for any reason).

Related