简体   繁体   中英

Redux: Proper way to access state from web service?

I have outlined a few ways to possibly access state from the web service, but I do not know which one is the proper one in a react-redux app, or if the proper one is even listed below.

Context:

Originally, I had an API.js file which acted as a base for web services. I would then import this into my action files. This went well until I needed to access the state (more specifically, a web token in the state that I needed for my header) from API.js . I tried to import my store, but it returned undefined . I then realized I had a circular dependency:

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

Custom Middleware


I was wondering if this is acceptable. I ditched my API.js . I use this to automatically modify outgoing network calls that have a specific action type. This is what my middleware stack looks like:

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

And 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)
 }

Now I have business logic inside my middleware!

Then I could just have an action creator named API_ActionCreator . But hey, if I was just going to use an action creator why not just...

Thunk it


Using thunks I could probably 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 my API_ActionCreator into my actions without any circular dependencies.

Subscribing to the store ?

Another way would be to have the web service be stateful; subscribe to the store in store or the web service , if I could somehow avoid running into a circular dependency when I called my web services inside my actions.

TLDR; Of course, this is all experimental, though I was able to get the middleware working.

I don't know which one is the most viable approach, is there maybe a more redux-ish way to do this?

Thunk action creators and centralized middleware are both standard approaches for managing API calls in Redux while having access to dispatch and getState`. Either of those is fine.

For more info, see Dan's answers on managing async behavior in Redux and why thunks and other middleware are useful for async work , as well as the other articles in the Redux Side Effects section of my React/Redux links list . You might also be interested in the list of Redux middleware for making network requests in my Redux addons catalog .

I'd like to share an approach that we used when facing the issue of needing to access the auth token when creating the header options for fetch requests between different services.

We ended up using a Singleton pattern to create an API service that would be responsible for:

  • Remaining a single instance throughout its use
  • Holding properties such as _token to be used by all services
  • Exposing a fetch method that can be used by services to set default headers using the token and make the request

Here 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;

Usage

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

Eg setting the token in the authentication state handlers is a good start as the token will be available from the state eg state.auth.token

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

ApiService.getInstance().setToken(token);

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

Eg add this method in the Root or App components that configure the store and have access to the initial state.

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

When the token is set as a property on the ApiService instance making fetch requests from any service with the token is straightforward.

Just import the ApiService and make a fetch as normal but use the public fetch method.

When making the fetch, pass in the URL and any relevant options such as the Method or Body as normal except the headers which are set by default with the auth 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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM