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>
);
}
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];
}
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];
}
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];
}
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>
);
}
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)