Git Product home page Git Product logo

redux-effects-fetch's Introduction

redux-effects

Virtual DOM for effects and impurities. You write pure functions, redux-effects handles the rest.

Installation

npm install redux-effects

Benefits

  • Trivial universality. If your effect middleware is universal, your app is universal.
  • Powerful meta-programming facilities (e.g. request caching)
  • More testable code
  • Better insights into what is happening in your application (e.g. logging effects)
  • Better ability to serialize state

Usage

Add redux-effects anywhere in your redux middleware stack*:

import effects from 'redux-effects'
import fetch from 'redux-effects-fetch'
import cookie from 'redux-effects-cookie'
import location from 'redux-effects-location'

applyMiddleware(effects, fetch, cookie, location)

* It used to need to come first, but that is no longer the case

In action creators

All effectful action creators should return a declarative object describing the effect to be done, even if their operation is normally synchronous*. In order to operate on the values returned by these actions, you need to specify your handlers in .meta.steps in the action object. This library comes with a composition utility bind, but you are free to write your own such as declarative-promise.

*E.g. Math.random or accessing cookies. These values are also impure, even though they are synchronous, because they are non-deterministic with respect to your function's parameters.

import {bind} from 'redux-effects'
import {cookie} from 'redux-effects-cookie'
import {createAction} from 'redux-actions'

function checkAuth () {
  return bind(cookie('authToken'), setAuthToken)
}

const setAuthToken = createAction('SET_AUTH_TOKEN')

The values returned by your actions will be passed to the handlers specified on your action object. Then, what your handlers return will be dispatched back into redux to either trigger other effects or be preserved in state. If your functions do not return a value, they do not do anything. Their only ability to effect the world is by ultimately dispatching actions that trigger state reducers, or by triggering effects that cause state to be preserved elsewhere (e.g. a POST request to an API server).

Effect composition

Effects compose by placing steps in .meta.steps on the action object. E.g.

{
  type: 'EFFECT_COMPOSE',
  payload: {
    type: 'FETCH'
    payload: {url: '/some/thing', method: 'GET'}
  },
  meta: {
    steps: [
      [success, failure]
    ]
  }
}

Since this is cumbersome to write out, there are libraries / action creators to help with it:

But it is important to understand that ultimately these libraries just produce plain JS objects, and you are totally free to create your own composition interfaces that behave exactly the way you want if you don't like these. There is nothing magical going on.

Writing effectful middleware

Effects middleware look essentially just like regular redux middleware, except that it must return a promise. If it does not return a promise, it won't compose with other effects, and so won't be very useful to anyone.

As a general rule, your effect middleware should be as dumb as possible. There are two reasons for this:

  • Effect middleware is inherently impure, and therefore difficult to test and reason about
  • Keeping complexity out of the effect middleware ensures it is as generally useful as possible, and as open as possible to abstraction and metaprogramming.

Example - Simple cookie middleware

import cookie from 'component-cookie'

export default function ({dispatch, getState}) {
  return next => action => {
    if (action.type !== 'COOKIE') {
      return next(action)
    }

    switch (action.verb) {
      case 'set':
        return Promise.resolve(cookie(action.name, action.value))
      case 'get':
        return Promise.resolve(cookie(action.name))
    }
  }
}

Example - Universal cookie middleware

import _cookie from 'component-cookie'

export default function (cookieMap) {
  return ({dispatch, getState}) => next => action => {
    if (action.type !== 'COOKIE') {
      return next(action)
    }

    switch (action.verb) {
      case 'set':
        return Promise.resolve(cookie(action.name, action.value))
      case 'get':
        return Promise.resolve(cookie(action.name))
    }
  }

  function cookie (name, value) {
    if (arguments.length === 2) {
      if (cookieMap) cookieMap[name] = value
      else _cookie(name, value)
    }

    return cookieMap ? cookieMap[name] : _cookie(name)
  }
}

With this form, you can simply initialize your cookie middleware with a map of cookies. E.g.

function (req, res, next) {
  req.store = applyMiddleware(effects(cookie(req.cookies)))(createStore)
}

Metaprogramming

Where this approach gets really interesting is when you start applying transformations to your effects. Normally these things are implemented in disparate and often hacky ways. But when you have declarative descriptions of all of your effects, you can unify your transformations into your redux middleware stack, and they can be completely orthogonal to the actual implementations of the effects themselves. Here are some examples:

Request caching

function httpCache () {
  const {get, set, check} = cache()

  return next => action =>
   !isGetRequest(action)
      ? next(action)
      : check(action.payload.url)
        ? Promise.resolve(get(action.payload.url))
        : next(action).then(set(action.payload.url))
}

