Code Monkey home page Code Monkey logo

redux-axios-middleware's Introduction

redux-axios-middleware

npm version

Redux middleware for fetching data with axios HTTP client

Installation

npm i -S redux-axios-middleware

You can also use in browser via <script src="https://unpkg.com/redux-axios-middleware/dist/bundle.js"></script>, the package will be available under namespace ReduxAxiosMiddleware

How to use?

Use middleware

By default you only need to import middleware from package and add it to redux middlewares and execute it with first argument being with axios instance. second optional argument are middleware options for customizing

import {createStore, applyMiddleware} from 'redux';
import axios from 'axios';
import axiosMiddleware from 'redux-axios-middleware';

const client = axios.create({ //all axios can be used, shown in axios documentation
  baseURL:'http://localhost:8080/api',
  responseType: 'json'
});

let store = createStore(
  reducers, //custom reducers
  applyMiddleware(
    //all middlewares
    ...
    axiosMiddleware(client), //second parameter options can optionally contain onSuccess, onError, onComplete, successSuffix, errorSuffix
    ...
  )
)

Dispatch action

Every action which have payload.request defined will be handled by middleware. There are two possible type definitions.

  • use action.type with string name
  • action with type will be dispatched on start, and then followed by type suffixed with underscore and
  • success suffix on success, or error suffix on error

defaults: success suffix = "_SUCCESS" error suffix = "_FAIL"

export function loadCategories() {
  return {
    type: 'LOAD',
    payload: {
      request:{
        url:'/categories'
      }
    }
  }
}
  • use action.types with array of types [REQUEST,SUCCESS,FAILURE]
  • REQUEST will be dispatched on start, then SUCCESS or FAILURE after request result
export function loadCategories() {
  return {
    types: ['LOAD','AWESOME','OH_NO'],
    payload: {
      request:{
        url:'/categories'
      }
    }
  }
}

Actions that are handled by this middleware return a promise. This gives you the ability to chain actions. A good example of this might be a form. In the form you might dispatch an actions to store the form values. The normal flow of the action into the reducers would not be altered but you can chain a then/catch onto the initial dispatch.

this.props.saveForm(formData)
  .then(() => {
    // router the user away
    this.context.router.push("/my/home/page")
  })
  .catch((response) => {
    //handle form errors
  })

Another example might be a set of actions that you want dispatched together.

Promise.all([
  dispatch(foo()),
  dispatch(bar()),
  dispatch(bam()),
  dispatch(boom())

]).then(() => {
  dispatch(
    loginSuccess(
      token
    )
  )
})

Request complete

After request complete, middleware will dispatch new action,

on success

{
  type: 'AWESOME', //success type
  payload: { ... } //axios response object with data status headers etc.
  meta: {
    previousAction: { ... } //action which triggered request
  }
}

on error

Error action is same as success action with one difference, there's no key payload, but now there's error;

{
    type:'OH_NO',
    error: { ... }, //axios error object with message, code, config and response fields
    meta: {
      previousAction: { ... } //action which triggered request
    }
}

if axios failed fatally, default error action with status 0 will be dispatched.

{
    type:'OH_NO',
    error: {
      status: 0,
      ... //rest of axios error response object
    },
    meta: {
      previousAction: { ... } //action which triggered request
    }
}

Multiple clients

If you are using more than one different APIs, you can define those clients and put them to middleware. All you have to change is import of middleware, which is passed to redux createStore function and defined those clients.

import { multiClientMiddleware } from 'redux-axios-middleware';
createStore(
 ...
 multiClientMiddleware(
   clients, // described below
   options // optional, this will be used for all middleware if not overriden by upper options layer
 )
)

clients object should be map of

{
  client: axiosInstance, // instance of axios client created by axios.create(...)
  options: {} //optional key for passing specific client options
}

For example:

{
  default: {
    client: axios.create({
       baseURL:'http://localhost:8080/api',
       responseType: 'json'
    })
  },
  googleMaps: {
    client: axios.create({
        baseURL:'https://maps.googleapis.com/maps/api',
        responseType: 'json'
    })
  }
}

Now in every dispatched action you can define client used:

export function loadCategories() {
  return {
    type: 'LOAD',
    payload: {
      client: 'default', //here you can define client used
      request:{
        url:'/categories'
      }
    }
  }
}

If you don't define client, default value will be used. You can change default client name in middleware options.

Middleware options

Options can be changed on multiple levels. They are merged in following direction:

default middleware values < middleware config < client config < action config

All values except interceptors are overriden, interceptors are merged in same order. Some values are changeable only on certain level (can be seen in change level column).

Legend: M - middleware config C - client config A - action config

key type default value change level description
successSuffix string SUCCESS M C A default suffix added to success action, for example {type:"READ"} will be {type:"READ_SUCCESS"}
errorSuffix string FAIL M C A same as above
onSuccess function described above M C A function called if axios resolve with success
onError function described above M C A function called if axios resolve with error
onComplete function described above M C A function called after axios resolve
returnRejectedPromiseOnError bool false M C A if true, axios onError handler will return Promise.reject(newAction) instead of newAction
isAxiosRequest function function check if action contain action.payload.request M check if action is axios request, this is connected to getRequestConfig
getRequestConfig function return content of action.payload.request M C A if isAxiosRequest returns true, this function get axios request config from action
getClientName function returns action.payload.client OR 'default' M C A attempts to resolve used client name or use defaultClientName
defaultClientName every possible object key type 'default' M key which define client used if getClienName returned false value
getRequestOptions function return action.payload.options M C returns options object from action to override some values
interceptors object {request: [], response: []} M C You can pass axios request and response interceptors. Take care, first argument of interceptor is different from default axios interceptor, first received argument is object with getState, dispatch and getAction keys

