Using React Hooks to Make an RPG Shop - Part 2
Tags: react, javascript
Published: 2021-02-24 17:00:00

Now that I've touched on how some hooks work in the previous post I'll explain my actual project. I had it ready to go before writing that post, but after I wrote it I realized I wasn't even taking full advantage of the hooks I was using. I guess there really is something to writing these posts that helps to develop a better understanding of these technologies afterall. 😆

<figure>
  <img src="https://github.com/robotspacefish/blog-photos/blob/master/rpg-store/4z0d2s.gif?raw=true" >
</figure>

As I was refactoring the project I somehow completely broke it. I would add an item to the cart and it would work fine, but if I added another of the same type it would add 2 more instead of 1. After some googling I determined the issue to be with `<React.StrictMode>` which is wrapped around `<App />` in `index.js`.

The purpose of `StrictMode` is to highlight potential problems and detect unexpected side effects. It works in development mode only, and causes your components to render twice. When I remove `StrictMode` from my app it works as intended so clearly this is the culprit. I'm still unsure of *why* I'm getting the unintended side effect of it adding a quantity of 2 to the item the second time, yet not the first time. I'm going to have to continue debugging this, but in the meantime I removed `StrictMode` and it works. 😅

## App Organization

<figure>
  <img src="https://github.com/robotspacefish/blog-photos/blob/master/rpg-store/file-organization.png?raw=true">
</figure>

In the `src` folder I have all of my components separated in their own folders inside a `components` folder. Each folder contains a `.js` and `.css` file for the corresponding component, as seen in the `Store` folder in the image above. In the `reducers` folder, there are files for each `useReducer` in my app. I'm using two: One handles adding, updating, and removing items from the cart, and the other handles opening and closing the modal as well as keeping track of the item that was clicked. The `helpers` folder contains a file called `constants`, which holds the `const` objects I'm using, and `cartHelpers` holds the logic for doing all the cart editing and doing the math for the cart total.

## How it Works

I decided not use `App.js` for my main logic because I have a `footer` on the page, so `App` just looks like this:

```javascript
const App = () => (
  <div className="App">
    <Store />
    <Footer />
  </div>
);
```

`Store.js` is where my main logic is. In hindsight this name might be confusing, because the word 'store' is associated with reducers as a container for state, but in this instance it's my item shop. I guess I should've just called it Shop. 🤦🏻‍♀️ I might go back and change that...

`Store` holds the two reducers mentioned earlier:

```javascript
const [cart, dispatchCart] = useReducer(cartReducer, []);

const [itemClicked, dispatchItemClicked] = useReducer(itemClickedReducer, { isModalVisible: false, modalType: null, item: null });
```

`cart` is initialized to an empty array, and `itemClicked` is initialized as an object with a few properties: `isModalVisible` controls when the add/remove item modal is displayed, `modalType` controls whether it's for adding or removing an item, and `item` stores the item object when an item in clicked in either `Inventory` or `Cart`.

I considered separating the modal stuff from the item clicked, but the modal needs to know about the item to display its info, and when the form in the modal is submitted that's when `dispatchCart` runs to either add or remove that item, so to me it makes sense to keep them grouped together.

There are a few functions inside `Store`:

`handleSubmitItem` is passed to `HowManyModal` (the modal with the form to add x amount of an item to the cart) and receives `qty` once the modal form is submitted. Since `handleSubmitItem` is inside `Store` it knows about the `itemClicked`. It checks if the `modalType` is `MODAL.ADD` or `MODAL.REMOVE` and sets a `const fn` to the appropriate function. `fn` is run with the item and quantity.

`MODAL.ADD` and `MODAL.REMOVE` are just constants to make it easier to read and safer than writing strings that might be typed incorrectly. My actions to send to the dispatcher are also stored as constants.

```javascript
// constants.js
export const ACTIONS = {
  SET: 'set',
  CLEAR: 'clear',
  ADD_TO_CART: 'add-to-cart',
  REMOVE_FROM_CART: 'remove-from-cart',
  UPDATE_QUANTITY: 'update-quantity'
}

export const MODAL = {
  ADD: 'add',
  REMOVE: 'remove'
}
```

