In this article, I will create an HTML Canvas drawing site using React Hooks. I will build the project from scratch using create-React-app scaffolding. Finally, the application has basic functions such as clear, undo, and use localStorage.
In this article I’ll show you any building custom Hooks and reusing stateful logic in plain old Hooks.
Basic setup
We’ll start by creating a new React application using create-react-app.
$ npx create-react-app canvas-and-hooks
$ cd canvas-and-hooks/
$ yarn start
Copy the code
Your browser will open http://localhost:3000/ and you’ll see a rotated React logo image, so you’re ready to start…
The first hook: useRef
Open the SRC/app.js file 📃 with your favorite editor and replace it with the following:
In general, you don’t need a REF to do updates in React. But canvas is not like other DOM elements. Most DOM elements have an attribute, such as: value, that you can update directly. In canvas allow ✅ you use context (Ben 🌰 : CTX) to draw something. To do this, we have to use ref, which is a reference to the actual Canvas DOM element.
Now that we have the Canvas context, it’s time to draw something. To do this, paste and copy the following code to draw an SVG hook. It has nothing to do with hooks, and don’t worry about 😓 if you don’t understand it.
So, we added state to our app. You can verify this by adding console.log(locations) to the return statement. As the user clicks, you see the printed array.
The third hook: useEffect
Currently, we have nothing to do with state. We drew hooks as before. Let’s look at how to fix this problem with useEffect hook.
There’s a lot of stuff going on here and let’s break it down a little bit. We move the onClick event handler’s draw function back into useEffect. This is important because drawing on the canvas is determined by the state of the app, which is a side effect. We’ll use localStorage later to maintain persistence, which can also be a side effect when state is updated.
I also made some changes to the actual drawing of the canvas itself. In the current implementation, the canvas is cleared every time the render is rendered and then all the positions are drawn. We can do a little smarter than that. But to keep it simple, leave it to the reader to optimize.
Now that we’ve done all the hard work, it should be easy to add new features. Let’s create a clear button.
Because any state update in React must be immutable, we can’t use things like locations.pop() to clear the most recent entry in the array. Our operation cannot change the original Locations array. The method is to use slice, copying all items until the last one. You can use locations. Slice (0, locations. Length-1), but slice has a smarter way of manipulating the -1 at the last bit of the array.
Before we start, let’s tidy up the HTML and add a CSS style file. Add the following div outside the buttons.
Now we have all the functionality we need to build, but not enough. One of the coolest things about Books is that you can use existing hooks to build new custom hooks. I create a custom usePersistentState hook to demonstrate this.
Here, we create our first custom hook and extract all the logic associated with saving and retrieving state from localStorage from the App component. The way we do this is that the usePersistentState Hook can be reused by other components. There is nothing specific to this component.
Let’s repeat this trick to manipulate canvas-related logic.
Second custom hook: usePersistentCanvas
import React from 'react'
import './App.css'/ /... drawfunction
// our first custom hook
function usePersistentState(init) {
const [value, setValue] = React.useState(
JSON.parse(localStorage.getItem('draw-app')) || init
)
React.useEffect(() => {
localStorage.setItem('draw-app', JSON.stringify(value))
})
return [value, setValue]
}
// our second custom hook: a composition of the first custom hook // and React's useEffect + useRef function usePersistentCanvas() { const [locations, setLocations] = usePersistentState([]) const canvasRef = React.useRef(null) React.useEffect(() => { const canvas = canvasRef.current const ctx = canvas.getContext('2d') ctx.clearRect(0, 0, window.innerWidth, window.innerHeight) locations.forEach(location => draw(ctx, location)) }) return [locations, setLocations, canvasRef] } function App() { const [locations, setLocations, canvasRef] = usePersistentCanvas() function handleCanvasClick(e) { const newLocation = { x: e.clientX, y: e.clientY } setLocations([...locations, newLocation]) } function handleClear() { setLocations([]) } function handleUndo() { setLocations(locations.slice(0, -1)) } return ( <>
) } export default AppCopy the code
As you can see, our App components became very small. All logic related to storing state in localStorage and drawing on canvas is extracted to custom hooks. You can further clean this file by moving hooks into the hooks file. This allows other components to reuse this logic, for example to make better hooks.
conclusion
If you compare hooks to life cycle methods such as componentDidMount, componentDidUpdate, what makes hooks so special? Take a look at the example above:
Hooks allow you to reuse lifecycle hook logic in different components
You can synthesize hooks to build richer custom hooks, just as you can synthesize richer UI components.
Hooks are smaller and more concise, no longer bloated, and the lifecycle methods are sometimes confusing.
It’s too early to tell if hooks really want to fix all of these issues – and what new bad practices might emerge – but look above I’m very excited and optimistic about the future of React!