interceptors

interceptors can be added to the middleware 2 different ways: Example:

  const middlewareConfig = {
    interceptors: {
      request: [
        function ({getState, dispatch, getSourceAction}, req) {
          console.log(req); //contains information about request object
          //...
        },
        function ({getState, dispatch, getSourceAction}, req) {
          //...
        }
      ],
      response: [
        function ({getState, dispatch, getSourceAction}, req) {
          console.log(req); //contains information about request object
          //...
        },
        function ({getState, dispatch, getSourceAction}, req) {
          //...
        }
      ]
    }
  };

In example above if request and\or response succeeded interceptors will be invoked. To set interceptors what will handle error on request or response pass an object to request and\or response arrays with corresponding success and\or error functions.

Example:

  const middlewareConfig = {
    interceptors: {
      request: [{
        success: function ({getState, dispatch, getSourceAction}, req) {
          console.log(req); //contains information about request object
          //...
          return req;
        },
        error: function ({getState, dispatch, getSourceAction}, error) {
          //...
          return response
        }
      }
      ],
      response: [{
        success: function ({getState, dispatch, getSourceAction}, res) {
          console.log(res); //contains information about request object
          //...
          return Promise.resolve(res);
        },
        error: function ({getState, dispatch, getSourceAction}, error) {
          return Promise.reject(error);
        }
      }
      ]
    }
  };

Finally, to include the middlewareConfig here is the relevant part of the example copied from the top of this file.

let store = createStore(
  reducers, //custom reducers
  applyMiddleware(
    //all middlewares
    ...
    axiosMiddleware(client,middlewareConfig),
    ...
  )
)

Examples

License

This project is licensed under the MIT license, Copyright (c) 2016 Michal Svrček. For more information see LICENSE.md.

Acknowledgements

Dan Abramov for Redux Matt Zabriskie for Axios. A Promise based HTTP client for the browser and node.js

redux-axios-middleware's People

Contributors

alexeib avatar andrefgneves avatar art049 avatar crobinson42 avatar danjersoft avatar nmaves avatar palatnyi avatar pkellner avatar rannn505 avatar svrcekmichal avatar thenerdyhamster avatar vasilevich avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-axios-middleware's Issues

How to run unit tests?

Hi,
I am considering using this instead of redux-sagas to manage all my api calls. Sagas makes it easy to test actions and dispatches.

What is the recommended way to test my app when using redux-axios-middleware to make api calls?

thanks,

Multiple API configuration

How would you use this middleware with multiple APIs

Is this possible? I don't think it is but maybe we could name space axios clients?

Thoughts?

Interceptor Response not respond to anything

Hi, Thanks for this great package!. Need ask or maybe I doing it wrong.

I'm trying to setup the axiosmiddleware with interceptor. The interceptor request is working properly but the interceptor response not working.

here is my code:


function interceptorRequest(dispatch, config) {
    const token = cookie.load('_t');
    const configuration = config;
    if (token) {
        configuration.headers.Authorization = `Bearer ${cookie.load('_t')}`;
    }

    return configuration;
}

function interceptorResponse(dispatch, response) {
    console.log(dispatch);
    console.log(response);
    console.log(error);
}

export const client = axios.create({
    baseURL: API_URL,
    headers: {
        Accept: 'application/json',
    },
});

export const clientOptions = {
    interceptors: {
        request: [interceptorRequest],
        response: [interceptorResponse],
    },
};

// store.js

// other import
import { client, clientOptions } from '../utils/axios';


const reduxRouterMiddleware = routerMiddleware(history);

const middlewares = [
    thunk,
    axiosMiddleware(client, clientOptions),
    reduxRouterMiddleware,
];

const store = compose(applyMiddleware(...middlewares))(createStore)(reducer);

export default store;

my question:

  1. my console.log (at response interceptor) never return anything. Why it not return anything?
  2. how can I setup the interceptors with error? For example:
// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  }, function (error) {
    // Do something with response error
    return Promise.reject(error);
  });

Thanks!

Change default actions in middleware

I would like to simplify little bit default actions onSuccess and onError. My suggestion is:

export const onSuccess = ({ action, next, response }, options) => {
  const nextAction = {
    type: getActionTypes(action, options)[1],
    payload: response,
    meta: {
      previousAction: action
    }
  };
  next(nextAction);
  return nextAction;
};

PROS:

  • all response is at one place, and we are using default axios response, so people using axios know where to find what

This is the proposed error handler:

export const onError = ({ action, next, error }, options) => {
  let errorObject;
  if(error instanceof Error) {
    errorObject = {
      data: error.message,
      status: 0
    };
    if(process.env.NODE_ENV !== 'production') {
      console.log('HTTP Failure in Axios', error);
    }
  } else {
    errorObject = error;
  }
  const nextAction = {
    type: getActionTypes(action, options)[2],
    error: errorObject,
    meta: {
      previousAction: action
    }
  };

  next(nextAction);
  return nextAction;
};

Error handler will handle HTTP Failure and create generic action error if something bad occured. It will also handle logging to console if not production env is used. After little bit of googling I found that code 0 may represent various http failures.

