Search code examples
reactjsreduxreact-reduxredux-sagaredux-thunk

A way to handle async state updates on Redux store without using redux-thunk or redux-saga middleware?


I'm all in favor of NOT solving problems you don't have.

So I started learning and using React without Redux.

But now my app state has grown and I'm starting to face some state management issues.

It's getting hard to handle it and my App.js is full of <SomeContext.Provider's>.

So I'm willing to add Redux to my app now.

But of course I'm making lots of API calls to get data for my App state. So, from my research basically I have two major options for async state updates: redux-thunk and redux-saga.

Both of them add cumbersome boilerplate and new concepts to handle and maintain.

But my question is: do I really need them (thunk or saga)?

I made this code that seems to work and it updates the store with async data.

The result:

enter image description here

https://codesandbox.io/s/ecstatic-antonelli-9cu7m

The code:

Basically I have this reducer:

getApiDataReducer.js

const initialState = {
  loading: false,
  error: null,
  data: null
};

function getApiDataReducer(state = initialState, action) {
  switch (action.type) {
    case "GET_DATA_START":
      return {
        ...initialState,
        loading: true
      };
    case "GET_DATA_SUCCESS":
      return {
        ...initialState,
        loading: false,
        data: action.payload
      };
    case "GET_DATA_FAIL":
      return {
        ...initialState,
        loading: false,
        error: action.payload
      };
    default:
      return state;
  }
}

export default getApiDataReducer;

rootReducer.js

import getApiDataReducer from "./getApiDataReducer";
import { combineReducers } from "redux";

const rootReducer = combineReducers({
  getApiData: getApiDataReducer
});

export default rootReducer;

And I have my index.js file with my App component:

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

import "./styles.css";

const store = createStore(rootReducer);

function mockAPIcall() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("DATA FROM API"), 1000);
  });
}

function mockAPIcallError() {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject("SOMETHING WRONG HAPPENED"), 1000);
  });
}

function App() {
  const { loading, error, data } = useSelector(state => state.getApiData);
  const dispatch = useDispatch();

  async function getAsyncData() {
    dispatch({ type: "GET_DATA_START" });
    try {
      const newData = await mockAPIcall();
      dispatch({ type: "GET_DATA_SUCCESS", payload: newData });
    } catch (err) {
      dispatch({ type: "GET_DATA_FAIL", payload: err });
    }
  }

  async function getAsyncDataError() {
    dispatch({ type: "GET_DATA_START" });
    try {
      const newData = await mockAPIcallError();
      dispatch({ type: "GET_DATA_SUCCESS", payload: newData });
    } catch (err) {
      dispatch({ type: "GET_DATA_FAIL", payload: err });
    }
  }

  return (
    <div>
      <div>
        Data:
        {
          loading ? "Loading..."
          : error ? error
          : data ? data
          : "No data yet..."
        }
      </div>
      <button onClick={getAsyncData}>Get Async Data</button>
      <button onClick={getAsyncDataError}>Get Async Error</button>
    </div>
  );
}

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

QUESTION:

I'm pretty sure those libraries (redux-thunk and redux-saga) can do much more than that. So I want to know which key features of those libraries am I losing by adopting this much simpler pattern.

Because from what I did on my app so far, I could pretty much refactor everything to this pattern. But I'm pretty sure I'm closing some future doors by doing it this way.

It seems like an open ended question, but I don't think it is. People use those libraries and they should be able to tell which important features are not being taken into consideration while using this pattern.


Solution

  • You are using Redux in a purely synchronous way (shielding it from any asynchronicity which Redux doesn't support anyway) and are missing benefits afforded by the Separation of Concerns design principle.

    EDIT BY OP: This article has helped me a lot: Separation of concerns design principle in Redux using redux-saga