An enhancement, not a replacement
Freedom of choice
Out of the box
Getting started
Section titled “Getting started”You can use your favorite package manager:
npm install react-nook
Then import:
import { ... } from 'react-nook';
Want to jump straight into action? There are various bite-sized demos that showcase specific scenarios where conditional hooks are helpful, and how to use them. If you want a more detailed walkthrough of the features and mechanisms, then keep reading!
The problem with React Hooks
Section titled “The problem with React Hooks”React = Composable UI, and with the introduction of hooks, that composability soared to the next level! The promise of reusable behavior across components is not all sunshine and rainbows though. To achieve their goal, the React team had to impose certain limitations on hook use, known as the “Rules of Hooks”. This mostly boils down to:
- Only call Hooks at the top level (outside of control-flow)
- Only call Hooks from React functions (components or other hooks)
The latter is reasonable and I don’t think anyone is arguing about it, whereas the former can be a pain when working in bigger projects. When writing custom hooks, we rarely plan around someone wanting to skip the hook’s execution based on a condition.
/** * @returns seconds passed since mount */export function useSeconds() { const [value, setValue] = useState(0);
useEffect(() => { const handle = setInterval(() => setValue((prev) => prev + 1), 1000); return () => clearInterval(handle); });
return value;}
function Widget() { const seconds = useSeconds();}
Now say we want to count up only when the widget is “active”. It’s easy enough for this simple use case, but for more complex hooks, a refactor like this can be quite involved.
export function useSeconds(active: boolean) { const [value, setValue] = useState(0);
useEffect(() => { if (!active) return;
const handle = setInterval(() => setValue((prev) => prev + 1), 1000); return () => clearInterval(handle); }, [active]);
return value;}
function Widget(props) { const seconds = useSeconds(props.active);}
What if we want to reset the count every time it becomes active again? Well then we’re out of luck, we have to split the components and mount based on this condition. What if we could just use hooks conditionally?
An enhancement, not a replacement
Section titled “An enhancement, not a replacement”Before I explain what ✳︎nooks are, I want to emphasize that they’re not a replacement for ⚛︎hooks, they make existing hooks better. Don’t worry about having to rewrite your existing logic.
The library also does not replace the React runtime in any way, nor does it require a build-step to work. React Nook uses a niche property of tagged template literals, and is described in more details here.
Nooks love you conditionally
Section titled “Nooks love you conditionally”✳︎Nooks are places in your codebase where “Rules of Hooks” can finally lay back and relax…
Let’s go over the relationship between ⚛︎hooks and ✳︎nooks
- ⚛︎hooks CANNOT use other ⚛︎hooks conditionally (the whole reason we’re here)
- ⚛︎hooks can use ✳︎nooks conditionally:
useNook(...)
- ✳︎nooks can use other ✳︎nooks conditionally:
mountFoo``(...)
- ✳︎nooks can use ⚛︎hooks conditionally:
nook(useFoo)
Creating nooks
Section titled “Creating nooks”The easiest way to create a ✳︎nook is to wrap an existing ⚛︎hook.
// A regular React hookfunction useToggle(initial: boolean) { const [value, setValue] = useState(initial); const toggle = useCallback(() => setValue((prev) => !prev), []);
return [value, toggle] as const;}
// A nook with the same behaviorconst mountToggle = nook(useToggle);
If you’re just now beginning to write these ⚛︎hooks, you may want to write them as ✳︎nooks to begin with!
const mountToggle = nook((initial: boolean) => { const [value, setValue] = useState(initial); const toggle = useCallback(() => setValue((prev) => !prev), []);
return [value, toggle] as const;});
The convention is to name nooks starting with mount
instead of use
, just to visually differentiate ✳︎nooks from ⚛︎hooks.
Using nooks
Section titled “Using nooks”The ✳︎nooks API can seem strange as first, but you get used to it. To use mountFoo
we append an empty template to it, then call the result:
mountFoo``(first_arg, second_arg, ...);
Compared to the ⚛︎hooks version:
useFoo(first_arg, second_arg, ...);
This small change allows the library to track based on which expression was called, instead of in which order. You can learn more about the technical details below
Inside components or hooks
Section titled “Inside components or hooks”To use nooks inside regular components or hooks, reach for useNook
.
function Component(props) { // ... const [value, toggle] = useNook(() => { // Aahh... all cozy and nice if (props.active) { return mountToggle``(); } // If not active, return a fallback and a noop return ['Inactive', () => {}]; }); // ...}
Inside other nooks
Section titled “Inside other nooks”No additional wrapper is necessary in this case:
const mountTimer = nook(() => { const [value, setValue] = useState(0); // ... return value;});
const mountTimerWithToggle = nook((initialActive: boolean) => { const [active, setActive] = useState(initialActive); // Resets each time we toggle active, because the timer gets unmounted const time = active ? mountTimer``() : 0;
const toggle = useCallback(() => setActive((prev) => !prev), []);
return [time, toggle];});
There’s a variety of bite-sized demos to check out!
How does it work?
Section titled “How does it work?”(explanation-in-progress)