PROS:

  • everyone can handle Error object in onError handler whatever he like
  • default error response of axios is used and returned

Making a simple get request and setting the response to payload

Hello,

I'm hitting a bunch of road blocks trying to set a response from a axios.get request to action payload. The main issue is that since the response is a promise it takes the entire promise as the payload and there is no way to set the response within the promise as the payload. Any advice on how to use this middleware to rectify the issue I am having?

I greatly appreciate any direction given and I thank you in advance.

Peter

Uncaught TypeError: (0 , _reduxAxiosMiddleware.isAxiosRequest) is not a function

isAxiosRequest works with import { isAxiosRequest } from 'redux-axios-middleware/lib/defaults';
Neither working with import { isAxiosRequest } from 'redux-axios-middleware/src/defaults';.
Nor with import { isAxiosRequest } from 'redux-axios-middleware/dist/bundle';
Also tried with import axiosMiddleware from 'redux-axios-middleware'; and then axiosMiddleware.isAxiosRequest(action)

lib directory is ignored in git.
src directory is ignored in npm.

How to use isAxiosRequest?
Failing with isAxiosRequest(action)

Tried version 4.0.0, 3.1.1 and 3.0
May be I am missing something basic!

There is a typo mistake in your example

I don't know if this is something that is related to the latest versions of axios.

But, when you state that:

const client = axios.create({ //all axios can be used, shown in axios documentation
baseUrl:'http://localhost:8080/api',
responseType: 'json'
});

The URL of baseUrl should be all capital letters, e.g. baseURL. This was something that made axios don't find the right property when it tries to check if there's any config parameter available with baseURL while creating the request object.

Best regards

Success action is not propagated to other middleware

Hi,

I have a problem with asynchronous request and the succeeding success action not being passed on to the other middleware but being completely consumed by redux-axios-middleware.

I have a custom middleware that is taking care of access token handling and that is saving the access token on LOGIN_SUCCESS to a cookie. But the LOGIN_SUCCESS action does never reach the custom middleware. Can someone pls shed some light here ? From my understanding actions get passed through all middleware, right ?

Thanks for your help,
Alexander

Action returned by promise not put through middleware

I have a middleware which formats API responses. Is it possible to actions returned by calling .then/.catch on the request action to be put through that middleware?

Example:

action/login.js

export const logIn = (username, password) => {
  const action = {
    types: [LOG_IN, LOG_IN_SUCCESS, LOG_IN_FAIL],
    payload: {
      request: {
        // request data
      }
    },
  };

  return dispatch => dispatch(action);
};


components/LogIn.js

class LogIn extends Component {
  submitForm = (event) => {
    const { logIn } = this.props;
    const { username, password } = this.state;

    event.preventDefault(); 
    logIn(username, password)
      .catch(failAction => console.log(failAction)) // <---
  };

  // render and stuff
}

failAction is "raw" action, meaning it didn't go through the middleware. Is there a possiblity to change that, or is formatting manually each time the only option?

Library limitations

Hi. First of all, thanks for the effort that went into this. It's really a neatly done piece of code. Congrats!

Secondly, after using it for a little while, I seem to be starting to hit the limits of the api (maybe) and I was wondering if I am just doing something wrong, or if this is indeed not 100% suited for me.

My usecase:
I have to fire a request and then on success I have a to chain a few more requests after that. e.g. :

  1. Create a resource.
  2. On success an id is returned
  3. Use this id to perform other requests

And here's where I struggle.
Firing action in reducer is obviously an anti pattern so I can't go that way.
Chaining of actions as described in readme would work (in my setup I don't get a promise for some reason so I am only speaking theoretically). The problem with this though is I can create this resource from many places . Chaining actions in UI seems therefore a bit too repetitive for comfort.

Am I missing something or would it make more sense for me to use pure axios with some defaults set?
Main reason why I went with this lib is ease of adding authorization and very neat config on the action side. Wouldn't want to lose that but I feel like I might have hit a limitation.

Thanks for your input!

Ability to chain calls

I have used this in the past and it works really well for chaining promises.

redux-promise-middleware

Is is really nice to be able to call a then on your bound action creator to capture the response outside of your reducers. You can see their latest release notes.

I think it might be a really simple change if you want me to create a PR.

How to call async method

I'm using your great middleware! Thanks guys but:

when:

GameModal.react.js

async doCreateGame() {
    const {
        fields,
        hideModal,
        formDisabled,
    } = this.props;
    
    if (formDisabled) return;
    
    try {
        const response = await createGame(fields.$values());
        console.log(response);
    
    } catch (error) {
        console.log(error);
    }
}

Actions.js

export function createGame(fields) {
	return {
		types: ['CREATE_GAME','CREATE_GAME_SUCCESS','CREATE_GAME_ERROR'],
		payload: {
		request: {
			url: '/api/game',
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			},
			data: {
				game_name: fields.game_name,
				game_token: fields.game_token + "1-gtkoen",
				destination_url: fields.destination_url
			}
		}
		}
	}
}

AxiosMiddlewareOptions

const axiosMiddlewareOptions = {
    interceptors: {
    request: [
        (action, config) => {
            if (action.getState().auth.accessToken) {
                config.headers['x-access-token'] = action.getState().auth.accessToken
            }
        
        return config
        }
    ]
    }
}

axiosMiddleware(client, axiosMiddlewareOptions),

It returns this:

{
    payload: {
    request: {
        data {
            destination_url: 'url',
            game_name: 'gname',
            game_token: 'token'
        },
        headers {
            Content-Type: 'application/json',
        }
    }
    },
    types: ['CREATE_GAME', 'CREATE_GAME_SUCCESS', 'CREATE_GAME_ERROR']
}

My question is:
Where is my x-auth-token and why i dont get response from url? Thanks :)

Reject (FAIL) action based on response

Have a case where an api do not return the correct error-status with request that fails, so I have added an interceptors in the axios-request

const client = axios.create({
  baseURL: 'https://api.domain.com'
})

client.interceptors.response.use((response) => {
  if (response.data && response.data.status && response.data.status === 'error') {
    return Promise.reject(new Error(response.data.message || 'Request failed'))
  }
  return response
}, (error) => {
  return Promise.reject(error)
})

This works fine with a plain axios, a request that contain a status-fields which is 'error', can be catched in the promise:

client.get('/auth/login', {
  params: {
    username: 'bar',
    passord: 'foo'
  }
}).then((data) => console.log('LOGIN OK', data))
.catch((error) => console.log('LOGIN ERROR', error.message))

With redux-axios-middleware I can get the action to fail with returnRejectedPromiseOnError option in config:

const middleware = [
  axiosMiddleware(client, {
    returnRejectedPromiseOnError: true
  })
]

But I also get a unhandled error in the console (which is not happening with the plain axios-setup)

HTTP Failure in Axios Error: Wrong username or passord.
    at App.js:22
    at tryCallOne (core.js:37)
    at core.js:123
    at JSTimers.js:295
    at _callTimer (JSTimers.js:148)
    at _callImmediatesPass (JSTimers.js:196)
    at Object.callImmediates (JSTimers.js:464)
    at MessageQueue.__callImmediates (MessageQueue.js:282)
    at MessageQueue.js:137
    at MessageQueue.__guard (MessageQueue.js:269)

Why is the response used next by default?

Hello! Many thanks to you for the work.
I have a question. Why is the response used next by default? In this case, the action with the request goes through the chain of middlewares only after redux-axios-middleware. Thus, if we do, for example, the middleware of authentification, we will have to:

  1. Divide the middleware fo authentification into two to process the request and response. Insert them before and after redux-axios-middleware.
  2. Override onSuccess and onError methods in options.
    Why not call dispatch by default?
    Thanks!

Allow passing options to action

Idea is to allow passing options like returnRejectedPromiseOnError to action.

{
  type: 'LOAD',
  payload: {
    request:{
      url:'/categories',
      options: {
        returnRejectedPromiseOnError: true
      }
    }
  }
}

This way user can override client options in middleware setup. Options will be merged:

default options => middleware options => action options

Where to pass onSuccess, onComplete, etc?

I am trying to have callbacks, but it's not working in the object:

    type: ORDER_SUBMIT,
    onSuccess({ getState, dispatch}){},
    payload: {
      request: {
        method: 'put',
        url: `/tickets/${orderData.orderTicketCode}`,
        data: {}
      },
      onSuccess({ getState, dispatch }) {
 
      },
      meta: {
        onSuccess({ getState, dispatch}) {
        }
      }
    }

Where is the correct location?

takeEvery/takeLatest are not catching *_SUCCESS or *_FAIL actions

For some reason when i have an action like USER/LOGIN, i can't do anything like

yield takeEvery('USER/LOGIN_SUCCESS', mySaga);

However,

yield takeEvery('USER/LOGIN', mySaga);

in my saga works just fine, but obviously at the moment request is dispatched, not successfully finished.
I've added a monitoring reducer and USER/LOGIN_SUCCESS is indeed dispatched, but takeEvery doesn't seem to care. Any ideas why?

setupedClient.client.request is not a function

Hi,

I'm getting ^ that error from::

const axios = require('axios');

module.exports = {
  client: axios.create({
      baseURL: 'http://localhost:5000',
      responseType: 'json',
      // timeout: 5000,
    }),
}

and in my action it looks like this::

export const GET_SHIT = 'GET_SHIT';

export function getShit() {
return  {
    type: GET_SHIT,
    payload: {
      request: {
        method: "get",
        url: "/getShit"
      }
    }       
  }
}

but i get an error that says ::

Uncaught TypeError: setupedClient.client.request is not a function
    at eval (eval at <anonymous> (bundle.js:19090), <anonymous>:210:37)
    at Object.eval [as getValue] (eval at <anonymous> (bundle.js:19930), <anonymous>:4:12)
    at GettingShit.componentDidMount (eval at <anonymous> (bundle.js:7864), <anonymous>:91:18)
    at CallbackQueue.notifyAll (eval at <anonymous> (bundle.js:6336), <anonymous>:76:22)
    at ReactReconcileTransaction.close (eval at <anonymous> (bundle.js:17683), <anonymous>:80:26)
    at ReactReconcileTransaction.closeAll (eval at <anonymous> (bundle.js:1804), <anonymous>:206:25)
    at ReactReconcileTransaction.perform (eval at <anonymous> (bundle.js:1804), <anonymous>:153:16)
    at batchedMountComponentIntoNode (eval at <anonymous> (bundle.js:6432), <anonymous>:126:15)
    at ReactDefaultBatchingStrategyTransaction.perform (eval at <anonymous> (bundle.js:1804), <anonymous>:140:20)
    at Object.batchedUpdates (eval at <anonymous> (bundle.js:17575), <anonymous>:62:26)
