Categories
Redux

Using the Redux Thunk to Dispatch Async Actions with React

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to use React Redux and Redux Thunk to dispatch async actions in a React app.

Installation

Redux Thunk is available with the redux-thunk package. To install it, we run:

npm install redux-thunk

Usage

We can use it by using the applyMiddleware function to apply the thunk middleware from redux-thunk .

For example, we can use it as follows:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";

function joke(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE":
      return { ...action.joke };
    default:
      return state;
  }
}

const store = createStore(joke, applyMiddleware(thunk));

const fetchJoke = () => {
  return async dispatch => {
    const response = await fetch("https://api.icndb.com/jokes/random/");
    const joke = await response.json();
    console.log(joke);
    dispatch({ type: "SET_JOKE", joke });
  };
};

function App() {
  const joke = useSelector(state => state.value && state.value.joke);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchJoke());
  }, []);
  return <div className="App">{joke}</div>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

In the code above, we have the fetchJoke function to dispatch an action into our store to get the joke and put the value in the store.

To dispatch async actions into our store, we have to apply the thunk middleware by writing:

const store = createStore(joke, applyMiddleware(thunk));

to apply the middleware.

Then in App , we call dispatch with the function returned from the fetchJoke passed inside. fetchJoke is an async action, which is also known as a thunk.

This will let us set the value from the API to the store properly.

Composing Actions

We can call another action within an action. Therefore, we can compose them by calling another action that we defined as follows:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from "redux-thunk";

function joke(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE":
      return { ...action.joke };
    default:
      return state;
  }
}

function jokeById(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE_BY_ID":
      return { ...action.joke };
    default:
      return state;
  }
}

const reducers = combineReducers({
  joke,
  jokeById
});
const store = createStore(reducers, applyMiddleware(thunk));

const fetchJokeById = id => {
  return async dispatch => {
    const response = await fetch(`[https://api.icndb.com/jokes/${id](https://api.icndb.com/jokes/$%7Bid)}
    `);
    const joke = await response.json();
    dispatch({ type: "SET_JOKE_BY_ID", joke });
  };
};

const fetchJoke = () => {
  return async dispatch => {
    const response = await fetch("https://api.icndb.com/jokes/random/");
    const joke = await response.json();
    dispatch({ type: "SET_JOKE", joke });
    dispatch(fetchJokeById(1));
  };
};

function App() {
  const joke = useSelector(state => state.joke.value && state.joke.value.joke);
  const jokeById = useSelector(
    state => state.jokeById.value && state.jokeById.value.joke
  );
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchJoke());
  }, []);
  return (
    <div className="App">
      <p>{joke}</p>
      <p>{jokeById}</p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

In the code above, we have 2 reducers.

In App, we called fetchJoke by dispatching the function returned by fetchJoke to the store in the useEffect callback. In fetchJoke , we ran:

dispatch({ type: "SET_JOKE", joke });

to set the joke in the joke reducer and then also called:

dispatch(fetchJokeById(1));

which calls the action function returned by fetchJokeById . This populates the joke in the jokeById store.

Photo by Lauren Fleischmann on Unsplash

Injecting a Custom Argument

Since Redux Thunk 2.1.0, we can use the withExtraArgument function to inject extra arguments to the function that we return in the thunk.

For example, we can use that method as follows:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
function joke(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE":
      return { ...action.joke };
    default:
      return state;
  }
}

const api = "https://api.icndb.com/jokes/random/";
const store = createStore(joke, applyMiddleware(thunk.withExtraArgument(api)));
const fetchJoke = () => {
  return async (dispatch, getState, api) => {
    const response = await fetch(api);
    const joke = await response.json();
    console.log(joke);
    dispatch({ type: "SET_JOKE", joke });
  };
};

function App() {
  const joke = useSelector(state => state.value && state.value.joke);
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(fetchJoke());
  }, []);
  return <div className="App">{joke}</div>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

In the code above, we create the store by writing:

const store = createStore(joke, applyMiddleware(thunk.withExtraArgument(api)));

Then we have the following fetchJoke thunk:

