Using React Hooks to make an RPG Shop
Tags: react, javascript
Published: 2021-02-21 19:51:25

Hooks allow you to use state in functional components and avoid the need for class components. They also add a lot of convenience to working with state. I wanted to quickly get the gist of how to use them, so I made a little RPG store/cart app. The shop contains a few items (classic Zelda stuff) and displays their names and prices. The cart displays the item name, quantity, and total item cost (item price x quantity) as well as a cart total. Items can be added and removed from the cart and the cart total will adjust accordingly.

# First, an Intro to useState, useEffect, and useReducer
In this first part I'll explain how `useState`, `useEffect`, and `useReducer` work, and the second part will be how I went about my actual project.

## useState
`useState` returns 2 elements: the current state and a function to update the state. When initializing state, you create a variable and set it equal to `useState`, which is passed the values you want to keep track of.

<figure>
<img src="https://github.com/robotspacefish/blog-photos/blob/master/react-hooks/useState-elements.png?raw=true" />
</figure>

`const state = useState({ username: '', email: '' })` stores an object containing `username` and `email` properties. You can choose any name that fits; it doesn't have to be `state`.

In order to retrieve the values, you have to target the first element of the state variable you created: `state[0]`, and to update the state you target the second element, which is the function to set the state: `state[1]`. You can see in the example below how the `input` elements are using the values from state, and `handleChange` returns `state[1]` which is setting the new value for whichever input is being updated.

```javascript
import React, { useState } from 'react'; // <- destructure useState to use it in your functional component

function App() {
  const state = useState({ username: '', email: '' });

  const handleChange = e => {
    const { value, name } = e.target;

    return state[1](prevState => ({
      ...prevState, // spread first before setting new values or they will be overwritten
      [name]: value
    }));

  }

  return (
    <div className="App">
      <form>
        <label>Username:</label>
        <input type="text" name="username" value={state[0].username} onChange={handleChange} />
        <br />
        <label>Email:</label>
        <input type="email" name="email" value={state[0].email} onChange={handleChange} />
      </form>
    </div>
  );
}
```

Since all your state is in a single object you have to remember to spread the previous state into the new object or it will be overwritten, just like you see in `handleChange`.

Having to use `state[0]` and `state[1]` seems like a recipe for disaster. You could store them in new, more descriptive variables instead:

```javascript
const state = useState({ username: '', email: '' });
const stateValues = state[0];
const setStateValues = state[1];
```

However, I don't know if this is bad practice or not, I haven't seen it. I usually see the following method used instead, where `useState` is destructured:

```javascript
import React, { useState } from 'react';

function App() {
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');

  return (
    <div className="App">
      <form>
        <label>Username:</label>
        <input type="text" name="username" value={username} onChange={e => setUsername(e.target.value)} />
        <br />
        <label>Email:</label>
        <input type="email" name="email" value={email} onChange={e => setEmail(e.target.value)} />
      </form>
    </div>
  );
}
```

This seems nicer because nothing is tied together and you don't have to worry about maintaining the previous state to avoid overwriting other properties. The downside to this method is that your properties aren't named in the React debugger.

If you set everything as a single object you can see the names of each property:

<figure>
  <img src="https://github.com/robotspacefish/blog-photos/blob/master/react-hooks/useState-one-state1.png?raw=true" >
</figure>

If you separate each bit of state, it's harder to debug because you can't tell what value belongs to which property:

<figure>
  <img src="https://github.com/robotspacefish/blog-photos/blob/master/react-hooks/useState-separate-state1.png?raw=true" >
</figure>

## useEffect
`useEffect` is a hook to manage side effects and can be used similar to `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount`. It executes after every component render cycle. It takes 2 arguments: The first is a function in which you put whatever you want it to do, and the second (optional) argument is an array that contains dependencies which trigger a re-render if they are changed. If you don't include a second argument, the `useEffect` will trigger after every re-render. If you specify an empty array `[]` it will run once, like `componentDidMount`.

In this example I'm fetching the first 20 Pokémon from PokéApi when the app first loads. It will only run once because the `useEffect` has no dependencies.

```javascript
import React, { useState, useEffect } from 'react'; // <- import useEffect

function App() {

  const [pokemon, setPokemon] = useState([]); // <- initialize to empty array

  // this useEffect runs when the app first loads
  useEffect(() => {
    fetch('https://pokeapi.co/api/v2/pokemon?limit=20&offset=0')
      .then(res => res.json())
      .then(data => {
        setPokemon(data.results);
      })
}, []); // <- empty array means don't run this again

  // other code here...
}
```

Below is an example of triggering the `useEffect` whenever the `url` variable changes. Here I'm setting it to the `nextUrl`, which is a value from the PokéApi that's used to get the next set of Pokémon.

