最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Redux: Are only synchronous calls allowed from reducer functions? - Stack Overflow

programmeradmin0浏览0评论

I have a reactJs app and right now I'm learning Redux to use it as Flux implementation.

I've created a store and I have created my first reducer function but now I have some questions that e to my mind, please help me to understand.

As you can see I have an action called 'FIND_PRODUCTS' which is basically fetching data from a backend service. To call this backend service I use basically an asynchronous ajax call, so basically the problem I'm facing is that the state is returned from the reducer function before my backend call has finished, so then the state is not updated correctly and the subscribers to the store are getting incorrect data. This problem is solved if I switch to a synchronous call, but then, the first warning I get is that synchronous call should be avoided because it might decrease the user's experience (performance).

So my question, can we only fetch data synchronously from a reducer function? Should the fetching data happens in the reducer function or there is another way to do that? if so, what is it?

Does this model of redux of having a single object tree to maintain the state scales well with large applications? If I have 1000 actions the switch in my reducer function will be huge! How can we avoid that?

Thank you!!

const initialState = {
availableLocales: [{text: 'En'}, {text: 'Es'}, {text: 'Fr'}],
selectedLocale: 'En',
translations: i18n.getTranslations(),
products: []
};


const reducer = (state = initialState, action = {type: 'NONE'})=> {

//To make the reducer a pure function
deepFreeze(state);
deepFreeze(action);
switch (action.type) {
    case 'SWITCH_LOCALE':
        let newState = Object.assign({}, state, {
            selectedLocale: action.locale,
            translations: i18n.getTranslations(action.locale)
        });
        return newState;
    case 'FIND_PRODUCTS':
        let newState = Object.assign({}, state, {
            products:ProductHelper().findProductsByProductType(action.productType)
        });
        return newState;

    default:
        return state
}
return state;
}

// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
const store = createStore(reducer);

// You can subscribe to the updates manually, or use bindings to your view  layer.
store.subscribe(() =>
    console.log(store.getState())
);

export default store;

I have a reactJs app and right now I'm learning Redux to use it as Flux implementation.

I've created a store and I have created my first reducer function but now I have some questions that e to my mind, please help me to understand.

As you can see I have an action called 'FIND_PRODUCTS' which is basically fetching data from a backend service. To call this backend service I use basically an asynchronous ajax call, so basically the problem I'm facing is that the state is returned from the reducer function before my backend call has finished, so then the state is not updated correctly and the subscribers to the store are getting incorrect data. This problem is solved if I switch to a synchronous call, but then, the first warning I get is that synchronous call should be avoided because it might decrease the user's experience (performance).

So my question, can we only fetch data synchronously from a reducer function? Should the fetching data happens in the reducer function or there is another way to do that? if so, what is it?

Does this model of redux of having a single object tree to maintain the state scales well with large applications? If I have 1000 actions the switch in my reducer function will be huge! How can we avoid that?

Thank you!!

const initialState = {
availableLocales: [{text: 'En'}, {text: 'Es'}, {text: 'Fr'}],
selectedLocale: 'En',
translations: i18n.getTranslations(),
products: []
};


const reducer = (state = initialState, action = {type: 'NONE'})=> {

//To make the reducer a pure function
deepFreeze(state);
deepFreeze(action);
switch (action.type) {
    case 'SWITCH_LOCALE':
        let newState = Object.assign({}, state, {
            selectedLocale: action.locale,
            translations: i18n.getTranslations(action.locale)
        });
        return newState;
    case 'FIND_PRODUCTS':
        let newState = Object.assign({}, state, {
            products:ProductHelper().findProductsByProductType(action.productType)
        });
        return newState;

    default:
        return state
}
return state;
}

// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
const store = createStore(reducer);

// You can subscribe to the updates manually, or use bindings to your view  layer.
store.subscribe(() =>
    console.log(store.getState())
);

export default store;
Share Improve this question edited Mar 2, 2016 at 12:35 fgonzalez asked Mar 2, 2016 at 12:28 fgonzalezfgonzalez 3,8877 gold badges56 silver badges83 bronze badges 2
  • Can't the ponent call the findProduct async function on the ponentWillMount method? – Raulucco Commented Mar 2, 2016 at 12:47
  • Sure, but then what we need the store for? I thought the whole point of this was to move the state away from ponents to the store.. – fgonzalez Commented Mar 2, 2016 at 13:05
