Learning React for Dummies: Hooks (useContext and useRef)

Week Three

ยท

6 min read

Let's hop right back into our third installment of Learning React for Dummies. This week we're going to take a look at the useContext and useRef hooks.

useContext

The useContext hook does not come up quite as much as useEffect or useState, but if you're building a large project with React you're almost inevitably going to run into the issue of having to pass state down through child component after child components to give it to the nested child component you actually need it for.

This is where the useContext hook comes in and saves the day. It allows us to manage state globally.

It's commonly used with the useState hook to share state between deeply nested components more easily than with useState on its own.

As we should remember from our talk on state, state should be held by the highest parent in its stack that requires access to the state.

This becomes quite annoying when we have to pass state to a deeply nested component because it has to be passed down through each component to get to the one we want.

When we pass state through each nested component with context, it's called "prop drilling".

Here's an example:

import { useState } from "react";
import ReactDOM from "react-dom";

function Component1() {
  const [user, setUser] = useState("Jesse Hall");

  return (
    <>
      <h1>{`Hello ${user}!`}</h1>
      <Component2 user={user} />
    </>
  );
}

function Component2({ user }) {
  return (
    <>
      <h1>Component 2</h1>
      <Component3 user={user} />
    </>
  );
}

function Component3({ user }) {
  return (
    <>
      <h1>Component 3</h1>
      <Component4 user={user} />
    </>
  );
}

function Component4({ user }) {
  return (
    <>
      <h1>Component 4</h1>
      <Component5 user={user} />
    </>
  );
}

function Component5({ user }) {
  return (
    <>
      <h1>Component 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  );
}

ReactDOM.render(<Component1 />, document.getElementById("root"));

Even components 2-4 didn't need the state, we had to pass state through them to get it to component 5.

The Solution

To fix this, we have to createContext.

Creating Context

To create context, first we have to import createContext to initialize it.

import { useState, createContext } from "react";
import ReactDOM from "react-dom";

const UserContext = createContext()

Next we'll use the Context Provider to wrap the tree of components that need the state Context.

Context Provider

Wrap child components in the Context Provider and supply the state value.

function Component1() {
  const [user, setUser] = useState("Jesse Hall");

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Component2 user={user} />
    </UserContext.Provider>
  );
}

Now, all components in this tree will have access to the user Context.

Use the useContext Hook

In order to use the Context in a child component, we need to access it using the useContext hook.

First, include the useContext in the import statement:

import { useState, createContext, useContext } from "react";

Then you can access the user Context in all components:

function Component5() {
  const user = useContext(UserContext);

  return (
    <>
      <h1>Component 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  );
}

Here's is the Full Example using React Context:

import { useState, createContext, useContext } from "react";
import ReactDOM from "react-dom";

const UserContext = createContext();

function Component1() {
  const [user, setUser] = useState("Jesse Hall");

  return (
    <UserContext.Provider value={user}>
      <h1>{`Hello ${user}!`}</h1>
      <Component2 user={user} />
    </UserContext.Provider>
  );
}

function Component2() {
  return (
    <>
      <h1>Component 2</h1>
      <Component3 />
    </>
  );
}

function Component3() {
  return (
    <>
      <h1>Component 3</h1>
      <Component4 />
    </>
  );
}

function Component4() {
  return (
    <>
      <h1>Component 4</h1>
      <Component5 />
    </>
  );
}

function Component5() {
  const user = useContext(UserContext);

  return (
    <>
      <h1>Component 5</h1>
      <h2>{`Hello ${user} again!`}</h2>
    </>
  );
}

ReactDOM.render(<Component1 />, document.getElementById("root"));

Now keep in mind, this hook isn't always the right choice. If simpler, simply passing props from parent to child may be the appropriate choice for the situation. This hooks comes into play if we're going to be constantly accessing a particular piece of data throughout an app and it would be tedious to pass down state over and over again.

The addition of hooks to React changed the way that Developers wrote React and the useContext hook is a great expose of the power hooks can have. In many previous contexts, developers would have had to resort to some sort of state management library like Redux.

Maybe in a later installment we'll talk about those... Still got plenty to get through here though ๐Ÿ˜….

Anywho, let's move on.

useRef

The useRef hook is great for holding a value that we want to persist between renders.

This allows us to create a value that is mutable that doesn't cause a re-render when updated. Very nice if we want to change the value of our currently empty input field without re-rendering the entire page unnecessarily.

You will notice that developing things with rendering and re-rendering is something that comes up all the time when building things with React.

So let's say that you want to create an input field and we want to track how many times our page re-renders.

So here's our Example:

import { useState, useEffect, useRef } from 'react'



function Counter () {

const [ inputValue, setInputValue ] = useState(0)
const count = setRef(0)

useEffect(() => {
     count.current = count.current + 1
}


return (
 <div>
     <input type='text
      onChange={(e) => setInputValue(e.target.value)} />
      <h3>This is how many times our page has rendered: { count.current }</h3>
</div>
)

}

So now, every time that we type or delete something in our input field you'll notice that the count goes up by one.

When the input field is changed the onChange event handler is called which takes in a callback function that is excepting an event as input and calling our setInputValue function from our useState hook and that updates the state of our inputValue.

When we update our inputValue a re-render is triggered (remember that useState triggers a re-render when state is changed), this in turn triggers the useEffect hook (which runs on every render).

The value of const doesn't change between renders remember? The useEffect hook is adding 1 to the the count variable every time it is triggered, which is every time that something is typed into the input field.

Hopefully that is making at least a little sense.

You'll notice that we used current to access our value of count. This is because useRef() only returns one item; an object called current.

So when we want to access that value that we initialized our useRef() with when we called it (0 in this case), we use count.current.

Another common use case of this hook would be when we want to keep track of state changes.

Example:

import { useState, useEffect, useRef } from 'react'


function App () {
     const [inputValue, setInputValue] = useState('');
     const previousInputValue = useRef('')

    useEffect(() => {
          previousInputValue.current = inputValue
}, [inputValue])


return (
    <>
          <input
          type='text'
           onChange={(e) => setInputValue(e.target.value)}
         />
         <h2>Current Value: {inputValue}</h2>
          <h2>Previous Value: {previousInputValue.current}</h2>

     </>

     )
}

In the useEffect, we're updating the useRef current value each time the InputValue is updated by entering text into the input field and triggering our onChange event handler.

In Conclusion

Overall, I feel as though I've been learning a lot since I started this blog and I'm very excited to see where it takes me.

I hope that you feel the same (:

Till next time, Cheers!

ย