(anonymous) @ bundle.js?4fc5:210
(anonymous) @ bindActionCreators.js?d387:3
componentDidMount @ gettingShit.jsx?4b86:55
notifyAll @ CallbackQueue.js?1e54:76
close @ ReactReconcileTransaction.js?e1cc:80
closeAll @ Transaction.js?b216:206
perform @ Transaction.js?b216:153
batchedMountComponentIntoNode @ ReactMount.js?ccff:126
perform @ Transaction.js?b216:140
batchedUpdates @ ReactDefaultBatchingStrategy.js?1670:62
batchedUpdates @ ReactUpdates.js?89d4:97
_renderNewRootComponent @ ReactMount.js?ccff:320
_renderSubtreeIntoContainer @ ReactMount.js?ccff:401
render @ ReactMount.js?ccff:422
(anonymous) @ index.jsx?f515:42
(anonymous) @ bundle.js:7085
__webpack_require__ @ bundle.js:20
(anonymous) @ bundle.js:21865
__webpack_require__ @ bundle.js:20
(anonymous) @ bundle.js:66
(anonymous) @ bundle.js:69

the index file with this stuff is::

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';

import axiosMiddleware from 'redux-axios-middleware';
import promise from 'redux-promise';
import createMemoryHistory from 'history/createMemoryHistory';
import client from './utils/axiosconfig.js';

import store from './reducers/index.js';

const history = createMemoryHistory();

const createStoreWithMiddleware = applyMiddleware(axiosMiddleware(client), promise)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(store)}> 
etc.etc. 

is there something i'm doing wrong?

Promise not rejected on api error

Hi,
i'm trying to catch api error using axios-middleware
server is responding with 404 however, promise is resolved instead of beeing rejected.
note that it's wrapped with other promise:

this: is not working:

Keychain
  .getGenericPassword()
  .then((credentials) => {
    return this.props.authProps(credentials.password).catch(() => {
      // handle error
    })
  })

workaround for this is to check in then callback if there is error key

Keychain
  .getGenericPassword()
  .then((credentials) => {
    return this.props.authProps(credentials.password).then((payload) => {
      if(!payload.error) { 
        // handle error 
      }
  })

Actions are dispatched correctly to store, just problem with this promise

How i can do cross domain request?

I set "Access-Control-Allow-Origin: *", but always get a warning:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Update doc

Please update your README from this one:

const client = axios.create({ //all axios can be used, shown in axios documentation
  baseURL:'http://localhost:8080/api',
  responseType: 'json'
});

To this one:

const client = axios.create({ //all axios can be used, shown in axios documentation
  baseURL:'http://localhost:8080/api',
  options: {   
    responseType: 'json'
  }
});

As I see from this line you use options for Axios from client.options. So If I would like to use responseType:json I should put it to payload.options or client.options.

I just spent a lot of time on investigating why I receive the following error:

axios Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'response Type' is '' or 'text' (was 'json').

So the solution was just simple - put responseType:json to client.options.

How to use authentication headers?

First of all: thank you for creating this middleware!

Let's say I have a JWT token stored in the redux tree (fetched/generated on the login) that is needed for certain request to succeed - is it possible to somehow pass/use the token with this middleware?

[feature] caching w/ age param

What do you guys think about adding a caching option for requests? We could implement an option w/ the cache to bypass cache if last request is older than age or something along those lines... thoughts?

add webpack build for UMD

Can you implement or will you accept a PR adding a webpack build into the package for UMD support? This would be great for your package, for example, I wanted to realtime code demo the features today to a client using Cloud9 or Kobra.io but couldn't use this library with a script tag via https://unpkg.com/[email protected]/ because it's only built for commonJs.

Action as promise

This issue/question arose from my previous #67. I struggle to get the action returned as a promise. As there is no example that I know of, I wanted to ask how it should be configured properly. right now I have (using typescript)

export const actionCreators = {
    setCurrentRole: (id: number): AppThunkAction<KnownAction> => (dispatch, getState) => {
        dispatch(new SetCurrentRole({ request: { url: `role/${id}/setCurrent`, method: "PUT" } }));
    }
};

And then on my button I have

onClick={(e) => this.props.setCurrentRole(5)}

This however returns undefined (as it seems to be void function) not a promise as hinted by documentation

Props are injected by redux-connect. Code just for completness sake

export default connect<{
    roles: RolesStore.RolesState
},
    typeof RolesStore.actionCreators, SidebarAccountPropsCustom>(
    (state: ApplicationState) => ({
        roles: state.roles
    }), // Selects which state properties are merged into the component's props
    RolesStore.actionCreators                 // Selects which action creators are merged into the component's props
    )(SidebarAccount);

These are my middlewares:

var middlewares: Middleware[] = [axiosMiddleware(client, axiosMiddlewareOptions), thunk, typedToPlain];
    if (windowIfDefined) {
        const loggerMiddleware = (createLogger as any)();
        middlewares.push(loggerMiddleware);
        if (history) {
            middlewares.push(routerMiddleware(history));
        }
    }

axiosMiddlewareOptions just include setup for authentication.

Hope to get to the bottom of this, cheers!

How about an onRequest hook?

Hi

I am working on a notification system for my app and was able to use onError and onSuccess hooks to dispatch additional actions like this:

import {onSuccess as defaultOnSuccess, onError as defaultOnError} from 'redux-axios-middleware/lib/defaults'

export const onSuccess = ({ action, next, response }, options) => {
  if (action.messages && action.messages[1]) {
    let message = action.messages[1]
    if (typeof message == 'function') {
      message = message(action)
    }
    next(notify(message, 'success'))
  }
  return defaultOnSuccess({action, next, response}, options)
};

export const onError = ({ action, next, error }, options) => {
  if (action.messages && action.messages[2]) {
    let message = action.messages[2]
    if (typeof message == 'function') {
      message = message(action)
    }
    next(notify(message, 'error'))
  }
  return defaultOnError({action, next, error}, options)
};

However, I cannot find any kind of onRequest handler to display the loading message. I can add another middleware, but it seems that it would make sense for redux-axios-middleware to have this

How to handle _FAIL actions

I am following the same pattern used in the middleware-netcore-example. I see _FAIL constants in the app.ts actions file. I am attempting to send failures into state, but I am getting my wires crossed.

state:
export type App = { readonly values: { name: string, value: number }[] _readonly errors: [{ message: string, code: number }]_ };

action:
export type Action = { type: 'GET_VALUES_FAIL', payload: AxiosResponse } | ... };

reducer:
case Actions.GET_VALUES_FAIL: return { ...state, _errors_: action.payload.data };

Ideally i would map the fail action to a list of errors in the state..my app i have a error message action that ties into App.errors. Just not sure now to move forward given the errors live in a different object in state. How can i add this error data to my state?

Improve Documentation (example)

I'm struggling to understand the correct way to utilize this library. If I need to parse the returned response and do any sort of extraction/validation ... it seems, I have to handle response logic within the reducers (which doesn't seem right)? Moreover, I notice there are onSuccess/onError functions but no documentation on how or where to define and whether or not they are global on every request or per request (are they part of the middleware config or action). Overall, this needs improved documentation, is there any example application that utilizes this library so I can see if this fits the teams needs or whether to create our own middleware for handling request headers and JWT tokens?