Add a ment  | 

4 Answers 4

Reset to default 5

Consider this:

Create actions.js file and export the actions functions like this:

import * as types from '../constants/action_types';
import * as api from '../utils/api'

export function something1(someId){

    return (dispatch) => {

        dispatch({type: `${types.SOMETHING1}_PENDING`});

        api.getSomething(someId)

            .then((res) => {
                dispatch({
                    type: `${types.SOMETHING1}_SUCCEEDED`,
                    somethings: res.body
                });

            .catch((err) => {

                dispatch({
                    type: `${types.SOMETHING1}_FAILED`,
                    errors: err.body
                })

            });
    }
} 

export function something2(someOtherId){

    return (dispatch) => {

        dispatch({type: `${types.SOMETHING2}_PENDING`});

        api.getSomething2(someOtherId)

            .then((res) => {
                dispatch({
                    type: `${types.SOMETHING2}_SUCCEEDED`,
                    otherThings: res.body
                });

            .catch((err) => {

                dispatch({
                    type: `${types.SOMETHING2}_FAILED`,
                    errors: err.body
                })

            });
    }
} 

Then the state only change when you have the data

Next separate your reducers in separate files and create one file to export them all like this reducers/index.js:

export { default as reducer1 } from './reducer1';
export { default as reducer2 } from './reducer2';
export { default as reducer3 } from './reducer3';
export { default as reducer4 } from './reducer4';

Then config your store like this:

configure_store.js

import { createStore, bineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from '../reducers';

const rootReducer = bineReducers(reducers);
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);

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

Finally add this to your root:

import configureStore from '../store/configure_store';

const store = configureStore();

class Root extends Component {

    render() {
        return (
            ...
            <Provider store={ store } >
            ...
            </Provider>
        );
    }
}

export default Root;

First, you CAN'T fetch data in reducer, because it needs to be pure by redux definition. You should create action creator, that would fetch data asynchronously and pass it to reducer. Actions CAN be impure.

Here you can read more http://redux.js/docs/advanced/AsyncActions.html

Also you can use middleware like redux-thunk to simplify this. https://github./gaearon/redux-thunk


As for the second question, you can have more than one reducer in your app. and than bine them with bineReducers(...) function http://redux.js/docs/basics/Reducers.html

As redux documentation said, reducers should be pure functions, so it shouldn't do ajax requests.

Better way to do so is use redux-thunk middleware, that allows you to call dispatch several times in one action.

So, in your example you do something like this:

// definition of action creator
function loadProducts(productType) {
  return {type: 'FIND_PRODUCTS', productType: productType}
}

...
// calling dispatch of your action
dispatch(loadProducts(productType));

But with redux-thunk your action creator will be something like this:

function loadProducts(productType) {
  return function(dispatch){
    dispatch({type: 'FIND_PRODUCT_STARTED'});
    // I don'h know how findProductsByProductType works, but I assume it returns Promise
    ProductHelper().findProductsByProductType(productType).then(function(products){
      dispatch({type: 'FIND_PRODUCT_DONE', products: products});
    });
  }
}

And your reducer will bee pure function:

...
case 'FIND_PRODUCTS_DONE':
  let newState = Object.assign({}, state, {
      products: action.products,
  });
  return newState;
...

In this case you can also handle loading state, i.e. set loading flag in your state to true when action.type is FIND_PRODUCT_STARTED.

In my example I assume that findProductsByProductType returns Promise. In this case you can even use redux-promise-middleware without redux-thunk, it will do all work for you:

function loadProducts(productType) {
  return {
    type: 'FIND_PRODUCT',
    payload: {
      promise: ProductHelper().findProductsByProductType(productType)
    }
  }
}

You should not use ProductHelper() in your reducer to request data.

Instead, you should use an action creator to dispatch an action that requests the data from your API. Your API middleware would return a promise that on pletion would dispatch an action intent with payload for your reducer to consume and for it to return the next state.

I remend you look at Redux Thunk and Redux API middleware

发布评论

评论列表(0)

  1. 暂无评论