DEV Community

Cover image for Using React Context API Like a Pro
Rahul
Rahul

Posted on • Edited on

Using React Context API Like a Pro

If you've been hearing the term "Context API" and feel totally confused about it (like me, some days ago) or you have no clue what this even means, look no further! I've got you covered (for the most part, I believe)

Concept behind Context API

One thing to note: You can very well work without Context API by using normal "prop drilling". Context API does only one thing and that is "it reduces coupling between non-related components".

React Components should only hold the logic necessary for their operation.
One component, one role. (Mind you that "role" highly depends on the type of task you are creating the component for)

Each react program has some components that hold certain states on which the program depend on. These states are passed from "parent components" to "children components" through "props".

Now, passing states between components that are not necessarily in a parent-child relationship, is handled through context API.

Consider this Example

Have a look at the component Diagram below

Alt Text

Here the SearchBar.js Component, down in the component tree, has a state that takes in the search input from the user

// SearchBar.js

import React, { useState } from "react";
import { Link } from "react-router-dom";

import styles from "./SearchBar.module.css";
import SearchLogo from "../../assets/search.svg";

const SearchBar = (props) => {
  const [searchQuery, setSearchQuery] = useState("");

  return (
    <div className={styles.searchBar}>
      <input
        placeholder="Search"
        type="text"
        className={styles.input}
        onChange={(e) => setSearchQuery(e.target.value)}
        value={searchQuery}
      />
      <Link
        to="/search-result"
      >
        <img src={SearchLogo} alt="Search Logo | magnifying glass" />
      </Link>
    </div>
  );
};

export default SearchBar;
Enter fullscreen mode Exit fullscreen mode

The state ("searchQuery") is actually what we need in the SearchResult.js component to filter out products or whatever.

Ways to achieve this

  1. Define the state and setState function in App.js, pass them as props to Layout.js, pass them further to Header.js, at last pass them to the SearchBar.js Component. Now use, the setState function to travel all the way back to App.js component and change the state.

OR

  1. UseContext API!!!

Creating a Context

First thing we need to do is to define our context. The way I like to do this is by creating a HOC(Higher Order Component), that wraps the App component.

Like so...

(Don't trip on the seeing the SearchContextProvider component. We'll define it in just a second.)

// index.js

import React from "react";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

import SearchContextProvider from "./context/search-context";