Response normalization

function normalize () {
  return next => action =>
    isGetRequest(action)
      ? next(action).then(normalizr)
      : next(action)
}

Note that while these examples both transform http requests, they are completely orthogonal to the actual implementation of those requests, and completely orthogonal to the action creator interface you choose to use to generate your descriptors. That means you can:

  • Swap out your http request implementation
  • Change your action creator interface
  • Use a different effect composition strategy
  • ...And most importantly, compose other transformations

And not have to change your transform middleware at all.

Ecosystem

Effect drivers

Plugins that enable various effects:

Alternate composition middleware

Composition helpers

redux-effects-fetch's People

Contributors

ashaffer avatar iamdustan avatar lazyd3v avatar muuki88 avatar ruprict avatar trevorgerhardt 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

Watchers

 avatar  avatar  avatar  avatar

redux-effects-fetch's Issues

Question: composing fetches?

I am currently looking for a way to fire off numerous fetches and have them bound to a single set of error/success handlers.

// standard Promises and browser fetch
var a = fetch('http://www.some.api/resource');
var b = fetch('http://www.another.api/resource');

return Promise.all([a, b]).then(success, failure);
// something like this maybe hopefully with bind-effects and effects-fetch?
bind(
  [fetch('http://www.some.api/resource'),
   fetch('http://www.another.api/resource')],
  onSuccess,
  onError
);

Tests

A lot of thanks for your library. Have you receipt how to write integration tests for that in browser? Tried to use https://www.npmjs.com/package/fetch-mock, but it doesn't work. Because it requires 'fetch should be added as a global'. Thanks in advance!

Update Readme: applyMiddleware info

Could you please update Readme. Change applyMiddleware info with
applyMiddleware(effects, fetch)(createStore).
Because applyMiddleware(effects(fetch))(createStore) doesn't work properly. Thanks!

Getting Actions must be plain objects Error?

I tried setting up redux effects fetch and it seems the middleware is not handling the effect? Any clues.

import { createAction, handleActions } from 'redux-actions'
import effects, { bind } from 'redux-effects'
import fetch, { fetchEncodeJSON } from 'redux-effects-fetch'
import createLogger from 'redux-logger'
import { combineReducers, createStore, applyMiddleware } from 'redux'

const loginSuccess = createAction('LOGIN_SUCCESS')
const loginFailure = createAction('LOGIN_FAILURE')
const signupUser = (user) => {
   return bind(fetch('/user', {
    method: 'POST',
    body: user
   }), ({value}) => loginSuccess(value), ({value}) => loginFailure(value))
}


const authState = {
  token: null,
  user: {
    name: '',
    age: '',
  },
}

const authActions = {
  userSignedUp: createAction('USER_SINGNED_UP'),
  userDidLogin: createAction('USER_DID_LOGIN'),
  setError: createAction('SET_ERROR'),
}

const authReducer = (state = authState, action) => {
  switch (action.type) {
    default:
      return state
  }
}


let store = createStore(combineReducers({ auth: authReducer }), { auth: authState }, compose(applyMiddleware(effects, fetch, fetchEncodeJSON, createLogger())));
console.log(store.getState())
console.log(store.dispatch(signupUser({ name: 'test' })))
{ auth: { token: null, user: { name: '', age: '' } } }
action @ 11:09:02.443 undefined
%c prev state color: #9E9E9E; font-weight: bold { auth: { token: null, user: { name: '', age: '' } } }
%c action color: #03A9F4; font-weight: bold function (next) {
    return function (action) {
      return action.type === FETCH ? g().fetch(action.payload.url, action.payload.params).then(checkStatus).then(createResponse, createErrorResponse) : next(action);
    };
  }
%c error color: #F20404; font-weight: bold Error: Actions must be plain objects. Use custom middleware for async actions.
    at dispatch (/home/pyros2097/Code/rad/node_modules/redux/lib/createStore.js:166:13)
    at /home/pyros2097/Code/rad/node_modules/redux-logger/lib/index.js:103:29
    at /home/pyros2097/Code/rad/node_modules/redux-effects-fetch/lib/fetchEncodeJSON.js:41:79
    at /home/pyros2097/Code/rad/node_modules/redux-effects-fetch/lib/index.js:36:153
    at /home/pyros2097/Code/rad/node_modules/redux-effects/lib/index.js:34:71
    at dispatch (/home/pyros2097/Code/rad/node_modules/redux/lib/applyMiddleware.js:45:18)
    at maybeDispatch (/home/pyros2097/Code/rad/node_modules/redux-effects/lib/index.js:63:22)
    at composeEffect (/home/pyros2097/Code/rad/node_modules/redux-effects/lib/index.js:39:23)
    at Object.dispatch (/home/pyros2097/Code/rad/node_modules/redux-effects/lib/index.js:34:47)
    at Object.<anonymous> (/home/pyros2097/Code/rad/test/index.js:41:19)
