Writing useEffect from scratch
Joaquin-Niembro
Posted on December 19, 2023
Polyfills are a good way to understand hooks better, even though they can seem challenging we should approach them as writing any custom hook, as the logic for writing any custom hook is the same for writing the hooks polypills for the most part!.
We can start of with what the useEffect hook is, we can break it down to a function that receives 2 things, an Effect which is nothing more that a function and a dependency array (or not in case we want it to execute on every render.
function useCustomUseEffect(fn, deps) {}
Then we can define how many functionalities and edge cases this function needs to be able to handle.
- Execute the function on the first render.
- Find a way to know it is the first render.
- Cache the dependencies in order to compare after they have changed.
- Execute the effect again in case the dependencies have changed.
- Manage the cleanUp function and execute it after the effect.
Using refs for validating first render
function useCustomUseEffect(fn, deps) {
const firstTimeRef = useRef(true);
if (firstTimeRef.current) {
// execute code first time and then set it to false as it will revalidate each render
firstTimeRef.current = false;
}
}
Now we can go ahead and execute the effect on the first render, this way.
function useCustomUseEffect(fn, deps) {
const firstTimeRef = useRef(true);
if (firstTimeRef.current) {
firstTimeRef.current = false;
fn();
}
}
Now we need to determine a way to verify the dependecies and if the dependencies change through renders, this makes a special case for refs once again as they persist through renders.
Using refs to cache the dependency array
function useCustomUseEffect(fn, deps) {
const firstTimeRef = useRef(true);
const cacheDepsRef = useRef([]);
if (firstTimeRef.current) {
firstTimeRef.current = false;
fn();
}
cacheDepsRef.current = deps ? deps : [];
}
And now we can compare the deps that come from the argument with the cacheDepsRef.current!
we can now move on to re-executing the effect every time the dependencies change, in order to do that we can compare the reps as mentioned above and trigger the effect.
Execute effect when dependencies change
function useCustomUseEffect(fn, deps) {
const firstTimeRef = useRef(true);
const cacheDepsRef = useRef([]);
if (firstTimeRef.current) {
firstTimeRef.current = false;
fn();
}
// comparing previous deps with current deps
if (JSON.stringify(deps) === JSON.stringify(cacheDepsRef.current)) {
fn();
}
cacheDepsRef.current = deps ? deps : [];
}
we have only 2 more things left to do for our useEffect custom implementation.
We are missing the case when the deps array does not exist and the clean up function.
No dependencies are provided
For the case where we don't receive a dependency array we can simple add another validation to let it execute each render and we should be just fine.
function useCustomUseEffect(fn, deps) {
const firstTimeRef = useRef(true);
const cacheDepsRef = useRef([]);
if (firstTimeRef.current) {
firstTimeRef.current = false;
fn();
}
if (JSON.stringify(deps) === JSON.stringify(cacheDepsRef.current)) {
fn();
}
// execute on every render
if (!deps) {
fn();
}
cacheDepsRef.current = deps ? deps : [];
}
And finally is time for cleaning up the effect!
Clean up function implementation
function useCustomUseEffect(fn, deps) {
const firstTimeRef = useRef(true);
const cacheDepsRef = useRef([]);
if (firstTimeRef.current) {
firstTimeRef.current = false;
const cleanUp = fn();
return () => {
if (cleanUp && typeof cleanUp === "function") {
cleanUp();
}
};
}
if (JSON.stringify(deps) === JSON.stringify(cacheDepsRef.current)) {
const cleanUp = fn();
return () => {
if (cleanUp && typeof cleanUp === "function") {
cleanUp();
}
};
}
if (!deps) {
const cleanUp = fn();
return () => {
if (cleanUp && typeof cleanUp === "function") {
cleanUp();
}
};
}
cacheDepsRef.current = deps ? deps : [];
}
As easy as that! ok, now all of our edge cases are completed!π
So our custom useEffect is now completed! hopefully this is clear enough and anybody reading this is one step closer to being confident writing this custom implementations and adding more functionality on top!π
Posted on December 19, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.