0

I've utilized redux-promise-middleware with react and redux to build an application that has several buttons (each will have its own unique ajax request that will update the state accordingly)

I started to make the dataReducer for three of the many buttons I'm making. dataReducer is already over 100 lines and its also very tedious to make this reducer for the remainder of the buttons.

Is there a more efficient way of creating this dataReducer function and shortening while ensuring that the dataReducer function remains pure

const dataReducer = (state = dataInitialState, action) => {
  console.log({ action_type: action.type });
  switch (action.type) {
    case "UPDATE_URL_ONE_PENDING":
      return {
        ...state,
        url_one: {
          ...state["url_one"],
          error: false,
          success: false,
          loading: true
        }
      };
    case "UPDATE_URL_ONE_FULFILLED":
      console.log({ payload: action["payload"] });
      return {
        ...state,
        url_one: {
          ...state["url_one"],
          error: false,
          success: true,
          loading: false,
          payload: action["payload"]
        }
      };
    case "UPDATE_URL_ONE_REJECTED":
      return {
        ...state,
        url_one: {
          ...state["url_one"],
          error: true,
          success: false,
          loading: false
        }
      };
    case "UPDATE_URL_TWO_PENDING":
      return {
        ...state,
        url_two: {
          ...state["url_two"],
          error: false,
          success: false,
          loading: true
        }
      };
    case "UPDATE_URL_TWO_FULFILLED":
      return {
        ...state,
        url_two: {
          ...state["url_two"],
          error: false,
          success: true,
          loading: false,
          payload: action["payload"]
        }
      };
    case "UPDATE_URL_TWO_REJECTED":
      return {
        ...state,
        url_two: {
          ...state["url_two"],
          error: true,
          success: false,
          loading: false
        }
      };
    case "UPDATE_URL_THREE_PENDING":
      return {
        ...state,
        url_three: {
          ...state["url_three"],
          error: false,
          success: false,
          loading: true
        }
      };
    case "UPDATE_URL_THREE_FULFILLED":
      return {
        ...state,
        url_three: {
          ...state["url_three"],
          error: false,
          success: true,
          loading: false,
          payload: action["payload"]
        }
      };
    case "UPDATE_URL_THREE_REJECTED":
      return {
        ...state,
        url_three: {
          ...state["url_three"],
          error: true,
          success: false,
          loading: false
        }
      };
    default:
      return state;
  }
};

Full Code (Code Sandbox Here)

import React from "react";
import ReactDOM from "react-dom";
import { Provider, connect } from "react-redux";
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";

//Action Creator
function updateData(url, type) {
  console.log({ url, type });
  return dispatch => {
    dispatch({
      type: type,
      payload: $.ajax({
        type: "GET",
        url: url,
        dataType: "json",
        async: false
      })
    });
  };
}

//App Component
class App extends React.Component {
  render() {
    return (
      <div>
        <button
          onClick={() => {
            console.log({ props: this.props });
          }}
        >
          Check Props
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=TSLA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_ONE"
            );
          }}
        >
          UPDATE URL ONE
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=GE&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_TWO"
            );
          }}
        >
          UPDATE_URL_TWO
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=PZZA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_THREE"
            );
          }}
        >
          UPDATE_URL_THREE
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=PZZA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_FOUR"
            );
          }}
        >
          UPDATE_URL_FOUR
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=PZZA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_FIVE"
            );
          }}
        >
          UPDATE_URL_FIVE
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=PZZA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_SIX"
            );
          }}
        >
          UPDATE_URL_SIX
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=PZZA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_SEVEN"
            );
          }}
        >
          UPDATE_URL_SEVEN
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=PZZA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_EIGHT"
            );
          }}
        >
          UPDATE_URL_EIGHT
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=PZZA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_NINE"
            );
          }}
        >
          UPDATE_URL_NINE
        </button>
        <button
          onClick={() => {
            this.props.updateData(
              "https://api.iextrading.com/1.0/stock/market/batch?symbols=PZZA&types=quote,stats,news,chart&range=1m&last=5",
              "UPDATE_URL_TEN"
            );
          }}
        >
          UPDATE_URL_TEN
        </button>
      </div>
    );
  }
}

