DEV Community

Cover image for Introduction to React Context
josesrodriguez610
josesrodriguez610

Posted on

Introduction to React Context

Last Week I wrote about setting up Redux so today I’m going to talk about another way to keep track of your state and this comes from React.

Context, like Redux is a way to share data between components without having to pass props through components that are not using, but Context is very lightweight and easy to set up.

Let’s jump in!

Like last week, today I have this code which will have an input that we are keeping track in the input state and when you press the button it will update the user state with the input state and display to the page. I made a console log to keep track of the user as well.
I made a couple of layer deep so I can give you an example of Context.

import React, { useState } from "react";
import Layer2 from "./Layer2";

function App() {

  const [input, setInput] = useState('');
  const [user, setUser] = useState('');

  console.log(user);

  const handleClick = () => {
    setUser(input);
  }

  return (
    <div>
      <center><h1>Hello Class  🚀</h1>
        <input value={input} type='text' onChange={(e) => setInput(e.target.value)} />
        <button onClick={handleClick}>Send</button>
        <br />
        <br />
        {user}
        <Layer2 />
      </center>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Alt Text

First we are going to create 2 files in our src folder called StateProvider.js and Reducer.js

Alt Text

Now let’s go into StateProvider.js and lets import createContext, useContext and useReducer from react

import React from 'react'
import { createContext, useContext, useReducer } from 'react'
Enter fullscreen mode Exit fullscreen mode

createContext lets you initialize Context and useContext accepts a context object and return the current value for the context and it will help re-render the value as well.

useReducer accepts a trigger (a type) and it returns that trigger value with the help of dispatch which fires an action.

Now we have to initialize createContext and save it in a variable which we will call StateContext and export it.

Now we are going to create a variable called StateProvider and this has three props. Reducer, initialState, and children.
We are going to return a tag accessing the Provider which is in the StateContext and we are going set a value with useReducer and that is going to take the reducer (accepts a type) and the initialState (initial value)
Now we are going to wrap the children prop with this tag which will point to our App.js

import React from 'react'
import { createContext, useContext, useReducer } from 'react'
export const StateContext = createContext();

export const StateProvider = ({ reducer, initialState, children }) => (
  <StateContext.Provider value={useReducer(reducer, initialState)}>
    {children}
  </StateContext.Provider>
);
Enter fullscreen mode Exit fullscreen mode

Now let’s move on to our Index.js
Here we are going to import the StateProvider from our StateProvider.js file and we are going to wrap our App component with it.

import React from "react";
import ReactDOM from "react-dom";
import App from "./App.js";
import { StateProvider } from './StateProvider'

ReactDOM.render(
  <StateProvider initialState={} reducer={}>
    <App />
  </StateProvider>
  , document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

As you can see StateProvider takes the initialState and reducer that we set with our useReducer in our StateProvider but because we don’t have any value there yet our app is going to freak out a little but to fix it we now need to go and start working on our Reducer.js file.

In our Reducer.js file we are going to create and export our initialState and our reducer to add to our Index.js. We are going to create user and set it to null.

export const initialState = {
  user: null,
}
Enter fullscreen mode Exit fullscreen mode

Now let’s create our reducer which takes a state and action. The logic of the reducer is going to be made with switch statements and the expression is going to be a type. The case will be SET_USER because we are going to set the user and we are going to make our default return state and then we are going to export reducer. Let’s put a console.log to see what action we select.

export const initialState = {
  user: null,
}

const reducer = (state, action) => {

  console.log(action)

  switch (action.type) {
    case 'SET_USER':
    // logic
      break;
    default:
      return state;
  }
}

export default reducer;
Enter fullscreen mode Exit fullscreen mode

Now we are going to set our ’SET_USER’ case. We have to return our state with a spread operator so it doesn’t overwrite all our state and then we are going to set our user to a action

export const initialState = {
  user: null,
}

const reducer = (state, action) => {
  console.log(action)
  switch (action.type) {
    case 'SET_USER':
      return {
        ...state,
        user: action.user
      }
      break;
    default:
      return state;
  }
}

export default reducer;
Enter fullscreen mode Exit fullscreen mode

Now we can go back to our Index.js and import our initialState and reducer.

import React from "react";
import ReactDOM from "react-dom";
import App from "./App.js";
import { StateProvider } from './StateProvider'
import reducer, { initialState } from './reducer'

ReactDOM.render(
  <StateProvider initialState={initialState} reducer={reducer}>
    <App />
  </StateProvider>
  , document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

For our last step let's go to our StateProvider. Let’s create a variable useStateValue which will use the useContext and StateContext and useStateValue will let us access our Context state.

import React from 'react'
import { createContext, useContext, useReducer } from 'react'
export const StateContext = createContext();


export const StateProvider = ({ reducer, initialState, children }) => (
  <StateContext.Provider value={useReducer(reducer, initialState)}>
    {children}
  </StateContext.Provider>
);

// this will let us interact with our Context State
export const useStateValue = () => useContext(StateContext);
Enter fullscreen mode Exit fullscreen mode

Now we are ready to use or Context in our App.js.
Let’s import useStateValue from our StateProvider.js

import React, { useState } from "react";
import Layer2 from "./Layer2";
import { useStateValue } from './StateProvider';

function App() {

  const [input, setInput] = useState('');
  const [user, setUser] = useState('');

  console.log(user);

  const handleClick = () => {
    setUser(input);
  }

  return (
    <div>
      <center><h1>Hello Class  🚀</h1>
        <input value={input} type='text' onChange={(e) => setInput(e.target.value)} />
        <button onClick={handleClick}>Send</button>
        <br />
        <br />
        {user}
        <Layer2 />
      </center>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now we are going to delete our user state, clear our logic from our handleClick and comment out our console.log(user) from App.js and use our Context.
useStateValue has a state which will let us access our current state value and the dispatch which will trigger our state to our new value.

import React, { useState } from "react";
import Layer2 from "./Layer2";
import { useStateValue } from './StateProvider';

function App() {

const [ state, dispatch ]  = useStateValue()

  const [input, setInput] = useState('');


  // console.log(user);

  const handleClick = () => {

  }

  return (
    <div>
      <center><h1>Hello Class  🚀</h1>
        <input value={input} type='text' onChange={(e) => setInput(e.target.value)} />
        <button onClick={handleClick}>Send</button>
        <br />
        <br />
        {user}
        <Layer2 />
      </center>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now let’s add our logic to our handleClick. We need to use the dispatch and select the type which will be ‘SET_USER’
And then we can set the user to our input state. Let’s deconstruct the state and get the user. Let’s uncomment our console.log(user)

import React, { useState } from "react";
import Layer2 from "./Layer2";
import { useStateValue } from './StateProvider';

function App() {

const [ { user }, dispatch ]  = useStateValue()

  const [input, setInput] = useState('');


  // console.log(user);

  const handleClick = () => {
    dispatch({
      type: 'SET_USER',
      user: input
    })

  }

  return (
    <div>
      <center><h1>Hello Class  🚀</h1>
        <input value={input} type='text' onChange={(e) => setInput(e.target.value)} />
        <button onClick={handleClick}>Send</button>
        <br />
        <br />
        {user}
        <Layer2 />
      </center>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now if we type or name in the input and look at the console you will see the action and then the Context state updated.

Alt Text

Now we can go to our Layer4.js and import useStateValue and use it.

import React from 'react'
import { useStateValue } from './StateProvider';

function Layer4() {

  const [{ user }, dispatch] = useStateValue();

  return (
    <div>
      <h1>Layer 4</h1>
      {user}
    </div>
  )
}

export default Layer4
Enter fullscreen mode Exit fullscreen mode

And now you can see the user state in Layer 4 !

Alt Text

Conclusion

Context is a very simple and easy way to keep your state without prop drilling.

Latest comments (1)

Collapse
 
reinaldosimoes profile image
Reinaldo Simoes • Edited

Good explanation. I would just add that even though React’s Context API is easy to understand and use, it lacks the scalability that Redux provides if done incorrectly. So there’s no right or wrong answer, it all depends on how much you want your application to do :)