Context API
I have a project âREST Countries API with color theme switcherâ from Frontend Mentor that I was not working on it for a while. Originally, I didnât use Context API
and in fact, I was planning to finish this without the API. Until I watched this video React JavaScript Framework for Beginners â Project-Based Course from FreeCodeCamp. I figured I should try to implement the API in the project.
Also, check out the live site for what it does before moving on.
Letâs start on the topic:
It is better to see the codebase in action and why the API is awesome! Without the API, props will have to pass down from component to component. There can be more layers of components. I often had to go and back forth between files while working on this project. I think this could be really difficult to debug the app when you have to figure out where the props originally came from. Letâs think about this if the app is large with thousands of components and passing props down through the tree root alike structureâŚ? I think you get the idea.
Letâs start on what the file looks like before.
Before useContext
function Main() {
// this is from the useFetchCountries.jsx where I set up for data, loading and list
const { countries, loading, list, setList } = useFetchCountries();
// this is for the search bar where the users will type to search
const [searchTerm, setSearchTerm] = useState("");
// this is for select region, like America, Asia, etc
const [selectRegion, setSelectRegion] = useState("");
// this is for the pagintation that will display 12 countries each page
const [countriesPerPage] = useState(12);
// this is to show the current page, e.g. page 1, page 2..etc
const [currentPage, setCurrentPage] = useState(1);
// a function to filter countries based on the search value
const handleSearch = (searchValue) => {
const value = searchValue.toLowerCase();
setSearchTerm(value);
const searchFiltered = countries.filter(({ name }) => {
const countryName = name.official.toLowerCase();
return countryName.includes(value);
});
setList(searchFiltered);
};
// a function to filter countries based on its region
const handleSelect = (selectValue) => {
setSelectRegion(selectValue);
const selectFiltered = countries.filter(({ region }) => region.includes(selectValue));
setList(selectFiltered);
};
// set up 12 countries display on each page
// and they go into pagination component
const idxOfLastCountries = currentPage * countriesPerPage; // 1 * 12 = 12
const idxOfFirstCountries = idxOfLastCountries - countriesPerPage; // 12 - 12 = 1
const currentCountries = list.slice(idxOfFirstCountries, idxOfLastCountries);
return (
<main className="">
<div className="">
<Form
searchValue={searchTerm}
onChangeSearch={(e) => handleSearch(e.target.value)}
optionValue={selectRegion}
onChangeSelect={(e) => handleSelect(e.target.value)}
/>
<Routes>
<Route path="/" element={<Countries countries={currentCountries} loading={loading} />} />
</Routes>
<Pagination
countriesPerPage={countriesPerPage}
totalCountries={list.length}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
/>
</div>
</main>
);
}
This is the whole code for the Main
component. It does look messy. We also need to pass down props to Form
, Countries
and Pagination
components. How can I organize the code better? This is where the API comes in.
A picture worth a thousand words!
As the illustration and code show, searchTerm
, selectRegion
, handleSearch
, and handleSelect
were defined in the Main
component. For the Search
and Select
to work, they will have to get props from Main
.
Now, letâs imagine this. If there are more components below Search
or Select
? You would pass props down more levels. If you find yourself pass down props from component to component and to more components down. This is prop drilling
.
Is prop drilling
a bad thing? Probably! As the app grows and more components are added, there will be more layers of components to pass down. It may get to the point where you need to take the time to find where props originally came from. Also, if there are bugs somewhere in between components, you may need to investigate them to find the root cause. This also requires time.
Now, letâs see what it looks like after using the API!
After useContext
function Countries() {
const { loading } = useContext(CountriesContext);
const { currentCountries } = useContext(PaginationContext);
if (loading) return <Spinner />;
return (
<>
<Form />
<section className="">
// this is a grid that display country cards which is the homepage
</section>
<Pagination />
</>
);
}
Note: the
Main
component become a homepage that will display country cards, detailed country informaiton page, and 404 error page.Countries
componenet is underpage
folder.
As you can see, there are no more functions or useState
hooks from the before
code block. They are all moved to CountriesContext.jsx
.
First, createContext
needs to be imported.
// define the context that you will need to call it with `useContext` hook
const CountriesContext = createContext();
export function CountriesProvider({ children }) {
// from useFetchCountries.jsx under hook folder
const { countries, loading, list, setList } = useFetchCountries();
// these lovely useState hooks for search and select
const [searchTerm, setSearchTerm] = useState("");
const [selectRegion, setSelectRegion] = useState("");
const handleSearch = (searchValue) => {
// the code is deinfed above
};
const handleSelect = (selectValue) => {
// the code is deinfed above
};
return (
// remember the line of code that was created with `createContext`?
// you put all functions and useState hooks that will be used by other components
<CountriesContext.Provider
value={{
loading,
list,
searchTerm,
selectRegion,
handleSearch,
handleSelect,
}}
>
{children}
</CountriesContext.Provider>
);
}
export default CountriesContext;
Letâs see the code from before and after with Form
component. Notice the after
code doesnât take any props like the before
code? This is the advantage of Context API
.
Before
<Form
searchValue={searchTerm}
onChangeSearch={(e) => handleSearch(e.target.value)}
optionValue={selectRegion}
onChangeSelect={(e) => handleSelect(e.target.value)}
/>
After
// wow no props to pass down!!!
<Form />
First, go to the SearchBar
component that needs one or more values from the CountriesContext.jsx
.
function SearchBar() {
// remember the `CountriesContext` that was created in the context file?
// you only call the value that you need for this component
// searchTerm, handleSearch in this case and then pass them to value and onChange below
const { searchTerm, handleSearch } = useContext(CountriesContext);
return (
<div className="">
<IoSearchSharp className="" />
<input
value={searchTerm}
onChange={(e) => handleSearch(e.target.value)}
className=""
type="text"
placeholder="Search for a country..."
/>
</div>
);
}
Yay, no more props pass down!
Summary
Imagine the context file as a main warehouse that holds everything. If a store needs a specific thing, they will call the warehouse and let them know the store needs thing A
or more things. And the warehouse will deliver these things that they need. In short, the SearchBar
component needs searchTerm
and handleSearch
to do its job, so they call the CountriesContext.jsx
for the 2 values that it requires.
Links
Feel free to look around and see the differences between both versions with or without useContext
Recap
The Context API
allows you to define functions and hooks in the context file. You can organize functions and hooks based on their purpose. e.g CountriesContext
for fetching country data, search and select functions or PaginationContext
for all pagination related.
Advantages:
- all functions and hooks are in one file
- keep component files clean
- components can take props from the Context API instead of passing down props
Resources
- React Docs - useContext
- read this before and was somewhat understand the idea of useContext
- React JavaScript Framework for Beginners â Project-Based Course
- This is where I actually understand
Context API
, highly recommend following along and trying to implement it in your project(s)!
- This is where I actually understand
- Prop Drilling by Kent C. Dodds
Thank you!
Thank you for your time and for reading this!