The course is part of this learning path
This module looks at how to manage state in React. You’ll be looking at state management, context, and reducers.
The objectives of this module are to provide you with an understanding of:
- State Management
- How to create and provide context
- How to provide dispatch
This learning path is aimed at all who wish to learn how to use the ReactJS framework.
We welcome all feedback and suggestions - please contact us at firstname.lastname@example.org to let us know what you think.
We're now going to look at how we can consume context in components. To consume the context, we need to create our own hook. This will be done in the same place that we created the context. The purpose of it will be to return the context value that's created when React's useContext hook returns it. It needs to be done here, so we can have access to the context and the Provider. We'll give our hook its own name. Usually, use followed by the context name. As you can see, a hook is essentially a function that returns something. React's useContext hook accepts a context object and returns a value for the current context. It gets the value from the Provider. We then return this context value from our hook and export the hook for a use in any component. Any child of a Provider component can use the context. The value it's provided with is decided by the value prop of the nearest context Provider above the calling component in the tree. If the Provider updates, the useContext hook will trigger a re-render of the component tree that consumes the value. We've now updated the CoursesProvider to have a useCoursesContext hook. This sets context to be the result of calling the useContext hook with the CoursesContext. Just to be sure it's used correctly, we check to see if context is undefined. If it is, it means there was no Provider for the component trying to consume the context. If this happens, we throw an error. The function returns the context, which is essentially the value defined in the Provider component, and, in our case, that's an object with a key of course and an array of course objects as its value. The function is exported for use in any component that wants to access this context. Remember, we should get an error if we forget to provide the CoursesContext further up the tree. We deconstruct the object returned by context to get the courses array, that's on line 16. Getting the value here means that we've been able to remove the need for a courses state in the component. Don't forget that if the value in the context updates, the Provider will re-render and, as the FilterableCoursesTable is a child of that Provider, it will re-render with the new value. You should also notice that all of the logic to make network calls for the courses data has been removed from this component. We'll use some of that logic when we deal with making the call in state management code later. The component still has the internal state we decided on way back in part three and four of Thinking in React. The searchText and the advanced values are only related to this part of the app, so no need for any context here. To deal with errors in the courses array, we've slightly modified the way in which this is done. We'll make the courses array contain a single element that is an object that contains a status key and a message. We can do that when we handle the data coming in, so we have no dependency on any other system for that. We've declared our error array to render an error component on line 18, and we populate this in the if statement on line 20, checking to see if the first element in the courses array is an object with a key of status. We then push the relevant components to the error array depending on its value and render it when needed. There's a little change in the conditional rendering too. We're showing the new code on the left and the old code on the right to help you compare it. We've replaced the errors && errors on line 74 of the older version of the FilterableCoursesTable code with a check on line 49 of the new code to see if the errors array has a length greater than zero. This ensures that the error component will render if there is one. The ugly Array.isArray expression on line 75 of the old code is removed and replaced on line 50 of the new code with a check for courses array to have length and the errors array not to have a length in order to render the courses table. To render the loading message, line 57 of the new code, we check to see if errors and courses array have no length, and we've added to check to the rendering of the Route to display a course in the newer component to remove a warning we would get if no courses were present in the context. If you're wondering why there's such a big difference in the number of lines, 23 to be exact, it's because the new version of our component uses context. That means we can remove all of the code that deals with making asynchronous calls for the data, the getCourses function, and the useEffect hook that assured a re-render when the courses updated. We've also removed two states from the component, courses and errors, and replaced them with the variables that get their values because of the context. You can see where code has been removed and replaced here. Our new component now only takes data and displays it using some conditionals to ensure the correct view. The removal of the asynchronous logic makes this component more reusable. We could make it more generic, just to create a filterable table of any data, with minimal changes to suit different data. We've been able to do this through context, although we could have done this for the example just but lifting state to the app component. However, this abstract example is a powerful demonstration of the potential for context in large complex apps.