HiveBrain v1.2.0
Get Started
← Back to all entries
snippetjavascriptTip

Advanced React state hooks

Submitted by: @import:30-seconds-of-code··
0
Viewed 0 times
hooksstateadvancedreact

Problem

React's toolbox is intentionally quite limited, providing you some very versatile building blocks to create your own abstractions. But, if you find useState() too limited for your needs, and useReducer() doesn't quite cut it either, how do you go about creating more advanced state management hooks? Let's deep dive into some advanced state management hooks.
Starting off with a simple one, the useToggler hook provides a boolean state variable that can be toggled between its two states. Instead of managing the state manually, you can simply call the toggleValue function to toggle the state.
The implementation is rather simple, as well. You use the useState() hook to create the value state variable and its setter. Then, you create a function that toggles the value of the state variable and memoize it, using the useCallback() hook. Finally, you return the value state variable and the memoized function.
The Map object is a very versatile data structure in JavaScript, but it's not directly supported by React's state management hooks. The useMap hook creates a stateful Map object and a set of functions to manipulate it.
Using the useState() hook and the Map() constructor, you create a new Map from the initialValue. Then, you use the useMemo() hook to create a set of non-mutating actions that manipulate the state variable, using the state setter to create a new Map every time. Finally, you return the map state variable and the created actions.

Solution

const useToggler = initialState => {
  const [value, setValue] = React.useState(initialState);
  const toggleValue = React.useCallback(() => setValue(prev => !prev), []);

  return [value, toggleValue];
};

const Switch = () => {
  const [val, toggleVal] = useToggler(false);
  return <button onClick={toggleVal}>{val ? 'ON' : 'OFF'}</button>;
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <Switch />
);


The implementation is rather simple, as well. You use the useState() hook to create the value state variable and its setter. Then, you create a function that toggles the value of the state variable and memoize it, using the useCallback() hook. Finally, you return the value state variable and the memoized function.
The Map object is a very versatile data structure in JavaScript, but it's not directly supported by React's state management hooks. The useMap hook creates a stateful Map object and a set of functions to manipulate it.
Using the useState() hook and the Map() constructor, you create a new Map from the initialValue. Then, you use the useMemo() hook to create a set of non-mutating actions that manipulate the state variable, using the state setter to create a new Map every time. Finally, you return the map state variable and the created actions.
Similar to useMap, the useSet hook creates a stateful Set object and a set of functions to manipulate it. The implementation is very similar to the previous hook, but instead of using a Map, you use a Set.
Similar to the previous hook, we might also need a hook that provides a default value if the state is null or undefined. The useDefault hook does exactly that. It creates a stateful value with a default fallback if it's null or undefined, and a function to update it.
The approach is very similar to the previous hook. You use the useState() hook to create the stateful value. Then, you check if the value is either null or undefined. If it is, you return the defaultState, otherwise you return the actual value state, alongside the setValue function.

Code Snippets

const useToggler = initialState => {
  const [value, setValue] = React.useState(initialState);
  const toggleValue = React.useCallback(() => setValue(prev => !prev), []);

  return [value, toggleValue];
};

const Switch = () => {
  const [val, toggleVal] = useToggler(false);
  return <button onClick={toggleVal}>{val ? 'ON' : 'OFF'}</button>;
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <Switch />
);
const useMap = initialValue => {
  const [map, setMap] = React.useState(new Map(initialValue));

  const actions = React.useMemo(
    () => ({
      set: (key, value) =>
        setMap(prevMap => {
          const nextMap = new Map(prevMap);
          nextMap.set(key, value);
          return nextMap;
        }),
      remove: (key, value) =>
        setMap(prevMap => {
          const nextMap = new Map(prevMap);
          nextMap.delete(key, value);
          return nextMap;
        }),
      clear: () => setMap(new Map()),
    }),
    [setMap]
  );

  return [map, actions];
};

const MyApp = () => {
  const [map, { set, remove, clear }] = useMap([['apples', 10]]);

  return (
    <div>
      <button onClick={() => set(Date.now(), new Date().toJSON())}>Add</button>
      <button onClick={() => clear()}>Reset</button>
      <button onClick={() => remove('apples')} disabled={!map.has('apples')}>
        Remove apples
      </button>
      <pre>
        {JSON.stringify(
          [...map.entries()].reduce(
            (acc, [key, value]) => ({ ...acc, [key]: value }),
            {}
          ),
          null,
          2
        )}
      </pre>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <MyApp />
);
const useSet = initialValue => {
  const [set, setSet] = React.useState(new Set(initialValue));

  const actions = React.useMemo(
    () => ({
      add: item => setSet(prevSet => new Set([...prevSet, item])),
      remove: item =>
        setSet(prevSet => new Set([...prevSet].filter(i => i !== item))),
      clear: () => setSet(new Set()),
    }),
    [setSet]
  );

  return [set, actions];
};

const MyApp = () => {
  const [set, { add, remove, clear }] = useSet(new Set(['apples']));

  return (
    <div>
      <button onClick={() => add(String(Date.now()))}>Add</button>
      <button onClick={() => clear()}>Reset</button>
      <button onClick={() => remove('apples')} disabled={!set.has('apples')}>
        Remove apples
      </button>
      <pre>{JSON.stringify([...set], null, 2)}</pre>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <MyApp />
);

Context

From 30-seconds-of-code: advanced-react-state-hooks

Revisions (0)

No revisions yet.