```javascript
  const [pokemon, setPokemon] = useState([]); // <- initialize to empty array
  const [url, setUrl] = useState('https://pokeapi.co/api/v2/pokemon?limit=20&offset=0');
  const [nextUrl, setNextUrl] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setPokemon(data.results);
        setNextUrl(data.next);
      })
  }, [url]); // <- trigger whenever the url changes
```

## useReducer

`useReducer` is an alternative to `useState`; it provides a convenient way to handle updating state without worrying about unexpected changes being made. It's useful for when you're mainting more complex state or if you have a lot of nested components to pass props through.

`useReducer` takes a function, called a reducer, and an initial value. It returns the current state and a dispatch method.

The reducer is a function that's responsible for making changes to the state. Its parameters are the current state and an action. The action is an object with conventionally named type and payload properties. The type is used in a switch statement to make the appropriate changes, and the payload is a value needed to make the changes.

In order to make changes, you dispatch the action and payload to the reducer, using the dispatch method.

Below is an example of a Todo List.

At the top is an object called `ACTIONS`, which just helps so you don't have to constantly write your actions as strings that you might type incorrectly. Plus, if you need to change something you can do it once at the top without breaking the rest of the app.

Underneath is the `reducer` function. `todos` is the current state of the todos. You can see in the `App` function where the todos state is set up: `const [todos, dispatch] = useReducer(reducer, [])`. `reducer` is the function to dispatch and `[]` is what `todos` is initialized to.

In the `switch` statement you can see that if `action.type` is equal to `ACTION.ADD` then a new todo will be created and a new array will be returned containing the previous state of the todos along with the new todo. Each `case` returns state in whatever way you want to alter it for that action. The `default` returns the state as is.

If you look down in the `return` statement of `App`, you can see where all the todos are being displayed, the `dispatch` method is being passed to each `todo`. This is convenient because you don't have to pass a bunch of different methods down to the `Todo` component; you can just pass `dispatch` and pass whatever necessary `type` and `payload` you need and the `reducer` will take care of the rest.

```javascript
// ===== App.js =====
import React, { useState, useReducer } from 'react';
import Todo from './Todo';

export const ACTIONS = {
  ADD: 'add',
  TOGGLE: 'toggle',
  DELETE: 'delete',
}

function reducer(todos, action) {
  switch (action.type) {
    case ACTIONS.ADD:
      return [...todos, newTodo(action.payload.task)]
    case ACTIONS.TOGGLE:
      return todos.map(t => (
        t.id === action.payload.id ?
          { ...t, complete: !t.complete } : t
      ));
    case ACTIONS.DELETE:
      return todos.filter(t => (t.id !== action.payload.id));
    default:
      return todos;
  }
}

function newTodo(task) {
  return { id: Date.now(), complete: false, task }
}

function App() {
  const [todos, dispatch] = useReducer(reducer, []);
  const [task, setTask] = useState('');

  function handleSubmit(e) {
    e.preventDefault();
    dispatch({ type: ACTIONS.ADD, payload: { task } });
    setTask('');
  }

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <input type="text" value={task} onChange={e => setTask(e.target.value)} />
      </form>

      <h1>Things To Do:</h1>
      <ul>
        {
          todos.length > 0 ?
            todos.map(t => <Todo key={t.id} todo={t} dispatch={dispatch} />)
            :
            "All done with tasks!"
        }
      </ul>
    </div>
  );
}

export default App;
```


Here in the `Todo` component, you can see that the Toggle and Delete buttons each have an `onClick` that runs the `dispatch` method, and each passes the appropriate `action` as `type`.

```javascript
// ===== Todo.js =====
import React from 'react'
import { ACTIONS } from './App';

export default function Todo({ todo, dispatch }) {
  return (
    <li>
      {todo.task}

      <button onClick={() => dispatch({ type: ACTIONS.TOGGLE, payload: { id: todo.id } })}>Toggle</button>

      <button onClick={() => dispatch({ type: ACTIONS.DELETE, payload: { id: todo.id } })}>Delete</button>
    </li>
  )
}
```


There's more you can do with hooks, including building your own. I encourage you to check out the official docs and the resources below to learn more.

---
### Futher Reading/Viewing / References
* [Using the State Hook](https://reactjs.org/docs/hooks-state.html)
* [Using the Effect Hook](https://reactjs.org/docs/hooks-effect.html)
* [useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer)
* [Learn useReducer in 20 Minutes](https://www.youtube.com/watch?v=kK_Wqx3RnHk) - Video
* [React.js Hooks Crash Course](https://www.youtube.com/watch?v=-MlNBTSg_Ww&t=74s) - Video