const mapStateToProps = state => state;
const mapDispatchToProps = dispatch => {
  return {
    updateData: (data, type) => {
      dispatch(updateData(data, type));
    }
  };
};

const AppEnhanced = connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

//reducer below that will determine how the state updates based on the action reducer above

const dataInitialState = {
  url_one: { error: false, success: false, loading: true, payload: [] },
  url_two: { error: false, success: false, loading: true, payload: [] },
  url_three: { error: false, success: false, loading: true, payload: [] },
  url_four: { error: false, success: false, loading: true, payload: [] },
  url_five: { error: false, success: false, loading: true, payload: [] },
  url_size: { error: false, success: false, loading: true, payload: [] },
  url_seven: { error: false, success: false, loading: true, payload: [] },
  url_eight: { error: false, success: false, loading: true, payload: [] },
  url_nine: { error: false, success: false, loading: true, payload: [] },
  url_ten: { error: false, success: false, loading: true, payload: [] }
};

const dataReducer = (state = dataInitialState, action) => {
  console.log({ action_type: action.type });
  switch (action.type) {
    case "UPDATE_URL_ONE_PENDING":
      return {
        ...state,
        url_one: {
          ...state["url_one"],
          error: false,
          success: false,
          loading: true
        }
      };
    case "UPDATE_URL_ONE_FULFILLED":
      console.log({ payload: action["payload"] });
      return {
        ...state,
        url_one: {
          ...state["url_one"],
          error: false,
          success: true,
          loading: false,
          payload: action["payload"]
        }
      };
    case "UPDATE_URL_ONE_REJECTED":
      return {
        ...state,
        url_one: {
          ...state["url_one"],
          error: true,
          success: false,
          loading: false
        }
      };
    case "UPDATE_URL_TWO_PENDING":
      return {
        ...state,
        url_two: {
          ...state["url_two"],
          error: false,
          success: false,
          loading: true
        }
      };
    case "UPDATE_URL_TWO_FULFILLED":
      return {
        ...state,
        url_two: {
          ...state["url_two"],
          error: false,
          success: true,
          loading: false,
          payload: action["payload"]
        }
      };
    case "UPDATE_URL_TWO_REJECTED":
      return {
        ...state,
        url_two: {
          ...state["url_two"],
          error: true,
          success: false,
          loading: false
        }
      };
    case "UPDATE_URL_THREE_PENDING":
      return {
        ...state,
        url_three: {
          ...state["url_three"],
          error: false,
          success: false,
          loading: true
        }
      };
    case "UPDATE_URL_THREE_FULFILLED":
      return {
        ...state,
        url_three: {
          ...state["url_three"],
          error: false,
          success: true,
          loading: false,
          payload: action["payload"]
        }
      };
    case "UPDATE_URL_THREE_REJECTED":
      return {
        ...state,
        url_three: {
          ...state["url_three"],
          error: true,
          success: false,
          loading: false
        }
      };
    default:
      return state;
  }
};

const reducers = combineReducers({
  data: dataReducer
});
const store = createStore(reducers, applyMiddleware(thunk, promise));

ReactDOM.render(
  <Provider store={store}>
    <AppEnhanced />
  </Provider>,
  document.getElementById("root")
);
Chris
  • 5,444
  • 16
  • 63
  • 119

2 Answers2

0

A lot of your code can be reduced to a single reusable component that updates its own local state according to the success or failure of the ajax request. Too often developers run to redux without understanding why. In this case/example, you don't need it. However, if your application was heavily nested and split across multiple parent components, then redux could be a viable option. Either way, it can still be reduced to a single reusable component.