ReactDOM.render(
  <React.StrictMode>
    <SearchContextProvider>
      <App />
    </SearchContextProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Enter fullscreen mode Exit fullscreen mode

Now, let's create a "context" and also the SearchContextProvider component

SearchContextProvider is just a simple HOC component, but with a special feature that it wraps the children components with a Context.Provider.

The most important thing to note is the value prop on SearchContext.Provider. ( Down in the code )
Whatever you put in the value prop, it becomes available to the children component.

Now you can use the "query" state and "searchHandler" function in any component inside the App component

import React, { useState } from "react";

// query is the state
// SearchHandler is a function for changing the state.
export const SearchContext = React.createContext({
  query: "",
  searchHandler: () => {},
});

// Defining a simple HOC component
const SearchContextProvider = (props) => {
  const [query, setQuery] = useState("");

  const searchHandler = (query) => {
    setQuery(query);
  };

  return (
    <SearchContext.Provider
      value={{ query: query, searchHandler: searchHandler }}
    >
      {props.children}
    </SearchContext.Provider>
  );
};

export default SearchContextProvider;
Enter fullscreen mode Exit fullscreen mode

PS: Don't be confused by the redundant code in the createContext function. It is totally optional.
I write this for better intellisense and code completion.

This works fine as well!

export const SearchContext = React.createContext();
Enter fullscreen mode Exit fullscreen mode

Using the Context

Using the context is super intuitive and as simple as it could be.
Use it just like any other state or function!!!

New lines we would like to add to the SearchBar.js component

...
import React, { useState, useContext } from "react";
import { SearchContext } from "../../context/search-context";
...

const SearchBar = (props) => {
...
  const searchContext = useContext(SearchContext);

  const searchQueryHandler = () => {
    searchContext.searchHandler(searchQuery);
  };
...
}
Enter fullscreen mode Exit fullscreen mode

SearchBar.js using the context API looks something like this

// SearchBar.js

import React, { useState, useContext } from "react";
import { Link } from "react-router-dom";

import { SearchContext } from "../../context/search-context";
import styles from "./SearchBar.module.css";
import SearchLogo from "../../assets/search.svg";

const SearchBar = (props) => {
  const [searchQuery, setSearchQuery] = useState("");
  const searchContext = useContext(SearchContext);

  const searchQueryHandler = () => {
    searchContext.searchHandler(searchQuery);
  };

  return (
    <div className={styles.searchBar}>
      <input
        placeholder="Search"
        type="text"
        className={styles.input}
        onChange={(e) => setSearchQuery(e.target.value)}
        value={searchQuery}
      />
      <Link
        to="/search-result"
        onClick={searchQueryHandler}
      >
        <img src={SearchLogo} alt="Search Logo | magnifying glass" />
      </Link>
    </div>
  );
};

export default SearchBar;
Enter fullscreen mode Exit fullscreen mode

This makes a copy of the searchQuery state and stores it in the query variable defined in our little context.

Now we could use the same state wherever we would like

// SearchResult.js

import React, { useContext } from "react";
import { SearchContext } from "../../context/search-context";

import styles from "./SearchResult.module.css";
import ProductSection from "../../components/ProductSection/ProductSection";

const SearchResult = ({ products, ...props }) => {
  const searchContext = useContext(SearchContext);
  let filteredProducts;
  if (products) {
    filteredProducts = products.filter((product) => {
      if (
        product.title.toLowerCase().includes(searchContext.query) ||
        product.tags.toLowerCase().includes(searchContext.query)
      ) {
        return product;
      }
      return null;
    });
  }
  return (
    <div>
      <div className={styles.title}>
        <h1>Search Results</h1>
      </div>
      <div className={styles.container}>
        {filteredProducts && (
          <ProductSection
            products={filteredProducts}
            sectionSlug="search-result"
          />
        )}
      </div>
    </div>
  );
};

export default SearchResult;
Enter fullscreen mode Exit fullscreen mode

Just a simple filtration logic, checking if "title" or "tags" contain the string stored in the searchContext.query variable.

Lines to focus on in the above code.

import React, { useContext } from "react";
import { SearchContext } from "../../context/search-context";
...

const SearchResult = ({ products, ...props }) => {
  const searchContext = useContext(SearchContext);

  let filteredProducts;
  if (products) {
    filteredProducts = products.filter((product) => {
      if (
        product.title.toLowerCase().includes(searchContext.query) ||
        product.tags.toLowerCase().includes(searchContext.query)
      ) {
        return product;
      }
      return null;
    });
  }
return (
  ...
)

}
Enter fullscreen mode Exit fullscreen mode

Just a simple filtration logic, checking if "tags" or the "title" contains the string stored in the searchContext.query variable.

Thank you so much for reading

Would love to hear your thoughts

Top comments (7)

Collapse
 
tbroyer profile image
Thomas Broyer

Unfortunately, I think this is not a good use of the Context API, component composition would be a better fit here.
kentcdodds.com/blog/application-st...
epicreact.dev/one-react-mistake-th...

Also, the way you use the Context API is actually suboptimal: kentcdodds.com/blog/how-to-use-rea...

Collapse
 
holdmypotion profile image
Rahul

I'll surely check them out! Thank you for sharing them, mate. Cheers!

Collapse
 
endrureza profile image
Endru Reza

kinda wonder though, is using react context better than redux ? we're talking about performance here

Collapse
 
markerikson profile image
Mark Erikson

The answers are "no", but also "it's complicated".

First, it's important to understand that Context and Redux are very different tools that solve different problems, with some overlap.

Context is not a "state management" tool. It's a Dependency Injection mechanism, whose only purpose is to make a single value accessible to a nested tree of React components. It's up to you to decide what that value is, and how it's created. Typically, that's done using data from React component state, ie, useState and useReducer. So, you're actually doing all the "state management" yourself - Context just gives you a way to pass it down the tree.

Redux is a library and a pattern for separating your state update logic from the rest of your app, and making it easy to trace when/where/why/how your state has changed. It also gives your whole app the ability to access any piece of state in any component.

So, yes, you can use both of them to pass data down, but they're not the same thing. It's like asking "Can I replace a hammer with a screwdriver?". Well, no, they're different tools, and you use them to solve different problems.

Beyond that: Context has limitations on how state updates are propagated. In particular, every component that consumes a given context will be forced to re-render when that context value updates, even if the component only cares about part of the data inside the context value.

For more details, please see my posts:

Collapse
 
holdmypotion profile image
Rahul • Edited

Super informative. Thank you so much for the response!
"""
You're actually doing all the "state management" yourself - Context just gives you a way to pass it down the tree.

Redux is a library and a pattern for separating your state update logic from the rest of your app, and making it easy to trace when/where/why/how your state has changed. It also gives your whole app the ability to access any piece of state in any component.
"""
by- Mark Erikson

Collapse
 
baziotabeans profile image
fabioBaziota

Amazing you definitely clarified the difference...

Collapse
 
baziotabeans profile image
fabioBaziota

Thanks you so much, now I can learn redux... ❤