0

Background

I am creating a universal react application with redux and react router. I have most of the application setup with server-side rendering and basic redux actions (modifying a Boolean in the store). Now I would like to make some api calls to get data for my app.

Current Implementation

I thought it would be a good idea to use the redux-api-middleware but I can't get the data to add to the store. I followed the documentation and created an action that looks like this.

example-action.js

import { CALL_API } from `redux-api-middleware`;


export function fn_get_data () {
  [CALL_API]: {
    endpoint: 'http://www.example.com/api/users',
    method: 'GET',
    types: ['REQUEST', 'SUCCESS', 'FAILURE']
  }
}

I run this action on a button click in my page. I see the action get fired but nothing goes into the store. I even added some custom code for the SUCCESS action and was able to console.log() the response but still could not get the data into the store.

Also I have added the middleware to the store in the same way the documentation says to.

configureStore.js

import { createStore, applyMiddleware, combineReducers } from 'redux';
import { apiMiddleware } from 'redux-api-middleware';
import reducers from './reducers';

const reducer = combineReducers(reducers);
const createStoreWithMiddleware = applyMiddleware(apiMiddleware)(createStore);

export default function configureStore(initialState) {
  return createStoreWithMiddleware(reducer, initialState);
}

Tried so far

So far I have tried a bunch of different things with the action, like making the actions into exportable symbols and calling them in a reducer and trying to merge the data into the current state that comes from the payload attribute in redux-api-middleware, but no luck.

Question

I really have 2 questions

  • Why are there no reducers in the documentation, is this just overlooked or does the data from the response just go directly into the store?
  • Why doesn't the data I call get added to the store (what am I missing)?

Any help, explanation or constructive criticism on the matter is really appreciated, thanks.

Community
  • 1
  • 1
Joe Lloyd
  • 19,471
  • 7
  • 53
  • 81

3 Answers3

2

As far as I can see, redux-api-middleware provides a bunch of facilities for making an API call, dispatching success/failure actions, and possibly processing the response a bit. However, how you handle those actions in your reducers is up to you. So, if you ask the middleware to dispatch "MY_REQUEST_SUCCESS", you'd need to add a "MY_REQUEST_SUCCESS" handler into your reducer logic, and update the state appropriately.

markerikson
  • 63,178
  • 10
  • 141
  • 157
2

Solution

Like markerikson said, the library only provides you with the tools, you still have to write reduceres to respond to the action. In my case I finally got data with the following reducer.

example-reducer.js

import * as ExampleActionType from "../action/example-action";
import Immutable from "immutable";

let defaultState = Immutable.fromJS({
  fakeData: {}
});

function exampleState (state = defaultState, action) {
  switch (action.type) {

    case ExampleActionType.REQUEST : {
        console.log(action);
        return state;
    }

    case ExampleActionType.SUCCESS : {
        console.log(action);
        return state.merge({fakeData: action.payload });
    }

    case ExampleActionType.FAILURE : {
        console.log(action);
        return state;
    }

    default:
        return state;
  }
}

I also had to export the symbols like this

example-action.js

export const REQUEST = Symbol('REQUEST');
export const SUCCESS = Symbol('SUCCESS');
export const FAILURE = Symbol('FAILURE');

Conclusion

Awesome library that gives you all the tools you need to creat api requests with very little code. Hope this helps someone who is confused about this like I was.

Joe Lloyd
  • 19,471
  • 7
  • 53
  • 81
0

redux-api-middleware isn't meant to store data to the store (that's why you don't need to setup any reducers). I actually built a library redux-cached-api-middleware that acts as a thin layer on top of redux-api-middleware and adds caching (can easily be used as a simple storing) capabilities.

Here's an example component:

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import api from 'redux-cached-api-middleware';
import Items from './Items';
import Error from './Error';

class ExampleApp extends React.Component {
  componentDidMount() {
    this.props.fetchData();
  }

  render() {
    const { result } = this.props;
    if (!result) return null;
    if (result.fetching) return <div>Loading...</div>;
    if (result.error) return <Error data={result.errorPayload} />;
    if (result.successPayload) return <Items data={result.successPayload} />;
    return <div>No items</div>;
  }
}

ExampleApp.propTypes = {
  fetchData: PropTypes.func.isRequired,
  result: PropTypes.shape({}),
};

const CACHE_KEY = 'GET/items';

const enhance = connect(
  state => ({
    result: api.selectors.getResult(state, CACHE_KEY),
  }),
  dispatch => ({
    fetchData() {
      return dispatch(
        api.actions.invoke({
          method: 'GET',
          headers: { Accept: 'application/json' },
          endpoint: 'https://my-api.com/items/',
          cache: {
            key: CACHE_KEY,
            strategy: api.cache
              .get(api.constants.CACHE_TYPES.TTL_SUCCESS)
              .buildStrategy({ ttl: 10 * 60 * 1000 }), // 10 minutes
          },
        })
      );
    },
  })
);

export default enhance(ExampleApp);

This work in cached manner, but you could easily pass custom shouldFetch function, and you would always refetch from API:

const enhance = connect(
  state => ({
    result: api.selectors.getResult(state, CACHE_KEY),
  }),
  dispatch => ({
    fetchData() {
      return dispatch(
        api.actions.invoke({
          method: 'GET',
          headers: { Accept: 'application/json' },
          endpoint: 'https://my-api.com/items/',
          cache: {
            key: CACHE_KEY,
            shouldFetch: () => true
          },
        })
      );
    },
  })
);

And the setup is as follows (notice the api reducer, that actually handles storing responses to redux state):

import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { apiMiddleware } from 'redux-api-middleware';
import api from 'redux-cached-api-middleware';
import reducers from './reducers';

const store = createStore(
  combineReducers({
    ...reducers,
    [api.constants.NAME]: api.reducer,
  }),
  applyMiddleware(thunk, apiMiddleware)
);
Karolis.sh
  • 1,618
  • 20
  • 20