Working example: https://codesandbox.io/s/0x0478x4wp


index.js

import React, { Fragment } from "react";
import { render } from "react-dom";
import StockButton from "./components/StockButton";

const App = () => (
  <Fragment>
    <StockButton symbol="PZZA" />
    <StockButton symbol="AAPL" />
    <StockButton symbol="MSFT" />
  </Fragment>
);

render(<App />, document.getElementById("root"));

components/StockButton

import React, { Component } from "react";
import PropTypes from "prop-types";
import axios from "axios";
import Placeholder from "../Placeholder";

const initialState = {
  error: false,
  success: false,
  loading: false,
  payload: []
};

class StockButton extends Component {
  state = { ...initialState };

  componentDidMount = () => this.fetchData();

  fetchData = () => {
    this.setState({ loading: true }, () => {
      const { symbol } = this.props;
      const URL = `https://api.iextrading.com/1.0/stock/market/batch?symbols=${symbol}&types=quote,stats,news,chart&range=1m&last=5`;
      axios
        .get(URL)
        .then(({ data }) => this.setState({ loading: false, payload: data }))
        .catch(error => this.setState({ ...initialState, error }));
    });
  };

  render = () =>
    this.state.error ? (
      <p>Error loading data!</p>
    ) : this.state.loading ? (
      <Placeholder />
    ) : (
      <div>
        <button onClick={this.fetchData}>Update {this.props.symbol}</button>
        <pre style={{ width: 500, height: 300, overflowY: "auto" }}>
          <code>{JSON.stringify(this.state.payload, null, 4)}</code>
        </pre>
      </div>
    );
}

StockButton.propTypes = {
  symbol: PropTypes.string.isRequired
};

export default StockButton;

components/Placeholder

import React, { Fragment } from "react";

const Placeholder = () => (
  <Fragment>
    <button style={{ width: 95, height: 24 }}>Loading</button>
    <pre style={{ width: 500, minHeight: 300, overflowY: "scroll" }} />
  </Fragment>
);

export default Placeholder;
Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
0

Sure - I would create a helper function to remove some of the duplication in the reducer like this:

   const updateUrl = (state, urlKey, error, success, loading, payload) => {
      return {
        ...state,
        [urlKey]: { // 'computed key'
          ...state[urlKey],
          error, success, loading, payload
        }
    }

Then you can use it like this:

    const dataReducer = (state = dataInitialState, action) => {
      console.log({ action_type: action.type });
      switch (action.type) {
        case "UPDATE_URL_ONE_PENDING":
          // call our helper func
          return updateUrl(state, "url_one", false, false, true);
        case "UPDATE_URL_ONE_FULFILLED":
          console.log({ payload: action["payload"] });
          return updateUrl(state, "url_one", false, true, false, action.payload);
          ...

However it seems like many of your reducer actions are unnecessary and could be merged - e.g. the two actions above could easily become one:

case "UPDATE_URL_ONE":
  // call our helper func
  return updateUrl(state, "url_one", action.error, action.success, action.loading);

You can probably further remove the individual actions with a simple "UPDATE_URL" action.

e.g.

case "UPDATE_URL":
  // call our helper func
  return updateUrl(state, action.url, action.error, action.success, action.loading, action.payload);

and in your actions file, you could have a generic updateUrlAction like so:

const updateUrlaction = (url, error, success, loading, payload) => dispatch => {
  dispatch( {
    type: 'UPDATE_URL',
    url, error, success, loading, payload }
}
jsdeveloper
  • 3,945
  • 1
  • 15
  • 14
  • This is almost what I was looking for. You mentioned that I could merge two actions to become one, which I'd love to do, but struggling to see how those actions can become one. I'm really going for as concise as possible – Chris Mar 11 '19 at 00:17
  • @Chris - sure I've updated my answer with a more concrete example for a generic updateUrl action. – jsdeveloper Mar 12 '19 at 22:48