No action after request finished.

When i dispatch action with type and request everything seems ok. I am transforming request with transformRequest and after request finish onSuccess and onError methods working well. But there is no action fires with '_SUCCESS' or '_ERROR' suffix. What could be my mistake?

return {
        type: types.POST_LOGIN,
        payload: {
            request: {
                url: '/users/login',
                method: 'POST',
                data: { username, password }
            }
        }
    }


onSuccess: function (app) {

        if (!app.response.data.Response && app.response.data.Header && app.response.data.Header.errors) {
            return app.dispatch(actions.apiError(app.response.data.Header.errors));
        }

        app.dispatch(actions.ajaxReponse(app.response.data));
    }

action Object {type: "AJAX_RESPONSE", data: Object}

set token

I'm using your middleware. But, when I use the action it does not work, says no token:

import axios from "axios";
import Cookie from "js-cookie";
import { API_BASE_URL } from '../config';

let token = Cookie.get('token') ? Cookie.get('token') : '';

export const api = axios.create({
    baseURL: API_BASE_URL,
    headers: {'Authorization': token}
});

export default api;

action:

export function userInfo() {
    return {
        type: types.USER_INFO,
        payload: {
            request: {
                url: `/user-info/`,
                method: "get"
            }
        }
    };
}

in the login component I put the Cookie and go to the main page:

Cookie.set('token', 'Token '+ response.payload.data.token);
 this.context.router.history.push('/');

And already on the main page do not work action. I can see Cookies in Aplication -> Cookies.
Only after rebooting the page everything works.

Can have to restart middleware config?
Thanks :)

[Question] how to use this together with redux-thunk?

Hi, I'm currently looking for a way to reduce much duplicate code from my action creators and I think this project could help me.

I'm currently using redux-thunk for all my async action creators. Is it possible to dispatch a standard action when a request fails?

Middleware options doesn't seems to work

I have the following code.

import axios from 'axios'
import axiosMiddleware from 'redux-axios-middleware'

const client = axios.create({
  baseURL: 'http://api.dev',
  responseType: 'json'
})

const options = {
  successSuffix: 'success',
  errorFix: 'error'
}

export default axiosMiddleware(client, options)

When I dispatch an action as { type: 'load' } then I get { type: 'load_FAIL' }. I expected to get { type: 'load_error' }.

Possible Unhandled Promise Rejection (id: 0)

Hi Guys thanks for the middleware library its really helpful.

Am facing issue when I receive success response from the server.

_FAIL works absolutely fine without error. But,

_SUCCESS is called and response is received as expected, but am getting a warning saying "possible unhandled promise Rejection (id: 1) TypeError: null is not a object (evaluating 'action.type') getStateForAction)"

Is it something you already faced, because promise is generated only by the middleware library for the action am performing.

Here is the config info:

const client = axios.create({ //all axios can be used, shown in axios documentation
    baseURL: 'http://192.168.5.10/CoreServices/',
    responseType: 'json'
});

const options = {
    interceptors: {
        request: [{
            success: ({getState, dispatch, getSourceAction}, req) => {
                if (getState() && getState().sessionReducer && getState().sessionReducer.token) {
                    req.headers['Authorization'] = 'Bearer ' + getState().sessionReducer.token
                }
                dispatch({
                    type: SHOW_LOADER
                });

                return req
            },
            error: ({getState, dispatch, getSourceAction}, req) => {
                dispatch({
                    type: HIDE_LOADER
                });

                return Promise.reject(req);
            }
        }],
        response: [{
            success: ({getState, dispatch, getSourceAction}, res) => {
                store.dispatch({
                    type: HIDE_LOADER
                });
                return res;
            },
            error: ({getState, dispatch, getSourceAction}, res) => {
                store.dispatch({
                    type: HIDE_LOADER
                });
                return Promise.reject(res);
            }
        }]
    }
};


