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 | |
---|---|
Point
was taken fromSketch.tsx
and exported, so all the frontend has access to it.
frontend/src/types/line-data.ts | |
---|---|
Line
was taken fromSketch.tsx
and exported, so all the frontend has access to it.
frontend/src/types/lines-data.ts | |
---|---|
LinesData
was taken fromSketch.tsx
and 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
lines
of 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_POINT
andADD_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
State
that contains thelines
and aDispatch
function. It's initialized withundefined
. - It's a method used to access the
Context
from 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()
fromSketch
were put here, in a reducer. This reducer definesAction
to modify theState
- The children will be wrapped by your Context Provider.
- The
useReducer
function 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
useLines
function. - Importation of the
State
andDispatch
, and renaming to better understanding. - Dispatch to the
LineProvider
to add the first point in a line. - Dispatch to the
LineProvider
to 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.