Chapter 3 - Create a React Context to store the lines¶
As the application gain in complexity, you will need to add some modularity in to become more maintainable. This chapter of the tutorial will show an example of how you can use the React Context feature.
In this chapter, you will take the state you did in the last chapter and put it in dedicated component. This other component will be a Provider, that will provide for all its children component access to its Context, containing all the drawing data state.
You will also create a dedicated folder for the types used in this drawing application to make full usage of TypeScript.
Info
All the following steps will be executed in the frontend directory.
In a new terminal, you can switch to the frontend directory with the following command.
| In a terminal, execute the following command(s). | |
|---|---|
Steps¶
Moves the types to their own files¶
To keep your application clean, you'll move the types you created in the previous chapter in a dedicated types directory.
Create all the required types.
| frontend/src/types/point.ts | |
|---|---|
Pointwas taken fromSketch.tsxand exported, so all the frontend has access to it.
| frontend/src/types/line-data.ts | |
|---|---|
Linewas taken fromSketch.tsxand exported, so all the frontend has access to it.
| frontend/src/types/lines-data.ts | |
|---|---|
LinesDatawas taken fromSketch.tsxand exported, so all the frontend has access to it.
Update the Sketch component to use the types.
Create the Line Provider¶
A provider is a React component that can give access to its children to some properties. The children can then use the provider and gain access to its state, allowing to make changes that are shared with all other components having access to this context.
In this application, the provider should keep the state of the drawing data for the sketch. What do the Sketch component expects from it?
- Access to the
linesof the drawings - A way to modify it by starting a new line or adding a point to an existing line
For that the LineProvider will provide:
- A state (
State) containing the lines (lines) - A dispatch function (
Dispatch) to update the state; - Two actions (
ADD_FIRST_POINTandADD_POINT) modifying theState;
Create the line provider.
- Import of the shared types.
- Import of the shared types.
- Define labels for the available actions.
- An action is defined by a
type(which action to perform on the state) and apoint(data passed to the action). - The dispatch function asks the state to perform the action on itself.
- The state type.
- Creation of the context. It has a
Statethat contains thelinesand aDispatchfunction. It's initialized withundefined. - It's a method used to access the
Contextfrom all the children of theProvider. You could only useuseContext()but you see here a proper way that test if it's a children that ask for theContext. - The function
addFirstPoint()andaddPoint()fromSketchwere put here, in a reducer. This reducer definesActionto modify theState - The children will be wrapped by your Context Provider.
- The
useReducerfunction creates and initialize the reducer. - Pass the current state and the dispatch function to the provider you created.
Update the main page¶
The LineProvider component can now wraps all components that need to have access to the lines of the drawing.
The LineProvider wraps the Sketch component so it will adopt it as one of its children and provides it with a Context to give it access to the lines.
| frontend/src/pages/index.tsx | |
|---|---|
Update the sketch¶
The sketch can now have access to the context of the LineProvider.
Update the sketch to use the provider.
- Import the
useLinesfunction. - Importation of the
StateandDispatch, and renaming to better understanding. - Dispatch to the
LineProviderto add the first point in a line. - Dispatch to the
LineProviderto add a point in a line.
Check the results¶
Start the application and access http://localhost:3000. You should see the exact same result as earlier.
To stop your Next.js application, press Ctrl+C in your terminal.
Summary¶
Congrats! The big improvement is the React Context. You now have a shared context to use and manipulated the lines. This will allow other components to add points to the drawing in future chapters.
Go further: a note on State, Immutability and Pure functions¶
Introduction¶
You may have asked yourself: why the methods used to modify the lines are so complicated? (We're not going to ask Avril Lavigne)
One can easy think that the following code...
...could be done like this:
However, this is not recommended in React. You have to keep the state immutable.
Definition¶
As the Wikipedia's page of Immutable object mentions:
Quote
In object-oriented and functional programming, an immutable object (unchangeable object) is an object whose state cannot be modified after it is created. This is in contrast to a mutable object (changeable object), which can be modified after it is created.
In React, state should be immutable. The actions needs to modify the state as immutable objects. A function that does this is called a "pure function".
It means that you cannot modify the State inside a reducer directly, you have to modify a copy of it, and then give a new version back to the reducer.
Methods like push, unshift and splice are mutative so you can't use it when applying updates to your State.
See this article for more details.
Also, you need to know that in development mode, React always sends the dispatch() twice. Don't put mutative code in your reducer, it will not work accordingly. More on what here: React Strict Mode.
What are the alternatives¶
Well, state manipulations are not that easy, and it can be worth using something that help us don't make mistakes.
You can use Immer to handle the immutability for you.
Demonstration¶
Install Immer.
| In a terminal, execute the following command(s). | |
|---|---|
You can modify line-provider.tsx as follow.
With the produce() method, no need to worry about immutability, the changes on draft will be applied to the State, and manages everything about immutability with the help of Immer.