Grasping Context

Photo by SHTTEFAN on Unsplash

Grasping Context

React is all about having components on hand to use wherever needed, even inside of other components. Props are a great ability of components .It is so cool for a parent component to be able to hand down information to any of its child components nested within the parent.

However, there is a problem called "prop drilling." Essentially, it is when you want to pass information to a component that is not a direct child of the parent component, but when the component is nested within the child component of a child component of a child component, etc. The information has to get passed from child component to the child of the first child component and so on and so on until finally reaches the target component nested 4 or 5 levels down.

There is a better way to make any information available to every part of the client side of your app if you so wish it. It is called "useContext'."

First, create a file in your app and insert the following code:

import { createContext } from "react";

const UserContext = createContext(null)

export default UserContext;

What is happening is that you are importing the createContext functionality from react and using it to return an empty context object. It is like declaring a global variable that you have not assigned a value yet. You then export said variable so that it can be imported by any other react file on the client side of you app that you give access to the context.

But what good is a blank variable?

The next thing you have to do is use something called a "Provider" to give components, and all of their descendants, access to the context. If you want to give your entire app access, then you should use Provider in the "App.js" file:

import React, { useState } from "react";
import Navbar from "./Navbar";
import Main from "./Main"
import Footer from "./Footer";
import UserContext from "./User";


function App() {
  /* Create default info for user state */
  const default_user = {
    user_name: "Bob",
    id: 0
  }
  /* Set user state to default */
  const [userData, setUserData] = useState(default_user);

  /* Function that will set user state to 'obj' passed */
  const setUser = (obj) => {
    setUser(obj);
  }

  return (
    <div id="appDiv">
      <UserContext.Provider value={[
        userData,
        setUser,
        "This is how useContext works!"
      ]}>
          <Navbar />
          <Main />
          <Footer />
      </UserContext.Provider>
    </div>
  );
}

export default App;

There are a few things happening here. I am importing the "UserContext" object we created earlier. I am wrapping the parts of the app that I want to be able to have access to the context in the "UserContext.Provider" component. I am then setting the "value" prop to an array of values to be accessible through the context. By setting the value prop, I am filling that blank variable. I could have set only one value, but I decided to show you some of the versatility of useContext.

Now that we have this data in the ethos, how do we use it?

Let's say I wanted to have greeting in the Navbar component. I could set up the following:

import React, { useContext } from "react";
import { Link } from "react-router-dom";
import UserContext from "./User";

function Navbar() {
    const value = useContext(UserContext);
    const user = value[0];
    const user_name = user["user_name"];

    return (
        <>
            <div id="navBar">
                <Link
                    id="logo"
                    to="/"
                >Logo Image</Link>
                <span
                    id={navSpan}
                >
                    <Link
                        id="posts"
                        to="/post"
                    >Posts</Link>
                    <Link
                        id="profile"
                        to="/profile"
                    >Profile</Link>
                    <Link
                        id="settings"
                        to="/settings"
                    >Settings</Link>
                </span>
            </div>
            <h1>Welcome {user_name || ''}!</h1>
        </>
    )
}

export default Navbar;

I import the useContext hook from react and UserContext. I grab the value prop from the context by passing UserContext to the useContext method. At this point, value is assigned the array we set in the Provider. So I can access the user which is the first element of the array, value[0], grab the user_name of the user object, and use the name in my welcome banner. Your screen should now say, "Welcome Bob!"

But we change the user_name in another part of app by importing the function in the value prop and passing it a new user object:

import React, { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import UserContext from "./User";

function User() {
    const { id } = useParams();
    const value = useContext(UserContext);
    const user = value[0];
    const func = value[1];

    useEffect(() => {
        fetch(`/user/${id}`)
        .then(res => {
            if (res.ok) {
                return res.json()
            } else {
                throw(res.statusText)
            }
        })
        .then(data => {
            func(data);
        })
        .catch(err => {
            throw new Error(err)
        })
    }, [])

    return (
        <>
            <h2>User: {user["user_name"] || ""}</h2>
            <h2>Id: {user["id"] || ""}</h2>
        </>
    )
}

export default User;

I fetch a user by id. Also, I again access the value prop from the Provider and grab the second index of the array, which was a function. I pass the user data object to the function, which sets the state of the user in App.js, which sets the user in context, which I can access to populate data in elements.

In my opinion, useContext is a very useful hook which can make it a lot simpler to access data from component to component without having to keep track of the data being passed through a series of components, which may not even exist in the next version of your app. Which means you would have to make sure the data chain was not broken every time you change a component rather than importing what you need when you need it in true React fashion.

I hope this has been insightful!