DEV Community

Dev Kadiem
Dev Kadiem

Posted on • Originally published at Medium on

Advanced ReactJS Tutorial: Master Context In A Few Simple Examples


ReactJS logo

React Context transfers data between different levels of your application without "Prop Drilling." It provides a way to pass data through the components tree.

Prop Drilling

To further understand react Context, we must first understand the problem it solves. Prop drilling occurs when we continue passing data to child components in an endless way, even though some of those components don't use it; they receive it to pass it to their children.

Let's take a typical example, a counter app.

export default function App() {
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <AddOneButton setCounter={setCounter} />
      <Counter counter={counter} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

It has two children components, one to increase the count and another to show the current count.

const AddOneButton = ({setCounter}) => {
  return (
    <button onClick={() => setCounter((value) => value + 1)}>Add One</button>
  )
}

const Counter = ({counter}) => {
  return (
    <p>Count: {counter}</p>
  )
}
Enter fullscreen mode Exit fullscreen mode

Now let us say we have a new component called Container that wraps the AddOneButton.

const Container = ({setCounter}) => {
  return <AddOneButton setCounter={setCounter} />
}
Enter fullscreen mode Exit fullscreen mode

And now, inside the App we render the new Container component.

<Container setCounter={setCounter} />
Enter fullscreen mode Exit fullscreen mode

Now it's clear that Container acutely does not need the setCounter function, we passed it just because AddOneButton needs it, and this what known as "Prop Drilling" this is why we need the Context Api.

NOTE

In this case, we can mitigate the use of prop drilling by making the Container component a generic component and passing AddOneButton as a child.

const Container = ({children}) => {
  return (
    {children}
  )
}

export default function App() {
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <Container>
        <AddOneButton setCounter={setCounter} />
      </Container>

      <Counter counter={counter} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Context with useState

now that we understand prop drilling, we can start using Context Api, but before that, we need to decide what way we will implement Context; there are mainly two ways either with useState or useReducer in this section, we will do it using useState, let us start by importing the required modules.

import {createContext, useContext} from "react";
Enter fullscreen mode Exit fullscreen mode

Then we create the Context and its provider.

export const CounterContext = createContext(null);

export const CounterContextProvider = ({ children }) => (
  <CounterContext.Provider value={useState(0)}>
    {children}
  </CounterContext.Provider>
);
Enter fullscreen mode Exit fullscreen mode

the createContext function initializes the Context, the provider will wrap our application (only the components inside the provider will have access to data), and because we are using context API we don't need to pass anything to Container or Counter components.

export default function App() {
  return (
    <CounterContextProvider>
      <Container />
      <Counter />
    </CounterContextProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now to access the data, we shall use useContext hook.

const Container = () => {
  return <AddOneButton />;
};

const AddOneButton = () => {
  const [, setCounter] = useContext(CounterContext);
  return (
    <button onClick={() => setCounter((value) => value + 1)}>Add One</button>
  );
};

const Counter = () => {
  const [counter] = useContext(CounterContext);
  return <p>Count: {counter}</p>;
};
Enter fullscreen mode Exit fullscreen mode

Context with useReducer

Note: It's recommended to use useReducer if our state is more complex.

Let us start by importing the required modules.

import { createContext, useContext, useReducer } from "react";
Enter fullscreen mode Exit fullscreen mode

Now let's create a reducer and plug it into our context data.

const reducer = (state, action) => {
  switch(action.type) {
    case "increment":
      return state + 1;
    default:
      return state
  }
}

export const CounterContext = createContext(null);

export const CounterContextProvider = ({ children }) => (
  <CounterContext.Provider value={useReducer(reducer, 0)}>
    {children}
  </CounterContext.Provider>
);
Enter fullscreen mode Exit fullscreen mode

The hook returns a tuple, the first element is the value, and the second is the dispatch function.

const Container = () => {
  return <AddOneButton />;
};

const AddOneButton = () => {
  const [, dispatch] = useContext(CounterContext);
  return (
    <button onClick={() => dispatch({type: "increment"})}>Add One</button>
  );
};

const Counter = () => {
  const [counter] = useContext(CounterContext);
  return <p>Count: {counter}</p>;
};
Enter fullscreen mode Exit fullscreen mode

And basically, that's how context Api works.

Top comments (0)