const fetchJoke = () => {
  return async (dispatch, getState, api) => {
    const response = await fetch(api);
    const joke = await response.json();
    console.log(joke);
    dispatch({ type: "SET_JOKE", joke });
  };
};

which returns a function with 3 parameters. The third parameter is the argument that we passed in, which is api .

Conclusion

Using Redux Thunk, we can pass in any function that calls dispatch into the dispatch function to manipulate our Redux store state.

We apply the thunk middleware, then we can dispatch thunks.

This means that we can call dispatch with other thunks within a thunk, so we can compose them easily.

Since Redux Thunk 2.1.0, we can use the withExtraArgument function to add an extra argument to the action that we return the function that we want to use with dispatch.

Categories
Redux

Using the React-Redux Hooks API to Manipulate Store State

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to use the useDispatch and useStore hooks in the React-Redux hooks API, and also look at performance and stale state issues.

useDispatch

We can use the useDispatch hook to get the dispatch function to dispatch actions to the store.

For example, we use useDispatch as follows:

import React from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore } from "redux";

function count(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(count);

function App() {
  const count = useSelector(state => state);
  const dispatch = useDispatch();
  return (
    <div className="App">
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
      <p>{count}</p>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

In the code above, we called useDispatch in App to get the dispatch function so that we can dispatch actions from the store.

Then we create functions that call dispatch to pass into the onClick prop.

useStore

We can use the useStore hook to get the reference to the Redux store. It’s the same one that we passed into the Provider component.

This shouldn’t be used frequently since we aren’t supposed to access the store directly within a React app since it doesn’t update according to the React update cycle.

Stale Props and “Zombie Children”

Stale props mean that a selector function relies on a component’s props to extract data, a parent component would re-render and pass down new props as a result of an action, but the component’s selector functions execute before the component has a chance to re-render with those new props.

This causes outdated data to be displayed. in the child component or an error being thrown.

“Zombie child” refers to the cases where multiple nested connected components are mounted ina first pass, causing a child component to subscribe to the store before its parent. Then an action is dispatched that deletes data from the store. The parent component would stop rendering the child as a result.

Because the child subscribed to the store first, its subscription runs before the parent stops render. When it reads a value based on props, the data no longer exists. Then an error may be thrown when the extraction logic doesn’t check for the case of the missing data.

Therefore, we should rely on props in our selector function for extracting data.

connect adds the necessary subscription to the context provider and delays evaluating child subscriptions until the connected component has re-rendered. Putting a connected component in the component tree just above the component using useSelector will prevent the issues above.

Photo by Ezra Jeffrey-Comeau on Unsplash

Performance

useSelector will do a reference equality comparison of the selected value when running the selector function after an action is dispatched.

It’ll only re-render if the previous state is different from the current state. connect and useSelector doesn’t prevent the component from re-rendering because of its parent re-rendering.

To stop parent re-rendering from re-rendering the child, we can use memoization to prevent that.

For example, we can write the following:

import React from "react";
import ReactDOM from "react-dom";
import { Provider, useSelector, useDispatch } from "react-redux";
import { createStore } from "redux";

function count(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(count);

function App() {
  const count = useSelector(state => state);
  const dispatch = useDispatch();
  return (
    <div className="App">
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
      <p>{count}</p>
    </div>
  );
}

App = React.memo(App);

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

In the code above, we added the React.memo call as follows:

App = React.memo(App);

to cache existing results and preventing re-render if the state remains the same.

Conclusion

We can use the useDispatch hook to get the dispatch function to update the store’s state.

There’s also a useStore hook to get a reference of the Redux store in our React components.

However, we shouldn’t access the store directly with useStore since we’re supposed to use useSelector to get the latest state from the store and use useDispatch to dispatch actions.

Also, we have to be aware of stale props from parent components and child components that have been removed because of state changes throwing errors and displaying incorrect data.

We should also memoize the state of components to cache them so that they don’t have to re-render when nothing changed.

Categories
Redux

Creating Our Own Redux Middleware

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it also as a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to create our own Redux middleware.

Middleware

Middleware is some code that we can put between dispatching an action and the moment that the action reaches the reducer.

To do this in a clean way, we define a function that takes the store as a parameter, which returns a function that takes the next function, which is the same as the dispatch function, which then returns a function that takes an action parameter and returns next(action) .

We can do that as follows:

import { createStore, applyMiddleware } from "redux";

function countReducer(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const logger = store => next => action => {
  console.log("dispatching", action);
  let result = next(action);
  console.log("next state", store.getState());
  return result;
};

let store = createStore(countReducer, applyMiddleware(logger));
store.subscribe(() => store.getState());
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });

In the code above, we have the logger middleware, which is a function that takes the store parameter which holds the Redux store itself, which then returns a function that takes the next function, which the same as store.dispatch . This function then returns a function that takes the action parameter, which is our action object which we called dispatch with.

In the function that takes action , first, we log action, then we call next with action , then we take the returned result and assigned it to result .

Then we log store.getState() , and return result .

To use the logger middleware we just defined, we write:

let store = createStore(countReducer, applyMiddleware(logger));

Then when we run dispatch we’ll see the console.log output from the logger.

Returning Non-Plain Objects with Middleware

We don’t have to return plain objects with middleware.

For example, we can also return a promise as follows:

const promiseMiddleware = store => next => action => {
  if (!(action instanceof Promise)) {
    return next(action);
  }
  return (async () => {
    const resolvedAction = await Promise.resolve(action);
    store.dispatch(resolvedAction);
  })();
};

In the code above, we returned a promise, which isn’t a plain object.

All it does is that if we pass in a promise into dispatch , it’ll resolve the promise and then call store.dispatch with the resolved action.

We can then call the applyMiddleware function when the createStore function is called.

Note that we called our async function, we don’t just return it. We have to call store.dispatch so that the values from our actions will be set in the store.

Defining and Using Multiple Middlewares

We can create and apply multiple middlewares when we call applyMiddleware . To do this, we just pass it all the middlewares for applyMiddleware .

For example, if we want to apply all the middlewares in the examples we have above, we can write:

import { createStore, applyMiddleware } from "redux";

function countReducer(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

const logger = store => next => action => {
  console.log("dispatching", action);
  let result = next(action);
  console.log("next state", store.getState());
  return result;
};

const promiseMiddleware = store => next => action => {
  if (!(action instanceof Promise)) {
    return next(action);
  }
  return (async () => {
    const resolvedAction = await Promise.resolve(action);
    store.dispatch(resolvedAction);
  })();
};

let store = createStore(
  countReducer,
  applyMiddleware(promiseMiddleware, logger)
);
store.subscribe(() => store.getState());
store.dispatch(Promise.resolve({ type: "INCREMENT" }));
store.dispatch({ type: "DECREMENT" });

Since we have our promiseMiddleware , we can now pass in promises to dispatch and set the value in the store.

Therefore, we can write:

store.dispatch(Promise.resolve({ type: "INCREMENT" }));
store.dispatch({ type: "DECREMENT" });

and we won’t get an error.

With the logger middleware, we get console.log output.

Conclusion

We can define middleware to do more than what we can do with Redux itself.

To do this, we define a function that takes the store as a parameter, which returns a function that takes the next function, which is the same as the dispatch function, which then returns a function that takes an action parameter and returns next(action) .

The function has to call dispatch so that the action object can be propagated to the reducer.

We can chain middlewares with the applyMiddleware function, which takes one or more middlewares.

Categories
Redux

Dispatching Async Actions with Redux

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to dispatch actions asynchronously.

Async Actions

In JavaScript, a lot of things have to be done asynchronously to avoid blocking the main execution thread.

Redux can dispatch asynchronous actions so that we don’t always have to change states synchronously.

To do this, we use the Redux-Thunk middleware. We can apply middlewares by using Redux’s applyMiddleware function.

With that, we can pass in functions that call dispatch instead of plain objects. We need this capability to run async actions that call dispatch and populate the store with it.

We can do this as follows:

import { createStore, applyMiddleware } from "redux";
import thunkMiddleware from "redux-thunk";

function jokeReducer(state = {}, action) {
  switch (action.type) {
    case "SET_JOKE":
      return action.joke;
    default:
      return state;
  }
}

let store = createStore(jokeReducer, applyMiddleware(thunkMiddleware));

function fetchJoke() {
  return async dispatch => {
    const response = await fetch("[https://api.icndb.com/jokes/random](https://api.icndb.com/jokes/random)");
    const joke = await response.json();
    dispatch({ type: "SET_JOKE", joke });
  };
}

(async () => {
  await store.dispatch(fetchJoke());
  console.log(store.getState().value.joke);
})();

In the code above, we have the jokeReducer which is the same as what we have before.

Then in to create the store, we write:

let store = createStore(jokeReducer, applyMiddleware(thunkMiddleware));

The difference from stores that only take plain object actions is that we have the applyMiddleware function where we passed in the thunkMiddleware.

The thunkMiddleware lets us pass in functions to dispatch instead of a plain object without it.

In the function we pass in, we can call dispatch with plain objects to do the synchronous actions.

Then we defined the fetchJoke function, which is or action creator, as follows:

function fetchJoke() {
  return async dispatch => {
    const response = await fetch("[https://api.icndb.com/jokes/random](https://api.icndb.com/jokes/random)");
    const joke = await response.json();
    dispatch({ type: "SET_JOKE", joke });
  };
}

In this function, we returned an async function that has the Redux dispatch function as the parameter.

Then once we get the joke with the Fetch API, we call dispatch provided in the parameter.

This way the Thunk middleware can call dispatch in the returned function.

We can return any function, but we need to use thunks for async functions since they don’t return plain objects.

Ordinary action creators must return plain objects, but if we use the Redux Thunk middleware, we can return a function that calls dispatch and getState.

Then we dispatch our async action and get the value from it as follows:

(async () => {
  await store.dispatch(fetchJoke());
  console.log(store.getState().value.joke);
})();

All we did above was call dispatch with the promise that the fetchJoke function returned as we defined above. Then we use store.getState().value.joke to get the joke’s value from the store.

Photo by Sincerely Media on Unsplash

Checking State Before Dispatching Synchronous Actions in Our Async Action Creator

We can write a function to check the state before getting data. To do this, we can make the following changes while keeping everything else the same:

function shouldFetchJoke(state) {
  return !state.value || !state.value.joke;
}

function fetchJoke() {
  return async (dispatch, getState) => {
    if (shouldFetchJoke(getState())) {
      const response = await fetch("[https://api.icndb.com/jokes/random](https://api.icndb.com/jokes/random)");
      const joke = await response.json();
      dispatch({ type: "SET_JOKE", joke });
    }
  };
}

In the code above, we defined the shouldFetchJoke function which checks the state.value and state.value.joke to see if the value has been set.

Then we change fetchJoke to call shouldFetchJoke . Then we take advantage of the getState parameter to get the state and check if a joke already exists in the store with shouldFetchJoke .

If not, then we proceed to get the joke.

Conclusion

With the Redux Thunk middleware, we can pass in actions that aren’t plain objects to dispatch.

To use it, we have to call applyMiddleware with the thunkMiddleware as the argument.

Then we can create an action creator that returns a function with the dispatch and getState functions as the first and second parameter respectively, then we can get the state with getState as usual.

Categories
Redux

Introduction to Redux Actions and Reducers

With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.

In this article, we’ll look at how to create actions that we can dispatch to our store with Redux.

Create a Store with a Single Reducer

We can create a store with a single reducer by creating a reducer function. Then we can use Redux’s createStore function to create the store from the reducer.

With the store, we get to subscribe to it to get the latest values, and also dispatch actions with it to update the state in the store by passing it to the reducer.

For example, we can write the following code to create the store and run some actions:

import { createStore } from "redux";

function todosReducer(state = [], action) {
  switch (action.type) {
    case "ADD_TODO":
      return [...state, action.todo];
    case "REMOVE_TODO":
      return state.filter(todo => todo !== action.todo);
    default:
      return state;
  }
}

let store = createStore(todosReducer);

store.subscribe(() => console.log(store.getState()));

store.dispatch({ type: "ADD_TODO", todo: "eat" });
store.dispatch({ type: "ADD_TODO", todo: "drink" });
store.dispatch({ type: "REMOVE_TODO", todo: "eat" });

In the code above, we created the todosReducer to receive the dispatched actions and then manipulate the state as we wish to do.

We have the switch statement to check the action’s type and then act accordingly.

The action type is indicated in the object that we passed into the dispatch method.

If the action type is “ADD_TODO” , then we insert the todo item in object we have in dispatch ‘s argument to the end of the array, and we spread all the old values before it.

If the action type is “REMOVE_TODO” , then we return a new array that excludes the todo item with the given todo text.

Otherwise, we return what we already have.

Then we use the createStore method to create the store so we can call dispatch to pass in plain objects to manipulate the data in the store.

We can also subscribe to it and then call getState() to get the latest values.

Note that we always make a copy of state and then return something new with it. This prevents accidental changes from mutating objects unintentionally.

Create a Store with a Multiple Reducers

In most apps, we want to store more than one kind of data in our store. We can do that by creating multiple reducers and then use the combineReducer function from Redux to combine them into one store.

To do this we can write something like the following code:

import { createStore, combineReducers } from "redux";

function todosReducer(state = [], action) {
  switch (action.type) {
    case "ADD_TODO":
      return [...state, action.todo];
    case "REMOVE_TODO":
      return state.filter(todo => todo !== action.todo);
    default:
      return state;
  }
}

function countReducer(state = 0, action) {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

let reducers = combineReducers({
  todos: todosReducer,
  count: countReducer
});
let store = createStore(reducers);

store.subscribe(() => console.log("todos", store.getState().todos));
store.subscribe(() => console.log("count", store.getState().count));

store.dispatch({ type: "ADD_TODO", todo: "eat" });
store.dispatch({ type: "ADD_TODO", todo: "drink" });
store.dispatch({ type: "REMOVE_TODO", todo: "eat" });

store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });

In the code above, we have 2 reducers — todoReducer and countReducer .

We combined them into one by calling Redux’s combineReducers function with the name of the state as the property name and the reducer function as the value.

combineReducers combines all the reducers into one reducer, so we can pass it into createStore . We can choose any state name we want.

The store is created by passing the big reducer returned from combineReducers .

Then in the subscribe callbacks, we can use store.getState() to get all the states. Then we can get the todos with the todos property and the count state with the count property.

In the console.log s, we should get something like:

todos ["eat"]
count 0
todos ["eat", "drink"]
count 0
todos ["drink"]
count 0
todos ["drink"]
count 1
todos ["drink"]
count 0

This is because we subscribed to the store and then get the state from each reducer individually.

The dispatch method is called as we did with the single reducer example.

We still pass in the action type and payload if there is one.

Since dispatch works the same as no matter how many reducers we have in the store, we should make sure that no 2 actions have the same name.

Photo by Roberto Nickson on Unsplash

Notes

combineReducers will throw errors in some cases to reduce the chance of committing bugs.

It’ll throw an error if a reducer function doesn’t return the state given to it as the first argument if the action isn’t recognized.

Also, it must never return undefined . It’s too easy to do this via an early return statement. Therefore, combineReducers will throw an error if we do that instead of letting the error manifest somewhere else.

If the state given to it is undefined , it must return the initial state for the specific reducer. This means that the initial state can’t be undefined .

We can specify the initial state as the default argument for the state parameter.

Redux will check for these rules when we’re writing our code.

Conclusion

We can create a store from one more reducer function. The only difference is that we have to use combineReducers to combine multiple reducers into one big reducer in order to pass it into createStore . We can pass in a single reducer straight into createStore .

Then we can use getState to get the latest returned state of all reducers. The states are available from getState() and we can get the properties from the returned object of getState to get those states.

Dispatching actions is the same regardless of how many reducers we have in our store.