DEV Community

Cover image for React Context API
Rachel Stark
Rachel Stark

Posted on

React Context API

Today, I'm sharing my experience of leveraging the power of React Context API with the useContext hook for authentication in React. My current project is relatively small, with each main page handling its own state, but there's one crucial element that constantly needs to be passed down throughout the entire application: the USER.

Authentication is a vital aspect of any application, ensuring that users have secure access to the appropriate features and data. Implementing React Context API and the useContext hook into my project, really made me realize how much prop drilling and state lifting could be eliminated.

Before I get started I would like to point out that I did not use Context for the user's account information. This example is purely for Authentication for logging in and out of the app and having just enough data for authorization. To achieve this, I only needed to pass the user's ID and username, allowing control of which features are accessible and customize the user experience accordingly. On the backend, I used Rails and created a dedicated serializer to carefully manage the user information being sent to the front-end for authentication purposes. The aim is to ensure both security and a personalized experience for each user.

First, create a contexts folder in your src file in React app that contains a new .js file called AuthContext.js.
src/contexts/AuthContext.js

Next to create a context for our user object call the createContext method in the file and create a provider component named AuthContextProvider that takes in props, formatted just like any other React component. (React snippets extension in VS Code, you can call rafce)

import { createContext } from 'react'

const AuthContextProvider = (props) => {

  return (
    <AuthContext.Provider value={}>
    </AuthContext.Provider>
  )
}

export default AuthContextProvider
Enter fullscreen mode Exit fullscreen mode

The component receives a value prop, which determines the desired state or data for the context. Next, create state for the user in the provider and plugging in a simple object for testing. (Don't forget to import useState) Pass the user as the value for the AuthContext. Then, add the children for the AuthContext { props.children }.

import { useState, useContext, createContext } from 'react'

const AuthContextProvider = (props) => {
   const [user, setUser] = useState({user_id: 1, username: "James"})

  return (
    <AuthContext.Provider value={ user }>
        {props.children}
    </AuthContext.Provider>
  )
}

export default AuthContextProvider
Enter fullscreen mode Exit fullscreen mode

We want to render an AuthContext.Provider with the value of the user and wrap all of the components' children allowing the the children to have access to the value that is passed in. ( user ) Now, in the App.js, I'm going to wrap my entire app with the provider because all of it needs the user in some way or passed down to their children. Make sure to import the AuthContextProvider!

import AuthContextProvider from '../contexts/AuthContext';

function App() {

  return (
    <>
      <AuthContextProvider>
        <NavBar  />
        <Routes>
          <Route path="/" element={<Home  />} />
          <Route path="/signup" element={<SignUp />} />
          <Route path="/signin" element={<SignIn  />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/trucks" element={<Trucks />} />
        </Routes>
      </AuthContextProvider>
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now all of the child components should have access to the value of the AuthContext.Provider. To get access to this context, you need to use the useContext hook from react in the components that need the user (context) and pass in the context that was created. (AuthContext). This will return the value of the user.

import { useContext } from 'react'
import { useAuth } from '../contexts/AuthContext'

const Home = () => {
  const user = useContext(AuthContext)

  return (
    <div>
      <h1>{user.username}</h1>
    </div>
  );
}

export default Home
Enter fullscreen mode Exit fullscreen mode

There is a way to shorten this since you will probably use it in a lot of components. Go back to your context file and create a custom hook.

import { useState, useContext, createContext } from 'react'

export const AuthContext = createContext()

const AuthContextProvider = (props) => {
    const [user, setUser] = useState({user_id: 1, username: "James"})

  return (
    <AuthContext.Provider value={ user }>
        {props.children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => useContext(AuthContext)

export default AuthContextProvider
Enter fullscreen mode Exit fullscreen mode

Here you can create a function called useAuth() that returns the output of useContext(AuthContext) (which is the value prop). Now when you go back to a child component, you can reduce your code and just call useAuth() instead of calling and importing useContext. see below:

import { useAuth } from '../contexts/AuthContext'

const Home = () => {
  const navigate = useNavigate()
  const user = useAuth()

  return (
    <div>
      <h1>{user.username}</h1>
    </div>
  );
}

export default Home
Enter fullscreen mode Exit fullscreen mode

Once you have it set up, I suggest testing it out to make sure you have it all working. Then you can move on to fetching your user data and setting the state. From there add your logIn and logOut functions. See below how I added the functions to the value object. You will pass them to the children the same way as above, only make sure you deconstruct so your component has access to them.
const { user, logOut } = useAuth()

import { useState, useContext, createContext, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'

export const AuthContext = createContext()

const AuthContextProvider = (props) => {
    const [user, setUser] = useState(null)
    const navigate = useNavigate()

    useEffect(() => {
      fetch("/auth").then((resp) => {
        if (resp.ok) {
          resp.json().then((newUser) => {
            setUser(newUser);
          });
        }
      });
    }, []);

    const logInUser = (userObj) => {
      setUser(userObj);
      navigate("/");
    };

    const logOut = () => {
      fetch("/logout", {
        method: "DELETE",
      }).then((resp) => {
        if (resp.ok) {
          setUser(null);
        }
      });
      navigate("/");
    };

  return (
    <AuthContext.Provider value={ {user, logOut, logInUser} }>
        {props.children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => useContext(AuthContext)

export default AuthContextProvider
Enter fullscreen mode Exit fullscreen mode

I was hesitant to implement useContext at first, but like most code... once you understand it, you wonder how you lived without it. I hope this can be helpful to someone, I'm always open to learning more so don't be afraid to leave a comment.

--
You can connect with me on LinkedIn & GitHub

Resources:
React - useContext

React useContext

Top comments (0)