I know the title sounds a bit scary, but trust me, you'll find it very easy once you're done reading this blog! I assure you that you'll understand this pattern without repetition.
I'll try to explain this in a Q&A format to make the understanding very clear. Let's get started with our first question:
Component composition is a very fundamental concept in React. It allows you to build complex or large user interfaces (UIs) by assembling smaller, independent components. Think of Lego blocks, where we assemble smaller blocks to create an entire car, building, or helicopter.
Easy, right? Let's move on to the next question then...
Let's say we have three components that present Star Wars character data but with different UIs. One component lists all characters with their photo, name, age, and popularity in a card format. The second displays all characters with just their photo and name, and the last one displays only character names.
How would you build that? A simple answer would be to write API fetch code in each component separately. Don't worry, you're not wrong; I would have done the same at my early stage.
But here's where hooks come into play. What about moving that API fetch code into a custom hook called useStarWarsCharacters
and using it in all three components?
Sounds good, right? Let's get a clearer picture by example.
Below is our custom hook useStarWarsCharacters
that fetches Star Wars characters.
// creating a custom hook that fetches star wars characters
export const useStarWarsCharacters = () => {
const [characters, setCharacters] = useState<Character>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(false);
const controller = new AbortController();
const getCharacters = async () => {
setIsLoading(true);
try {
const response = await fetch(
"https://akabab.github.io/starwars-api/api/all.json",
{
method: "GET",
credentials: "include",
mode: "cors",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
signal: controller.signal,
},
);
const data = await response.json();
setIsLoading(false);
if (!data) return;
setCharacters(data);
} catch (err) {
setError(true);
} finally {
setIsLoading(true);
}
};
useEffect(() => {
getCharacters();
return () => {
controller.abort();
};
}, []);
return [characters, isLoading, error];
};
See below example for it's usage.
// importing the custom hook to a component and fetch the characters
import React from "react";
import { Character } from "./types";
import { useStarWarsCharacters } from "./useFetchStarWarsCharacters";
const StarWarsCharactersContainer: React.FC = () => {
const [characters, isLoading, error] = useStarWarsCharacters();
return (
<CharacterList loading={loading} error={error} characters={characters} />
);
};
export default StarWarsCharactersContainer;
Just like that we can now isolate all stateful logic—a type of logic that needs reactive state variable(s)—and compose or use it in all three components using our useStarWarsCharacters
custom hook. As a result, the code is more modularized and testable because the hooks are loosely tied to the component and can therefore be tested separately.
Yep, That's all, you've now understood Component composition with Hooks patter in react.
Subscribe for more content like this delivered straight to your inbox, and let's chat in the comments. Share your thoughts and experiences!👀
Happy Hacking! 👾