```javascript
// Store.js
const Store = () => {
  // reducers, other functions...

  const handleSubmitItem = (qty) => {
    const fn = itemClicked.modalType === MODAL.ADD ?
      handleAddToCart : handleRemoveFromCart;

    fn(itemClicked.item, qty);
  };

  // ... etc
}
```

If adding, `handleAddToCart` is the function that's run. It checks to see if the item already exists in the cart. If so, `dispatchCart` is run with the `type` `ACTIONS.UPDATE_QUANTITY`, otherwise it's run with `type` `ACTIONS.ADD_TO_CART`.

```javascript
// Store.js
const Store = () => {
  // reducers, other functions...

  const handleAddToCart = (item, qty) => {
    const itemExists = cart.find(i => i.name === item);

    const type = itemExists ? ACTIONS.UPDATE_QUANTITY : ACTIONS.ADD_TO_CART;

    dispatchCart({ payload: { item, qty }, type });
  }

  // ... etc
}
```

If removing, a similar thing happens in `handleRemoveFromCart`. If the item's `quantity` property is equal to `qty`, `dispatchCart` is run with `type` `ACTIONS.REMOVE_FROM_CART`, otherwise it's run with `type` `ACTIONS.UPDATE_QUANTITY` and the `qty` property in the `payload` is set to `-qty` so that the `updateQuantity` function will add the negative amount to the item's quantity, which actually subtracts it.

```javascript
// Store.js
const Store = () => {
  // reducers, other functions...

  const handleRemoveFromCart = (item, qty) => {
    const removeAll = item.quantity === qty;

    removeAll ?
      dispatchCart({ type: ACTIONS.REMOVE_FROM_CART, payload: { item } })
      :
      dispatchCart({ type: ACTIONS.UPDATE_QUANTITY, payload: { qty: -qty, item } });
  }

  // ... etc
}
```

```javascript
// cartHelpers.js

export const updateQuantity = (cart, item, quantity) => (
  cart.map(i => (
    i.name === item.name ?
      { ...i, quantity: i.quantity += quantity } : i
  ))
);
```

The `HowManyModal` component is the modal that pops up when an item is clicked. It uses the `useState` hook to keep track of the item quantity the user wants to add or remove.

```javascript
const [howMany, setHowMany] = useState(1);
```

A form with a number input has a value set to `howMany`. `howMany` is initialized as 1 so that a quantity of 1 is first displayed in the modal, and the user can adjust from there.

<figure>
  <img src="https://github.com/robotspacefish/blog-photos/blob/master/rpg-store/add-to-inventory.png?raw=true" alt="add to cart from inventory">
  <figcaption>Add to Cart from Inventory</figcaption>
</figure>

If the `modalType` is `MODAL.REMOVE` the max number that can be input is the max amount the user has of that item in their cart, otherwise it maxes out at 99.

```javascript
<input
  type="number"
  id="how-many"
  min="1"
  max={`${modalType === MODAL.REMOVE ? itemClicked.quantity : 99}`}
  value={howMany}
  onChange={handleOnChange}
/>
```

<figure>
  <img src="https://github.com/robotspacefish/blog-photos/blob/master/rpg-store/remove-from-cart.png?raw=true" alt="remove item from cart">
  <figcaption>Remove Item from Cart</figcaption>
</figure>

As mentioned previously, when the "Add to Cart"/"Remove from Cart" button is clicked, `handleSubmitItem` runs and dispatches the appropriate reducer based on the modal type. Another function runs next: `clearItemClicked` which dispatches `dispatchItemClicked` with the `type` `ACTIONS.CLEAR`. This just sets `isModalVisible` back to false and `modalType` and `item` to `null`. Alternatively I could have just pass the dispatch function directly to the modal instead of passing `clearItemClicked` down, but I think I did it this way when I was considering separating the `itemClicked` from the modal stuff.

That's pretty much the bulk of how it works. The rest of the functions are presentational and broken down to display the items in their containers.

The code can be viewed on [github](https://github.com/robotspacefish/rpg-store-shopping-cart) if you'd like to check it out.

Try the demo [here](https://robotspacefish.dev/rpg-store-shopping-cart/)

---

### Further Reading / References
* [Strict Mode](https://reactjs.org/docs/strict-mode.html)