DEV Community

Kenneth Lum
Kenneth Lum

Posted on • Edited on

Having fun with React Custom Hooks

It can be quite fun with Custom Hooks in React.

Let's say we just think of, I want a company that can give me a countdown from 3 to 0, and then give me a way to reset the count.

So we could just write this:

export default function App() {
  const [count, reset] = useXYZCompany();

  return (
    <div className="App">
      <h1>{ count }</h1>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

That's simple enough. It does not do anything imperative. It follows the line that in React, a lot of things are just declarative... all the way down to when we need to have something imperative to make it work.

So let's say this XYZCompany uses an iPhone to do the job:

function useXYZCompany() {
  const [count, reset] = useIPhone();
  return [count, reset];
}
Enter fullscreen mode Exit fullscreen mode

For simplicity, we just make each level return the same count and reset function. We could change it so that the XYZCompany provides some extra functions instead of just a countdown number.

Likewise, the iPhone uses an iPhoneApp:

function useIPhone() {
  const [count, reset] = useIPhoneApp();
  return [count, reset];
}
Enter fullscreen mode Exit fullscreen mode

The iPhoneApp does the imperative thing. It uses useEffect to run something:

function useIPhoneApp() {
  const [count, setCount] = useState(3);

  useEffect(() => {
    let intervalID;

    if (count > 0) {
      intervalID = setInterval(() => {
        setCount(count - 1);
      }, 1000);
    }

    return () => intervalID && clearInterval(intervalID);
  });

  function resetFn() {
    setCount(3);
  }

  return [count, resetFn];
}
Enter fullscreen mode Exit fullscreen mode

which is to simply decrement the count. Note that this useEffect runs every time, and I notice this is the common style that React code is written: it just "do" and "undo", so that we don't have to worry about anything, such as the count being the same from the closure. Each time, it just "undo" the previous task, and "do" the new task (of setting up the timer). It is like mathematical induction: if we know this step is correct, then undoing it and redoing it at a different state is also correct, and therefore, everything is correct.

So we can see the code running at: https://codesandbox.io/s/gallant-cloud-177mn?file=/src/App.js

When we press the Reset button, it is to tell the XYZCompany to do a reset. And then XYZCompany uses the iPhone and tells the iPhone to reset. The iPhone in turns tells the iPhoneApp to do a reset.

We don't have to go that many levels down. We can directly use useIPhoneApp() in the main component, but it is just to show how it would still work after many levels down.

The setState() is written so that when it updates any value, any user, all the way to the top, will be re-rendered (re-invoked). So App would call useXYZCompany, and then call useIPhone, and then call useIPhoneApp.

So that's the methodology: we just get back some value from our custom hook. It looks static, but don't worry about it. As long as somewhere down the line, if it has a setState(), then it will "magically" get down to you, appearing to be "changing the static value", as in the case of count.

A random text shifter

We can also make a random text shifter, so that it will randomly shift some text. The custom hook is called useShifter(). The code:

function useShifter() {
  const [shift, setShift] = useState(0);

  useEffect(() => {
    const intervalID = setInterval(() => {
      setShift((shift) => {
        if (shift < 0) return -shift;
        else if (shift > 0) return 0;
        else if (Math.random() < 0.1) return -Math.random() / 9;
      });
    }, 33);

    return () => intervalID && clearInterval(intervalID);
  }, []);

  return { position: "relative", left: `${shift}em`, top: `${shift / 3}em` };
}

export default function App() {
  const shifter = useShifter();

  return (
    <div className="App">
      <h1 className="message" style={shifter}>
        Hello
      </h1>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Demo at: https://codesandbox.io/s/optimistic-hamilton-1u9dv

This is another custom hook for a morpher shifter: https://codesandbox.io/s/epic-forest-kqt1d?file=/src/App.js

Top comments (0)