DEV Community

Geraldo dos Santos
Geraldo dos Santos

Posted on

The new magical Redux land.

This post assumes you have worked somewhat with Redux before, so apologies in advance if the syntax/explanations make little sense to you. 🖖

In the beginning...

...back in the age of class components, a fair amount of tutorials out there would always teach React + Redux together, like they were necessarily part of each other.

What often happened is the steep learning curve of those two put together would make people have not so positive feelings towards one or the other (but very likely Redux was the one liked the least).

Knowledge

Dispatch, reducers, action creators, containers...

I mean, looking back, I can understand the feeling very well.

What I experienced:

// Having your container components (the ones that do the Redux magic)  

function mapStateToProps(state) {
  // Pass the global state down as you want it
}

function mapDispatchToProps(dispatch) {
  // Bind your action creators and have your components accessing them
}

export default connect(mapStateToProps, mapDispatchToProps)(MyContainerComponent);
Enter fullscreen mode Exit fullscreen mode

Then wrap all the components under your container component and go to Redux town. Now you are able to update and read the global state in your application.

Oh and don't forget your reducers!

I remember when our team back then was going through all that and often feeling overwhelmed, because of so many layers, so many moving parts. Little did I know what the future would hold.

But I ended up not using redux for nearly two years because of my new job. We didn't need it, so I never heard of it again, until...

Tomorrow

Fast forward 2 years.

Another day, another side project

Recently, during a random talk with a good colleague of mine, he mentioned being enthusiastic about Redux and that sparked my curiosity, so I decided to check it out, and oh boy.

New perspectives

We can build something cool with it.

Let's fetch Magic The Gathering cards.

In order to get started, we need a store.

// src/store/index.js

import { configureStore } from "@reduxjs/toolkit";

const store = configureStore({ reducer: {} });

export default store;
Enter fullscreen mode Exit fullscreen mode

That's it. In their docs you can check how it was before, and the pleasants surprises didn't stop there.

Now we need reducers.

Redux Toolkit has reducer slices, and what are those?

slice reducer: a reducer that is being used to handle updates to one specific slice of the state tree, usually done by passing it to combineReducers

(from their docs)

Basically, it's a more focused reducer, it's easier to split the state changing logic instead of having one gigantic reducer that does a lot.

This is how you create a slice:

// src/features/search/searchSlice.js

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  searchString: ""
};

export const searchSlice = createSlice({
  name: "search",
  initialState,
  reducers: {
    searchCard: (state, action) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.searchString = action.payload;
    }
  }
});

// Action creators are generated for each case reducer function
export const { searchCard } = searchSlice.actions;

export default searchSlice.reducer;

Enter fullscreen mode Exit fullscreen mode

Like it's mentioned in the code and their docs, there's no need for spreading objects over and over again. Redux Toolkit already takes care of the immutability for you, so feel free to assign the value to the state directly. Never again overwriting the state by accident!

Notice how we didn't have to create any actions? createSlice already does it for you, combined with a reducer. But if you want to create those by hand, feel free to check the createReducer on their API.

Cool, our slice is ready. We can finally connect it to the store:

// src/store/index.js

import { configureStore } from "@reduxjs/toolkit";
import searchReducer from "../features/search/searchSlice";

export const store = configureStore({
  reducer: {
    search: searchReducer
  }
});

Enter fullscreen mode Exit fullscreen mode

Now we're ready to use it in our components.

Interacting with our components

Past

Remember connect, mapStateToProps and mapDispatchToProps?

This is them now, feel old?

const search = useSelector((state) => state.search);
const dispatch = useDispatch();
Enter fullscreen mode Exit fullscreen mode

useSelector is how we tap into the global state shared by your store.

And useDispatch allows us to dispatch our actions. But where do we get our actions from?

From the slice! 🤩

import { search } from "./features/search/searchSlice";
Enter fullscreen mode Exit fullscreen mode

Putting it all together:

// src/App.js
import { useDispatch, useSelector } from "react-redux";

import { searchCard } from "./features/search/searchSlice";
import "./styles.css";
import { useCallback, useEffect, useState } from "react";
import { useUpdateEffect } from "react-use";

export default function App() {
  const searchState = useSelector((state) => state.search);
  const [card, setCard] = useState(null);

  const dispatch = useDispatch();

  const handleChange = useCallback(
    (searchString) => {
      if (!searchString) return;

      fetch(
        `https://api.scryfall.com/cards/named?fuzzy=${encodeURI(searchString)}`
      )
        .then((response) => response.json())
        .then((jsonResponse) => setCard(jsonResponse));
    },
    [setCard]
  );

  useUpdateEffect(() => {
    handleChange(searchState.searchString);
  }, [searchState]);

  return (
    <div className="App">
      <h1>
        Would you like a magic card?{" "}
        <span role="img" aria-label="card">
          🃏
        </span>
      </h1>
      <div style={{ marginBottom: "2rem" }}>
        <label htmlFor="card-search">Card select: </label>
        <select
          id="card-seard"
          onChange={(event) => dispatch(searchCard(event.target.value))}
        >
          <option value="">Choose one</option>
          <option value="lightning bolt">Lightning bolt</option>
          <option value="ancestral recall">Ancestral Recall</option>
        </select>
      </div>
      <div>
        {card?.image_uris && (
          <img
            src={card.image_uris.normal}
            alt={card.name}
            style={{ height: "300px" }}
          />
        )}
      </div>
    </div>
  );
}



Enter fullscreen mode Exit fullscreen mode

I'll leave the running sandbox here:

Conclusion

Despite this being a simple example to add Redux on, I wanted to share how much easier it is to get started on it. Something I plan to write about later is their RTK (Redux Toolkit) Query. Adding on this post would make it too convoluted.

I was completely blown away from what it can do and I definitely recommend checking it out.

After playing around with all for a bit, I ended up creating this fun side project, take a look if you're into tabletop RPGs: www.yougotaquest.com

Discussion (0)