Skip to content
Colin Wren
Twitter

Using a React Context With React-konva

JavaScript, Software Development2 min read

The shared state means that the changes in the left hand menu can update the data used to render the screen components in the user journey map
The shared state means that the changes in the left hand menu can update the data used to render the screen components in the user journey map

Following on from my post on using react-konva as part of reciprocal.dev to build a User Journey Map and then adding zoom and panning to said map I decided to add some enhancements to change properties of components rendered on the canvas from outside the canvas.

In order to implement this enhancement I needed to introduce an app-wide state so I could track both the interaction in the non-canvas UI and update the value in the canvas components.

To do this I started by building a context that I could then access, using the useContext hook in the components that needed to access the properties the context exposed to change the app’s state.

I added the context provider at the root component level so child components could access the context and while this worked well with the UI elements I had created to display the screen information, it didn’t work with the components in the canvas.

The components that were children of the Stage used to render canvas elements were unable to access these properties due to scoping issues.

Accessing the React Context within a react-konva Stage

The fix for getting around the scoping issue is relatively simple, it just requires setting up another context provider inside the Stage .

For the new provider to re-use the value from the original provider we need to wrap a consumer around the Stage and make sure that our context provider component can have it’s value set by a prop.

Once we have the provider inside the Stage we can the use the useContext hook within the components that will be rendered to the canvas.

The final structure looks like this:

1import { useState, useContext } from 'react';
2import { Stage, Rect } from 'react-konva';
3import ColourContext, { ColourConsumer, ColourProvider } from './contexts/ColourContext';
4
5function Screen() {
6 const { colour } = useContext(ColourContext);
7 return (
8 <Rect
9 x={0}
10 y={0}
11 width={200}
12 height={200}
13 fill={colour}
14 />
15 )
16}
17
18function Canvas() {
19 return (
20 <ColourConsumer>
21 {(colourValue) => (
22 <Stage>
23 <ColourProvider value={colourValue}>
24 <Screen />
25 </ColourProvider>
26 </Stage>
27 )}
28 </ColourConsumer>
29 )
30}
31
32function App() {
33 const [colour, setColour] = useState('#FF0000');
34 return (
35 <ColourProvider value={{ colour, setColour }}>
36 <Canvas />
37 </ColourProvider>
38 )
39}
The Canvas component relays the context values so that components within the Stage can access them

Using the React Context to update UI elements on state change

With the context accessible from inside the canvas we can use this context to update a shared state between UI elements outside of the canvas and those inside of it.

Firstly we need to have some means of getting and setting a value that will be updated via the non-canvas UI. To do this we use useState to track a value called colour and the getter and setter of this value to the provider via it’s value prop.

This colour value is changed inside of the non-canvas UI we call setColour whenever the user selects a value from the colour select element.

Within the canvas components we take the colour value from the context and use it to set the colour used in the component.

1function Screen() {
2 const { colour } = useContext(ColourContext);
3 return (
4 <Rect
5 x={0}
6 y={0}
7 width={200}
8 height={200}
9 fill={colour}
10 />
11 )
12}
13
14// This requires the context to be relayed within the Konva Stage
15function Canvas() {
16 return (
17 <ColourConsumer>
18 {(colourValue) => (
19 <Stage>
20 <ColourProvider value={colourValue}>
21 <Screen />
22 </ColourProvider>
23 </Stage>
24 )}
25 </ColourConsumer>
26 )
27}
28
29// No need to relay, can just use useContext
30function ColourForm() {
31 const {colour, setColour} = useContext(ColourContext);
32
33 function handleChange(e) {
34 setColour(e.target.value);
35 }
36
37 return (
38 <select value={colour} onChange={handleChange}>
39 <option value='#ff0000'>Red</option>
40 <option value='#00ff00'>Green</option>
41 <option value='#0000ff'>Blue</option>
42 </select>
43 )
44}
45
46function App() {
47 const [colour, setColour] = useState('#ff0000');
48 return (
49 <ColourProvider value={{ colour, setColour }}>
50 <Canvas />
51 <ColourForm />
52 </ColourProvider>
53 )
54}
The context is set at the app level and then relayed in the canvas component that holds the Stage

Once we’ve got the colour value being read by the canvas component and set by the non-canvas component we can now set the colour of the Rect using the select box.

Summary

While this post covers a very simple use case there’s no reason why you couldn’t use a React Context to track the state of a number of values and build an advanced UI that allows the user to update objects within the canvas from outside of it.