%c next state color: #4CAF50; font-weight: bold { auth: { token: null, user: { name: '', age: '' } } }
—— log end ——
/home/pyros2097/Code/rad/node_modules/redux-logger/lib/index.js:119
        if (logEntry.error) throw logEntry.error;
                            ^

Error: Actions must be plain objects. Use custom middleware for async actions.
    at dispatch (/home/pyros2097/Code/rad/node_modules/redux/lib/createStore.js:166:13)
    at /home/pyros2097/Code/rad/node_modules/redux-logger/lib/index.js:103:29
    at /home/pyros2097/Code/rad/node_modules/redux-effects-fetch/lib/fetchEncodeJSON.js:41:79
    at /home/pyros2097/Code/rad/node_modules/redux-effects-fetch/lib/index.js:36:153
    at /home/pyros2097/Code/rad/node_modules/redux-effects/lib/index.js:34:71
    at dispatch (/home/pyros2097/Code/rad/node_modules/redux/lib/applyMiddleware.js:45:18)
    at maybeDispatch (/home/pyros2097/Code/rad/node_modules/redux-effects/lib/index.js:63:22)
    at composeEffect (/home/pyros2097/Code/rad/node_modules/redux-effects/lib/index.js:39:23)
    at Object.dispatch (/home/pyros2097/Code/rad/node_modules/redux-effects/lib/index.js:34:47)
    at Object.<anonymous> (/home/pyros2097/Code/rad/test/index.js:41:19)

Updating Readme.md

I would like to propose to write redux-effects-fetch uses isomorphic-fetch. I've felt nervous like 'hmm.. it may use fetch of es6. is it ok for any browsers? (after opening the source code) oh, great! Redux-effects-fetch takes isomorphic-fetch!'

self is not defined

When using this library alongside React Native v0.31.0-rc.1 I get Can't find variable: self on the debugger and the android log says

E/ReactNativeJS( 6043): Can't find variable: self
W/ReactNativeJS( 6043): Unable to symbolicate stack trace: undefined is not a function (evaluating 'fetch(devServer.url+'symbolicate',{
W/ReactNativeJS( 6043): method:'POST',
W/ReactNativeJS( 6043): body:JSON.stringify({stack:stack})})')
E/ReactNativeJS( 6043): Module AppRegistry is not a registered callable module.
W/ReactNativeJS( 6043): Unable to symbolicate stack trace: undefined is not a function (evaluating 'fetch(devServer.url+'symbolicate',{
W/ReactNativeJS( 6043): method:'POST',
W/ReactNativeJS( 6043): body:JSON.stringify({stack:stack})})')

I think is somewhat related to this, yet I haven't been able to fix it, any ideas?

Consider erroring if response object has an error key

Would you be open to throwing to an error status if a given json response has an error key? Something like the following chained onto the end of the fetch thens:

function checkErrorKey (maybeJson) {
  if (maybeJson.error) throw maybeJson.error;
  return maybeJson;
}

Other types for deserialization

We deserialize application/octet-streams via response.arrayBuffer(). I doubt that would trample anyone if they were looking for a text response. It'd be an easy addition to the deserialization function if that's a convention that we want to use.

Is this repo still maintained? Can I send a PR to fix it for React Native? 😊

Hi,

We use this library at Walmart. And now we're trying to use it within our React Native codebase, but unfortunately, it's not working there due to isomorphic-fetch, which is not compatible with React Native.

One way to solve the problem is to modify the package.json of redux-effects-fetch and map isomorphic-fetch to a stub file that basically does nothing (since React Native doesn't need a fetch polyfill).

So, the package.json would be modified to include the following

"react-native": {
  "isomorphic-fetch": "./rn-isomorphic-fetch.js"
}

And the content of rn-isomorphic-fetch.js would simply be:

export default {}

...and by doing that, we fix redux-effects-fetch for React Native apps.

This change would only affect React Native apps because only React Native Packager cares about the react-native key in package.json. webpack and other bundlers ignore this key.

If I send a PR with these changes, would you be able to merge it and publish a new version?

Thanks a lot!

MK

Return the response object as part of the result

I'm particularly thinking about the response status, which may be useful to handle the response (is it a 401, a 422 or a 500?), but perhaps some other information about the response could be handy for different use cases.

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.