switch (action.type) {
    case `${LOGIN}`:
        return {...state}
        break;
    case `${LOGIN}_SUCCESS`:
        return {...state, isLoggedIn: true}
        break;
    case `${LOGIN}_FAIL`:
        console.log('login failed!');
        if(action.error &&
            action.error.response &&
            action.error.response.data &&
            action.error.response.data.errors &&
            Array.isArray(action.error.response.data.errors) &&
            action.error.response.data.errors.length > 0) {
            return {...state, isLoggedIn: false, error: action.error.response.data.errors[0].message}
        } else {
            return {...state, error: 'test error from backend'}
        }
        break;
    case RESET_AUTHENTICATION_ERROR:
        return {...state, error: null}
        break;
    default:
        return {...state}
}

Please let me know if you have some info about this.

  • Dilip.

please provide example using middlewareConfig

My GET is failing with redux-axios-middleware but when I make a simple call with what I believe is the same URL but with just an axios.get, it works. I think an interceptor will help me but I don't understand how to use it.

It would be really helpful if you could update the readme to include usage of the middlewareConfig you define in the readme.

Error never get into catch section after calling an actions

Hi,

At docs I read at the section

Actions that are handled by this middleware return a promise. This gives you the ability to chain actions. A good example of this might be a form. In the form you might dispatch an actions to store the form values. The normal flow of the action into the reducers would not be altered but you can chain a then/catch onto the initial dispatch.

this.props.saveForm(formData)
  .then(() => {
    // router the user away
    this.context.router.push("/my/home/page")
  })
  .catch((response) => {
    //handle form errors
  })

So , I want to tried with my simple form handler to handle error section

handleFormSubmit(data) {
        this.props
            .signIn(data)
            .then((response) => {

            })
            .catch(response => {
                console.log(response);
            });
    }

reducer:

export function signIn({ email, password }) {
    return {
        type: 'LOGIN',
        payload: {
            request: {
                url: 'auth/login',
                method: 'POST',
                data: { email, password },
            },
        },
    };

I want to tried error response to see if its working properly. But it never console it out (422, 401) response.

But If I move my console.log to then section it will print it out properly.

Object {type: "LOGIN_FAIL", error: Error: Request failed with status code 422, meta: Object}

Why It enter to success response instead of catch section (I removed all my response interceptor)?.

intercepting: getSourceAction in response is returning undefined

Is this intended to work only for the request ? On my side calling getSourceAction by passing the response is returning undefined. Here is a sample snippet:

response: [
      {
        success: ({ dispatch, getSourceAction }, response) => {
          console.log(getSourceAction(response))
          return Promise.resolve(response)
        },
        error: ({ dispatch, getSourceAction }, error) => {
          console.log(getSourceAction(error))
          return Promise.reject(error)
        }
      }
    ]

What I'm trying to accomplish is refreshing of a token when it expires and server returns that request is not authorized.

Here is an example how this could be accomplished using axios interceptors: https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview

Examples repository

Hello,

I recently created a repository which demonstrate how to use redux-axios-middleware using a typescript/react frontend and a .NET Core backend.

I think it could be interesting to give a link to this repository to people who want to see how it looks with a pure example. What do you think?

Please take a look here : https://github.com/Odonno/react-redux-axios-middleware-netcore-example

PS : More examples are coming but you can raise an issue if you see any mistake or if you have any suggestion.

Full example App

Could someone provide a full example App using redux axios middleware?

Send filtered response to reducer as payload?

Can we influence what the response returns as payload to the action?

For example I'm querying an API that returns a result with way more information that I needed, and I only want to use a single piece. Can I somehow filter after successfully receiving the raw response data, and sending only the data I need to the reducer with the _SUCCESS action?

Is it possible to send custom headers?

we're trying to send custom headers for NTML authentification purposes. I've tried this

const client = axios.create({ //all axios can be used, shown in axios documentation baseURL: 'http://1.2.3.4:8080', options: { responseType: 'json', SamAccount: 'username' } });

and
const client = axios.create({ //all axios can be used, shown in axios documentation baseURL: 'http://1.2.3.4:8080', options: { responseType: 'json', headers: { 'SamAccount': 'username' }; } });

but we're not seeing the headers change. the options property seems to be how it is done in the axios documentation... are there any other things that we should try?

Dispatch new action when async action is succeeded.

Lets say that i have login action like following.

export function login(credentials) {
	return {
		types: ['LOGIN','LOGIN_SUCCESS','LOGIN_ERROR'],
		payload: {
		request: {
			url: '/api/login',
			method: 'POST',,
			data: credentials
		}
		}
	}
}

When login is succeeded, i want to dispatch new action doAnother.
How can i do that?

Do i need to do that in reducer when action.type = LOGIN_SUCCESS?
I guess its anti-pattern, and there must be more redux-like approach.

Intercept response error without handling it

I would like to be able to intercept a response error with a response error interceptor and still get the ${requestAction}_FAIL action to be triggered. But it seems that the error just gets swallowed up whenever I add such an interceptor. Am I missing something?

For the moment, I'm just using onError handler like this:

const middlewareConfig = {
  onError: ({ action, error, next, dispatch }) => {
    if (error.response.status === 401) {
      dispatch(AuthActions.logout());
    }
    // propagate failure for further handling
    const nextAction = {
      type: `${action.type}_FAIL`,
      error,
      meta: {
        previousAction: action,
      },
    };
    next(nextAction);
    return nextAction;
  },
};

Why not export the `SUCCESS_SUFFIX` and `ERROR_SUFFIX` in `index.js`?

I noticed in the document the default success and error suffix for action types are mentioned and exported from getActionTypes.js. But in index.js the 2 default suffix constants are not exported.

This makes developers to maintain 2 more hardcode constant in their code. However I wish I could use the default constants rather than hardcode.

OAuth2 Implementation

Hi,

I was wondering how I could easily setup an interceptor that adds to requests headers an Authorization token excepted for some (like the one used to retrieve the token, etc..).

I'm asking you this since I couldn't find any way that allows the request intereceptor to get the proper action.

interceptors : {
      request: [({ getState, dispatch, action }, config) => {

No matter what action is performed, only the first one called will persist here (in my case the AUTH action if i'm not already signed in).

I've read on different issue that this is related to the client instance, but honestly I'm totally lost now and I can't figure out what to do.

I'd really appreciate some help!
Thank for your great work too!

Regards,

getState() is cached no matter that store was changed

As the redux-axios-middleware is caching configuration per name of the client it's not possible different store to be passed for testing unless different client is created for each test which sounds overwhelming.

Here is a sample test that illustrates the problem:

import axios from 'axios';
import axiosMiddleware from '../src/middleware';

import { expect } from 'chai';
import configureMockStore from 'redux-mock-store';
import MockAdapter from 'axios-mock-adapter';

const options = {
  returnRejectedPromiseOnError: true,

  interceptors: {
    request: [
      ({ getState, dispatch }, config) => {
        console.log('state in interceptor: ', getState());
        config.headers['Authorization'] = 'Bearer ' + getState().access_token;
        return config;
      }
    ],
    response: [
      {
        success: ({ dispatch }, response) => {
          return response;
        },
        error: ({ dispatch, getSourceAction }, error) => {
          return Promise.reject(error);
        }
      }
    ]
  }
};

const middleware = axiosMiddleware(axios, options);

describe('axiosMiddleware', () => {
  const mockAxiosClient = new MockAdapter(axios);
  const mockStore = configureMockStore([middleware]);
  const mockAdapter = mockAxiosClient.adapter();

  afterEach(() => {
    mockAxiosClient.reset();
  });

  after(() => {
    mockAxiosClient.restore();
  });

  it('attaches authorization header on each request', () => {
    let got;

    mockAxiosClient.onGet('/test').reply(config => {
      got = config.headers['Authorization'];
      return [200];
    });

    const action = () => {
      return {
        type: 'LOAD',
        payload: {
          request: {
            url: '/test'
          }
        }
      };
    };

    const store = mockStore({ access_token: '::access_token::' });

    return store.dispatch(action()).then(() => {
      expect(got).to.equal('Bearer ::access_token::');
    });
  });


  it('attaches another authorization header on each request', () => {
    let got;

    mockAxiosClient.onGet('/test2').reply(config => {
      got = config.headers['Authorization'];
      return [200];
    });

    const action = () => {
      return {
        type: 'ANOTHER_LOAD_ACTION',
        payload: {
          request: {
            url: '/test2'
          }
        }
      };
    };

    const store = mockStore({ access_token: '::another_access_token::' });

    return store.dispatch(action()).then(() => {
      expect(got).to.equal('Bearer ::another_access_token::');
    });
  });
});

The following test fails with:

  1) axiosMiddleware attaches another authorization header on each request:

      AssertionError: expected 'Bearer ::access_token::' to equal 'Bearer ::another_access_token::'
      + expected - actual

      -Bearer ::access_token::
      +Bearer ::another_access_token::

e.g the test of first test is used as it caches the store instance that was passed no matter that another store is created using mockStore.

As better feedback I've added few loggings in the interceptor too:

return ({ getState, dispatch }) => next => action => {
    if (!middlewareOptions.isAxiosRequest(action)) {
      return next(action);
    }
    console.log(`action: ${action.type}, store value: `, getState());

and this is the execution log:

  axiosMiddleware
action: LOAD, store value:  { access_token: '::access_token::' } -> this is in the middleware
state in interceptor:  { access_token: '::access_token::' } -> this is in the interceptor 
    ✓ attaches authorization header on each request
action: ANOTHER_LOAD_ACTION, store value:  { access_token: '::another_access_token::' } -> this is in the middleware
state in interceptor:  { access_token: '::access_token::' } -> this is in the interceptor

Uncaught (in Promise) issue

When the API return an error, return Promise.reject(newAction); this line throw an uncaught exception. Which need to be caught like this store.dispatch(someAsyncAction).catch(err => err) otherwise it will throw an Uncaught (in Promise) error. I think return newAction is good enough there.

return client.request(options.getRequestConfig(action))
      .then(
        (response) => {
          const newAction = options.onSuccess({ action, next, response, getState, dispatch }, options);
          options.onComplete({ action: newAction, next, getState, dispatch }, options);

          return newAction;
        },
        (error) => {
          const newAction = options.onError({ action, next, error, getState, dispatch }, options);
          options.onComplete({ action: newAction, next, getState, dispatch }, options);
          return Promise.reject(newAction);
        });

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.