DEV Community

JustinBass
JustinBass

Posted on

Hello useContext!

When first introduced to the world of React we tend to make our executions more complex than they need to be. Don't get me wrong, after only a year of studying software engineering my executions at times are more complex than needed. But isn't that the beauty of creating and building out code? Those aha moments?! I had a particular aha moment in the last application I built. It allowed me to realize why hooks can benefit the building process on the frontend of an application. This particular hook I'm speaking of is called useContext.

useContext allows us to share data between components without having to pass that data from a parent component to a child component to another child and so on. The process of passing data like so, is called prop drilling.

Image description

Above we have a parent component MoviePage. You can see that I have set a variable selectedMovie with a value of a movie in the database based on a condition. I know I'm going to need this data for this parent's child, ReviewCard. The reasoning behind this is when a review is deleted I can update the selected movie, its reviews, and set a new state for my movie's array and its relations. In my ReviewCard import I pass a prop called selectedMovie equal to our variable selectedMovie. This allows me to use the data from the parent in the ReviewCard child component. I recommend keeping the prop naming conventions the same as the data variable or function names you are passing. As your app grows there's a good chance of easily getting confused with the data you're working with. Doing this will save you time from having to go back and forth between components to remind you of what's actually in front of you.

Image description

In our ReviewCard child component we are destructuring our selectedMovie prop. Don't forget to destruct your props! I can't tell you how many times I've forgotten this one small detail and spent hours wondering why my execution wouldn't work. Now I'm able to use the data needed to update the selected movie, its reviews, and set a new state for my movie's array and its relations once a review is deleted. But is this truly considered prop drilling? Remember prop drilling is having to pass data from a parent component to a child component to another child and so on. For this to be considered prop drilling, imagine that the ReviewCard component has a child. This child was the actual component that needed the data from the MoviePage component, which is the parent of ReviewCard. We pass our prop from MoviePage to ReviewCard and pass the prop to this component's child. Ladies, gents, thems and theys... that is prop drilling at it's finest.

Although prop drilling isn't completely frowned upon, useContext can be used globally throughout your application and prevents the action of having to prop drill. A bonus that comes along with this hook is that it will make it easier to keep track of data you want to share with other components and calls for cleaner code. In my application, I have four models set up in the backend. User, Movie, Review, and Reply. On the frontend, all of them will have their state set up for the data they retain. Now I could technically create one provider component where I would create context for all models and set state for data like I did in a project when I first learned useContext:

Image description

As you can see, given the useState() setup, I was using one provider component to set the context for all models and their data...🤭 If I'm being honest, I originally thought for useContext to work, the provider component always had to be called UserProvider and we set the state for all models and their data in this one provider component. THIS IS WHY IT'S IMPORTANT TO RESEARCH AND ASK QUESTIONS! Keep in mind you can name this provider component whatever you'd like. However, UserProvider makes sense because this component is technically providing data to other components who may need it. Naming it UserRocks would be a bit odd...

Remember convention over configuration is best practice. Since I have a User, Movie, Review, and Reply model, I want to separate my concerns and give each of them its very own provider component where context will be set for that particular class and its instances:

USER CONTEXT

Image description

MOVIE CONTEXT

Image description

REVIEW CONTEXT

Image description

REPLY CONTEXT

Image description

Ahhh, suddenly my anxiety has calmed down. 💁🏽

Giving each model its own provider component to set up context for their data allows for that separation of concern I mentioned earlier. This is where I can say 'Ok, I'm going to put all HTTP requests that relate to this particular model and its data and set state'. That way when the time comes I know exactly where to look, grab from, and update when working with a particular model and it's data in the backend from the frontend.

"VALUE" PROP

Image description

Notice in the return of our provider components we have what looks like a prop called value. Well, that's because it is a prop and any child of a provider component has access to that prop and its values! Now what exactly are we setting the values to in this prop? Data that can be or needs to be shared with other components. Maybe something equivalent to an array of all movies that the Movie model holds in our backend or even an array of all the reviews that the Review model holds:

Image description

Image description

In the snaps above I'm using a useEffect() to send a fetch to a registered route in the back end that will then be mapped to a controller action to render back JSON of all movies in the Movie model database. Within that fetch, I'm setting the movies into an empty array called 'movies' using state. Great, now I have the data I need for movies to render them to the front end. I might just need that array of movies for other purposes rather than just rendering the title, poster, year, and the cast of the movie. So I want to make context out of this array to possibly share this data with other child components that need it. More on that a bit later. I then pass that data under 'movies' as a prop under 'value=' in the case that I do need this data for child components.

Provider Components have child components say whaaa????

Well, how do we make provider components and their prop data available to other components within our app if technically it has no child components? Well, it doesn't have any children yet because we haven't given it any children. In our earlier example with MoviePage and ReviewCard I was able to share the data that the MoviePage has and pass it onto ReviewCard as a prop because ReviewCard is its child! We imported ReviewCard into the MoviePage component and rendered whatever code is set up in ReviewCard from the MoviePage making ReviewC its child. However, without the data provided by the MoviePage what originally was supposed to render visually to the user would not have been possible.

So if we want all components to have access to a provider component data we need to ensure that the provider components are wrapped around the top-level component of our application. To do so we need to import all provider components we'd like data from into our top-level component of the application and then proceed to wrap:

Image description
Image description

Voila! There you have it, useContext is set and ready to go! No more prop drilling. But how do we use useContext throughout our child components? Let's say I have settings in my application where a user can update their username or user image:

Image description

Sure we can request the backend to update the user info through a fetch and change the user state, but what if we have a movie that has many reviews and these reviews render with an image and the username of the user who left the review?

Image description

We'd have to update the movie's state within that fetch request. This is where the realization of how great useContext is.

useContext IN ACTION

In my UserProvider component, I have fetches set up to update a user username and a user image:

Image description

Image description

I know because a movie has many reviews and these reviews are rendered with an image and the username of the user who left the review I will have to update my movies state. Let's say in theory I had set up the state of movies in the top-level component of my application. I might have to prop-drill the movie data down until I reached the child component I need to get to in order to update a state within this fetch. Because I set up context for my movie data I can easily import useContext and MovieContext into the child component of MovieContext, User_Profile. I also destructed my 'prop' { movies } equal to useContext( MovieContext ). Remember, every provider component holds a 'value' prop :

Image description

I also imported UserContext into my User_Profile component (a child of UserProvider) and destructed my props updateUsername and updateUserImage that hold a value of my fetch POST functions in UserProvider:

Image description

If you look back at my fetch POST functions set up in UserProvider you can see it takes in a movies argument. You can see now because I imported useContext, MovieContext, and destructed my movies prop I'm able to pass in the movie data into my updateUsernameSubmit and updateUserImageSubmit functions for my form onSubmit events:

Image description
Image description

This will allow for an update on the state of movies so when a user updates their username or image not only will you seen an update on the users profile but also an update on a movies reviews. And guess what?! No PROP DRILLING!:

BEFORE UPDATE

Image description

AFTER UPDATE

Image description

RESOURCES

  1. freeCodecamp

  2. React

Top comments (0)