I wanted to learn more about how React Hooks work under the hook, so I dove in the source code. In this blog we'll demistify all hooks and learn a bit about typed Javascript and React. Let's go!
useState
Let's start with React's most used hook
const [count, setCount] = useState(0);
The first thing we notice is the "[var, setVar]" syntax. This is actually Javascript syntax that allows you to spread an array returned from a function. Check example below.
function fn() {
let var1 = 'Hello', var2 = 'World', var3 = '!';
return [var1, var2, var3];
}
const [var1, var2, var3] = fn();
console.log(var1); // Outputs: Hello
console.log(var2); // Outputs: World
console.log(var3); // Outputs: !
Anyway, let's look at the useState source code.
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
That's the whole code for the useState function! It looks like Typescript, but actually Flow is used for types, originally developed at Facebook.
Looking again at how state is initialized
const [count, setCount] = useState(0);
We see that we pass a generic value "0" which could be a number, string or even a function that returns a value. How do I know this? It's in the source!
function useState<S>(
initialState: (() => S) | S,
)
Here we get that a generic value is passed (research Generics if you don't know this concept) and it can be the generic value itself or a function.
Let's look further.
[S, Dispatch<BasicStateAction<S>>]
We also get that the return type is an array with 2 values, S (generic vlaue) and a Dispatch function that helps use update that state. This is what we broke down initially, remember?
We won't go into what a dispatcher is, yet. It's a very important concept but I'm already making this too long.
Basically it "dispatches" a change of state through a "reducer".
useEffect
export function useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
That is the whole code for useEffect. Yes, I know! It's short.
We can see almost the same format as in useState. But this time we take a function and an array as arguments. The function itself returns either another function or void.
If you look at an example of useEffect:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
}, []);
We precisely see that useEffect() takes first a function as argument, and then an optional array.
In typescript you'd see the use of "?:" for optional types but here we see this which works for Flow:
deps: Array<mixed> | void | null,
We also see the use of dispatchers again and a dispatcher being called with "dispatcher.useEffect". Let me know if you want me to go more in depth on the dispatchers in a later blog.
More hooks
Let's see more hook examples
export function useContext<T>(Context: ReactContext<T>): T {
const dispatcher = resolveDispatcher();
return dispatcher.useContext(Context);
}
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}
export function useCallback<T>(
callback: T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback, deps);
}
Now we notice that we can no longer avoid this anymore, we must know what a dispatcher is!
If you're interested, we'll continue in part 2.
Top comments (0)