Redux: The correct way to access a webservice?

I have outlined several ways to possibly access state from a web service, but I don't know which one is correct in a react-redux application, or if the corresponding one is even listed below.

Context:

I originally had a file API.js

that served as a base for web services. Then I import this into my action files. This worked well until I needed to access the state (more precisely, the web token in the state I need for my header) from API.js

. I tried to import my store but it returned undefined

. Then I realized that I have a circular dependency:

api -> store -> reducers -> components -> actions

Custom middleware


I was wondering if this is acceptable. I threw mine API.js

. I use this to automatically change outgoing network calls with a specific action type. This is what my middleware stack looks like:

const middleware = applyMiddleware(
    myCustomModifyRequestMiddleware,
    thunk,
    . . .

      

And it myCustomModifyRequestMiddleware

basically looks like:

 const myCustomModifyRequestMiddleware = store => next => action {
     if(action.type === 'MODIFY_ME') {

         //Dispatch an FSA
         store.dispatch({
             type: action.payload.actual_type,
             payload: axios.create({ . . .
             meta: action.meta
         })
     }

     return next(action)
 }

      

I now have business logic inside my middleware!

Then I could only have an action creator with a name API_ActionCreator

. But hey, if I was just going to use an action creator, why not just ...

Thunk it


Using thunks, I could just have something like API_ActionCreator.js

:

const apiActionCreator = (actual_type, url, data . . .) {
    return (dispatch, store) {
        //Now I can get the state...
        store.getState()

        //Return an FSA
        return { 
            type: actual_type,
            payload: axios.create...

      

Now I can import mine API_ActionCreator

into my actions without any circular dependencies.

Shop subscription?

Another way would be to keep the web service discreet; subscribe to the store in store

or web service

if I could somehow avoid using a circular dependency when I was calling my web services inside my actions.

TL; DR; This is all experimental, of course, although I was able to get the middleware.

I don't know which one is the most viable, maybe more efficient way to do this?

+3


source to share


2 answers


Thunk action creators and centralized middleware are standard approaches for managing API calls in Redux by having access to dispatch

and getState`. Any of them are fine.



For more information, see Dan's answers on managing asynchronous behavior in Redux and why thunks and other middleware are useful for async working , as well as other articles in the Redux Side Effects section of my React / Redux Reference List . You may also be interested in a list of Redux middleware for making network requests to the Redux Add-ons Directory .

+1


source


I would like to use the approach we have used when facing the problem of having to access the auth token when creating header parameters for fetch requests between different services.

As a result, we created a Singleton template for creating an API service that will be responsible for:

  • The remaining one copy during its use
  • Storing properties like _token to be used by all services
  • Providing a fetch method that can be used by services to set default headers using a token and execute a request

This is what the service looked like:

let _instance = null;

class ApiService {
  static getInstance() {
    if (_instance === null) {
      _instance = new ApiService();
    }

    return _instance;
  }

  setToken(token) {
    this._token = token;
  }

  defaultHeaders(immediateHeaders) {
    const headers = {
      'Content-type': 'application/json',
      ...immediateHeaders,
    };

    if (this._token) {
      headers['Authorization'] = `Bearer ${this._token}`;
    }

    return headers;
  }

  fetch(url, options) {
    const headers = this.defaultHeaders();

    const opts = {
      ...options,
      headers,
    };

    return fetch(url, opts);
  }
}

export default ApiService;

      

Using

When using this approach, the first thing to do is set the token property on the service during the state handlers that are exposed to the token when it is available as state.

For example, setting the token in the authentication state handlers is a good start as the token will be available from the state like state.auth.token

To do this, in your login success action, either as a thunk or saga, set a token before redirecting the user to a private route or a specific component that may be fetch dependent:



ApiService.getInstance().setToken(token);

      

On page refresh, if the token is undefined, make sure it can be rehydrated from initialState.

For example, add this method to Root or App components that set up storage and have access to the source state.

if (initialState.auth.token) {
  ApiService.getInstance().setToken(initialState.auth.token);
}

      

When the token is set as a property on the ApiService instance, so fetch requests from any service with the token are simple.

Just import the ApiService and fetch as usual, but use the public fetch method.

When creating the fetch, pass the url and any relevant parameters such as method or body as usual, except for the headers, which are set by default using the authentication token.

import ApiService from './api.service';

// Get the API service instance

const api = ApiService.getInstance();

export default () => ({

  fetchWorkspaces: async () => {

    const response = await api.fetch(url);

    const workspaces = await response.json();

    return workspaces;

  },
})

      

Hope this is helpful!

0


source







All Articles