You have a project and need help?

Let's talk

Please enter a valid email address.
Please enter a valid phone number
hire-us

HOME / BLOG / 5 utility React Hooks for every project

5 utility React Hooks for every project

5 utility React Hooks for every project

Simple React Hooks for everyday projects to save you some time and make your code more readable.

React Hooks are here to stay. We had our time with them, learned how they work and how to use them. Most importantly, we learned that eslint-plugin-react-hooks is our best friend, especially react-hooks/exhaustive-deps rule :). From that, we can create Hooks that group common logic and reuse them on multiple projects, something like Lodash functions. With that, let's see 5 useful Hooks that saves you some time and make your code easier to read.

useInputState

Simple Hook to use with input tag. The primary purpose is an abstraction over input onChange property.

function useInputState(initialState = "") {
  const [state, setState] = useState(initialState);

  const setInputState = useCallback(event => setState(event.target.value), []);

  return [state, setInputState];
}

We are extracting value from the event argument and use it to set the next state. We are also using useCallback Hook with empty array (zero dependencies) to be sure that setInputState reference will never change and thus cause unneeded render.

How to use it:

() => {
  const [value, onChange] = useInputState("");

  return <input type="text" value={value} onChange={onChange} />;
}

useToggleState

This Hook is for those situations when you need to toggle between two values.

function useToggleState(
  initialState = false,
  [on, off] = [true, false]
) {
  const [state, setState] = useState(initialState);

  const toggleState = useCallback(() => {
    setState(s => (s === on ? off : on));
  }, [on, off]);

  return [state, toggleState, setState];
}

It has default values (true/false) for interaction with an input checkbox element. You can also use it to toggle between dark and light themes just by setting on value to 'dark' and off value to 'light'. The main part of useToggleState is toggleState function, which is nothing special, just a good old conditional (ternary) operator.

How to use it:

() => {
  const [theme, toggleTheme] = useToggleState("light", ["dark", "light"]);

  return (
    <div className={theme}>
      <button onClick={toggleTheme}>Toggle theme</button>
    </div>
  );
}

useDidUpdateEffect

If you need to skip the first render of functional component (the equivalent of componentDidMount method in class component), useDidUpdateEffect Hook is right for the job.

function useDidUpdateEffect(fn, inputs) {
  const fncRef = useRef();
  fncRef.current = fn;
  const didMountRef = useRef(false);

  useEffect(() => {
    if (!didMountRef.current) {
      didMountRef.current = true;
    } else {
      return fncRef.current();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, inputs);
}

We set didMountRef default value to false to indicate that the component is not mounted. After that, we use useEffect to change didMountRef or call the provided function. First time useEffect runs, it will set didMountRef to true indicating componentDidMount. Every other call to useEffect will call the provided function simulating componentDidUpdate. I wrote simulating because the functional component doesn't know the difference between mount and update. It knows only to render itself, and that is the reason we store the value of didMountRef in useRef Hook to keep data between renders.

useNotNilEffect

This one is like useEffect, with a twist. It runs a callback function only if all dependencies are different than null or undefined.

function useNotNilEffect(fn, dependencies = []) {
  const fnRef = useRef();
  fnRef.current = fn;

  useEffect(() => {
    const notNil = dependencies.every(
      item => item !== null && item !== undefined
    );
    if (notNil) {
      return fnRef.current();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, dependencies);
}

We don't want to depend on callback function to run effect because we can potentially cause the infinite loop. That is the reason we save function reference to useRef and on every render reassign the value of fnRef to the latest function reference. Inside useEffect we are checking if our dependencies will pass nil test and thus will it run callback or ignore it.
One note is that we can't rely on auto fix dependencies (react-hooks/exhaustive-deps rule) because ESLint can only statically determine our dependencies.

How to use it:

() => {
  const [state, toggleState] = useToggleState();

  useEffect(() => {
    //run on mount and state change
  }, [state]);

  useDidUpdateEffect(() => {
    //will skip mount but will run on state change
  }, [state]);

  return <button onClick={toggleState}>Toggle</button>;
}

useTableSort

Almost every React project will have tables. Those tables will probably have sort capabilities. We'll want to group common sort logic to make our job easier.

function useTableSort(initialKey, initialDirection) {
  const [sortKey, setSortKey] = useState(initialKey);
  const [
    sortDirection,
    toggleSortDirection,
    setSortDirection
  ] = useToggleState(initialDirection, [
    SORT_DIRECTION.ASC,
    SORT_DIRECTION.DESC
  ]);

  const setSort = useCallback(
    key => {
      if (key !== sortKey) {
        setSortKey(key);
        setSortDirection(initialDirection);
      } else {
        toggleSortDirection();
      }
    },
    [sortKey, setSortKey, toggleSortDirection, setSortDirection]
  );

  return { key: sortKey, direction: sortDirection, set: setSort };
}

Sort logic will have key (table column), and direction (ASC/DESC) by which we sort table rows. useTableSort returns key, direction, and sort. sort method depends on a key argument. If the key is the same as the last one, it will toggle direction otherwise will change key and set the direction to initialDirection.

How to use it:

({ data = [] }) => {
  const { key, direction, set } = useTableSort("name", SORT_DIRECTION.ASC);

  const list = useMemo(() => {
    /*sort data*/
  }, [key, direction, data]);

  return (
    <table>
      <thead>
        <tr>
          <th onClick={() => {set("name")}}>Name</th>
          <th onClick={() => {set("email")}}>Email</th>
        </tr>
      </thead>
      {/* list  */}
    </table>
  );
}

Conclusion

Code reusability is always important, and React Hooks are not the exception to that rule. 5 Hooks is step in that direction. I'm sure as time goes on, I'll find more of them and create part 2 of this blog post.

YOU MIGHT ALSO LIKE

You have a project and need help?

Let's talk

Please enter a valid email address.
Please enter a valid phone number
hire-us