I’ve been working with React since ~v0.12 was released. (2014! Wow, where did the time go? It has changed a lot. I remember some “Aha “moments along the way. One thing that has been maintained is the mindset of using it. Instead of working directly with DOM, we think differently.
For me, my learning style is to get something up and running as quickly as possible. Then, whenever necessary, I explore deeper areas and everything in the document. Learn by doing, have fun, and push things forward.
The target
The goal here is to show you enough React to cover those “Aha “moments. I recommend looking at documentation in cases where you want to dig deeper. I’m not going to repeat them.
Please note that you can use theCodePenTo find all the examples, but you can also skip toMy lot repoFind a complete work game in.
The first application
You can boot the React app in various ways. Here’s an example.
import React from 'https://cdn.skypack.dev/react' import { render } from 'https://cdn.skypack.dev/react-dom' const App = () => <h1>{`Time: ${Date.now()}`}</h1> render(<App/>, document.getElementById('app')Copy the code
The starting point
Now that we’ve learned how to make a component, we can roughly determine what we need.
import React, { Fragment } from 'https://cdn.skypack.dev/react' import { render } from 'https://cdn.skypack.dev/react-dom' const Moles = ({ children }) => <div>{children}</div> const Mole = () => <button>Mole</button> const Timer = () => <div>Time: 00:00</div> const Score = () => <div>Score: 0</div> const Game = () => ( <Fragment> <h1>Whac-A-Mole</h1> <button>Start/Stop</button> <Score/> <Timer/> <Moles> <Mole/> <Mole/> <Mole/> <Mole/> <Mole/> </Moles> </Fragment> ) render(<Game/>, document.getElementById('app'))Copy the code
Start/stop
Before we do anything, we need to be able to start and stop the game. Starting the game triggers elements like timers and moles, bringing them to life. This is where we can introduce conditional rendering.
const Game = () => { const [playing, setPlaying] = useState(false) return ( <Fragment> {! playing && <h1>Whac-A-Mole</h1>} <button onClick={() => setPlaying(! playing)}> {playing ? 'Stop' : 'Start'} </button> {playing && ( <Fragment> <Score /> <Timer /> <Moles> <Mole /> <Mole /> <Mole /> <Mole /> <Mole /> </Moles> </Fragment> )} </Fragment> ) }Copy the code
We have a playing state variable that we use to render the elements we need. In JSX, we can use a condition with && and render something if the condition is true. Here we say render the board and its contents if we are playing. This also affects button text, we can use triples.
Open the demo in this link and set the extension to highlight render. Next, you’ll see that the timer will render over time, but when we hit the Mole, everything will be re-rendered.
Loops in JSX
You might think that the way we rendered Mole was inefficient. You are right to think so! Here’s an opportunity to render these things in a loop.
With JJSX, we tend to use array.map to render a collection of things 99% of the time. For example.
const USERS = [
{ id: 1, name: 'Sally' },
{ id: 2, name: 'Jack' },
]
const App = () => (
<ul>
{USERS.map(({ id, name }) => <li key={id}>{name}</li>)}
</ul>
)
Copy the code
Another approach is to generate content in a for loop and then render the return of a function.
return (
<ul>{getLoopContent(DATA)}</ul>
)
Copy the code
What does that key property do? This helps React determine which changes need to be rendered. If you can use a unique identifier, do so as a last resort, using the index of the items in the collection. Read the documentation on lists for more).
For our example, we don’t have any data to work with. If you need to generate a collection of things, here’s a trick you can use.
new Array(NUMBER_OF_THINGS).fill().map()
Copy the code
This may work for you in some situations.
return ( <Fragment> <h1>Whac-A-Mole</h1> <button onClick={() => setPlaying(! playing)}>{playing ? 'Stop' : 'Start'}</button> {playing && <Board> <Score value={score} /> <Timer time={TIME_LIMIT} onEnd={() => console.info('Ended')}/> {new Array(5).fill().map((_, id) => <Mole key={id} onWhack={onWhack} /> )} </Board> } </Fragment> )Copy the code
Or, if you want a persistent collection, you can use something like uUID.
import { v4 as uuid } from 'https://cdn.skypack.dev/uuid'
const MOLE_COLLECTION = new Array(5).fill().map(() => uuid())
// In our JSX
{MOLE_COLLECTION.map((id) =>
)}
Copy the code
End of the game
We can only use the “Start” button to end our game. When we do end it, when we start it again, the score is still there. Our onEnd Timer doesn’t work yet.
We’re going to introduce a third party solution that makes our mole rock up and down. This is an example of how to introduce a third-party solution that works with the DOM. In most cases, we use references to grab DOM elements and then use our solution in an effect.
We will use GreenSock(GSAP) to wobble our moles. We won’t delve into the GSAP apis today, but if you have any questions about what they do, let me know
There is an updated Mole with GSAP.
import gsap from 'https://cdn.skypack.dev/gsap' const Mole = ({ onWhack }) => { const buttonRef = useRef(null) useEffect(() => { gsap.set(buttonRef.current, { yPercent: 100 }) gsap.to(buttonRef.current, { yPercent: 0, yoyo: true, repeat: 1})}, []) return ( <div className="mole-hole"> <button className="mole" ref={buttonRef} onClick={() => onWhack(MOLE_SCORE)}> Mole </button> </div> ) }Copy the code
We added a wrapper to Button that allows us to show/hide the Mole, and we also gave our button a ref. Using an effect, we can create a Tween (GSAP animation) that moves the button up and down.
You’ll also notice that we’re using className, the JSX equivalent of class, for application class names. Why don’t we use classNames in GSAP? Because if we have a lot of elements that have that className, our effect will try to use them all. That’s why useRef is a good choice to stick with.
Please look at pen 8. Moving Molesby@jh3y.
Great, now that we have Mole that wiggles, functionally speaking, our game is done. They all move exactly the same, which is not ideal. They should run at different speeds. The longer the Mole is hit, the less points should be scored.
Our mole’s internal logic can handle how the score and speed are updated. Passing in the initial speed, delay, and points as props makes the component more flexible.
<Mole key={index} onWhack={onWhack} points={MOLE_SCORE} delay={0} speed={2} />
Copy the code
Now, let’s break down our Mole logic.
Let’s start with how our score will decrease over time. This might be a good candidate for a REF. We have something that doesn’t affect rendering, and its value can be lost in a closure. We created our animation in an effect that will never be recreated. In each iteration of our animation, we want to reduce the value of points by a multiple. A point value can have a minimum value, defined by a pointsMin item.
const bobRef = useRef(null)
const pointsRef = useRef(points)
useEffect(() => {
bobRef.current = gsap.to(buttonRef.current, {
yPercent: -100,
duration: speed,
yoyo: true,
repeat: -1,
delay: delay,
repeatDelay: delay,
onRepeat: () => {
pointsRef.current = Math.floor(
Math.max(pointsRef.current * POINTS_MULTIPLIER, pointsMin)
)
},
})
return () => {
bobRef.current.kill()
}
}, [delay, pointsMin, speed])
Copy the code
We will also create a REF to reserve a reference for our GSAP animation. We will use this when the Mole is punched. Note that we also return a function that kills the animation on uninstall. If we don’t kill the animation on uninstall, the duplicate code will continue to fire.
9. Subtract marks, by @jH3Y.
What happens when a mole gets beaten? We need a new state to solve this problem.
const [whacked, setWhacked] = useState(false)
Copy the code
Instead of using the onWhack item in our Button’s onClick, we can create a new function Whack. This sets whacked to true and calls onWhack with the current pointsRef value.
const whack = () => {
setWhacked(true)
onWhack(pointsRef.current)
}
return (
<div className="mole-hole">
<button className="mole" ref={buttonRef} onClick={whack}>
Mole
</button>
</div>
)
Copy the code
The last thing to do is to useEffect to respond to the whacked state of the effect. Using a dependency array, we can ensure that the effect is only run if the WHACKED changes. If whacked is true, we reset the point, pause the animation, and animate the Mole underground. Once underground, we wait for a random delay before restarting the animation. With timescale, the animation starts up faster and we set whacked to false.
useEffect(() => { if (whacked) { pointsRef.current = points bobRef.current.pause() gsap.to(buttonRef.current, {yPercent: 100, duration: 0.1, onComplete: () => { gsap.delayedCall(gsap.utils.random(1, 3), () => { setWhacked(false) bobRef.current .restart() .timeScale(bobRef.current.timeScale() * TIME_MULTIPLIER) }) }, }) } }, [whacked])Copy the code
So that gives us.
See author 10. React to Whacksby@jh3y.
The last thing to do is pass items to our Mole instances, which will make them behave differently. However, how we generate these items can cause a problem.
<div className="moles"> {new Array(MOLES).fill().map((_, Id) => (<Mole key={id} onWhack={onWhack} speed={gsap.utils. Random (0.5, 1)} delay={gsap.utils. Random (0.5, 1)} delay={gsap.utils. 4)} points={MOLE_SCORE} /> ))} </div>Copy the code
This caused a problem because when we generated the mole, the item would change with each render. A better solution would be to generate a new Mole array every time we start the game and iterate over it. This way, we can keep the game random without causing problems.
Const generateMoles = () => new Array(MOLES).fill().map(() => ({speed: gsap.utils. Random (0.5, 1), delay: MOLES: below) Gsap. Utils. Random (0.5, 4), the points: MOLE_SCORE })) // Create state for moles const [moles, setMoles] = useState(generateMoles()) // Update moles on game start const startGame = () => { setScore(0) setMoles(generateMoles()) setPlaying(true) setFinished(false) } // Destructure mole objects as props <div className="moles"> {moles.map(({speed, delay, points}, id) => ( <Mole key={id} onWhack={onWhack} speed={speed} delay={delay} points={points} /> ))} </div>Copy the code
This is the result! I have added some styles and some varieties of moles for our buttons.
See the author’s 11. Whack-a-mole in action, by @jH3Y.
We now have a fully functional whac-a-Mole game built in React. We spent less than 200 lines of code. At this stage, you can take it away and make it your own. Design it the way you like, add new features, etc. Or you can stick with it and we can put something extra together!
Track the highest score
We have a working whack-a-mole, but how do we keep track of the highest scores we achieve? We can use an effect that writes our score to localStorage at the end of each game. But what if the persistence thing is a universal requirement. We can create a custom hook called usePersistentState. This can be a wrapper around useState that reads/writes to localStorage.
const usePersistentState = (key, initialValue) => {
const [state, setState] = useState(
window.localStorage.getItem(key)
? JSON.parse(window.localStorage.getItem(key))
: initialValue
)
useEffect(() => {
window.localStorage.setItem(key, state)
}, [key, state])
return [state, setState]
}
Copy the code
Then we can use it in our game.
const [highScore, setHighScore] = usePersistentState('whac-high-score', 0)
Copy the code
We use it exactly the same as useState. We can also connect to onWhack and set a new high score at the appropriate time during the game.
const endGame = points => {
if (score > highScore) setHighScore(score) // play fanfare!
}
Copy the code
How do we know if our game results in a new high score? Another state? Probably.
See author 12. Tracking high scores by @jH3Y.
The embellishment of whimsy
At this stage, we’ve covered everything we need. Even how to make your own custom hooks. Please feel free to make it your own.
Still hesitating? Let’s create another custom hook to add audio to our game.
const useAudio = (src, volume = 1) => {
const [audio, setAudio] = useState(null)
useEffect(() => {
const AUDIO = new Audio(src)
AUDIO.volume = volume
setAudio(AUDIO)
}, [src])
return {
play: () => audio.play(),
pause: () => audio.pause(),
stop: () => {
audio.pause()
audio.currentTime = 0
},
}
}
Copy the code
This is a rudimentary hook implementation for playing audio. We provide an audio SRC, and then we get an API to play it. We can add noise when we whac the mole. And then the decision is, is this part of Mole? Is it something we passed on to Mole? Is that what we call in onWhack?
These are the types of decisions that arise in component-driven development. We need to consider portability. Also, what happens if we want to mute the audio **? ** How can we do this on a global scale? As a first approach, it might make more sense to control audio within the Game component.
// Inside Game
const { play: playAudio } = useAudio('/audio/some-audio.mp3')
const onWhack = () => {
playAudio()
setScore(score + points)
}
Copy the code
It’s all about design and decision. If we bring in a lot of audio, renaming the Play variable can become tedious. Returning an array from our hook-like useState will allow us to name variables whatever we want. However, it can also be difficult to remember which index of an array represents which API method.
Please look at Pen 13. Squeaky Mole. By @jH3Y.
That’s it!
Enough to get you started on your React journey, and we had to do some fun stuff. We did cover a lot of ground.
- Create an application.
- JSX.
- Components and props.
- Example Creating a timer.
- Use references.
- Create custom hooks.
We played a game! Now you can use your new skills to add new features or make it your own.
Where did I take it? At the time of writing this article, so far at this stage.
See @jH3Y’s Penwhac-a-Mole W/React &&GSAP.
What’s next!
I hope building Whac-a-Mole inspires you to start your React journey. What’s next? If you want to dig deeper, here are links to some resources, some of which I’ve found useful along the way.
- The React document
- “Making
setInterval
Declarative With React Hooks,” Dan Abramov - “How to get data using React Hooks,” Robin Wieruch
- “When to use useMemo and useCallback,” Kent C Dodds
- Read more React articles at Smashing Magazine