Battle_Slug
Battle_Slug

Reputation: 2105

Dispatch(action) changes the store state but component is not updated

I have pretty simple code, the idea is to fetch some data via ajax and make them available for the whole life of app. I need it this way because I use the Index component in many places, and I cannot afford fetch the data each time I render it. I see via all that messages in console, that the data I need is fetched and the store.state is updated and I see there all data I need, but Index component is never updated, and I don't get this data inside it. I am a novice, so probably I'm just blind to something stupid... I'd also appreciate any piece of advise regarding the whole architecture for my problem.

var React = require('react');
var ReactDOM = require('react-dom');
var Router = require('react-router').Router;
var Route = require('react-router').Route;
var createBrowserHistory = require('history/lib/createBrowserHistory');
var createStore = require('redux').createStore;
var Provider = require('react-redux').Provider;
var connect = require('react-redux').connect;

window.onload=setOptions;

var options = [];
var setOptReducer = function (state = [], action) {
    console.log('setOptReducer was called with state', state, 'and action', action)
    switch (action.type) {
        case 'ADD_ITEM':
            return [
                ...state,
                action.data
            ]
        default:
            return state;
    }
};

var setOptCreator = function (options) {
    console.log("options inside setOptCreator ", options);
    return {
        type: 'ADD_ITEM',
        data: options
    }
};

var optDispatcher = function(options) {
    store.dispatch(setOptCreator(options));
};

const store = createStore(setOptReducer, options);

function setOptions() {
    loadFromServer(optDispatcher);
};

function mapStateToProps(state) {
    console.log("mapStateToProps ",state);
    return {options: state.options};
};

var IndexContainer = React.createClass({
    render: function () {
        return (
            <div>
                {console.log("Index rendered with options ", this.props.options)}
            </div>
        );
    }
});

function loadFromServer(callback) {
    function option(value, label) {
        this.value = value;
        this.label = label;
    }
    $.ajax({
        url: "/api/",
        dataType: 'json',
        cache: false,
        success: function (data) {
            var options = [];
            {.....put some elements from data to options}
            console.log("options inside loadFromServer is ", options);
            callback(options);
            console.log('store state after callback:', store.getState());
        }.bind(this)
    });
};

var Index = connect(mapStateToProps)(IndexContainer);

ReactDOM.render(
    <Provider store={store}>
            <Router history={createBrowserHistory()}>
                <Route path="/" component={Index}/>
            </Router>
    </Provider>,
    document.getElementById("container")
);

Upvotes: 1

Views: 2764

Answers (2)

Battle_Slug
Battle_Slug

Reputation: 2105

Finally with huge help of luanped I got to the root of the problem, and I believe it worth putting as a separate answer.

mapStateToProps really cannot map state.options to options as the state doesn't contain options attribute, because in the setOptReducer actionData is being saved by concatenating it to the state array, not by putting as a separate named attribute of object state:

   case 'ADD_ITEM':
        return [
            ...state,
            action.data
        ]

So mapStateToProps doesn't really changes options (change is undefined to undefined) and that's why the component doesn't re-render. So the decision is to expose the whole state as options, which is changing this.props of the component, so it works. To make it work the more correct way, without exposing the whole state to the component, but just the options part, reducer's code should be:

    return {
        ...state,
        options: action.data
    }

This way state becomes to have options attribute, mapStateToProps sees it and the component re-renders.

Upvotes: 3

luanped
luanped

Reputation: 3198

Basically in your mapStateToProps, you have options: state.options; but right now there is no thing named as options in the state. From the docs http://redux.js.org/docs/api/createStore.html, when you use createStore passing in setOptReducer as argument, it creates a store with just one reducer and as a result state on it's own is the value of the store.

Could you try changing your mapStateToProps to this?

function mapStateToProps(state) {
    console.log("mapStateToProps ",state);
    return {options: state};
};

If this works, then you could rename some things, since right now it might be confusing. For example removing var options = [] and change the reducer function to options and have

const store = createStore(options, []);

It is recommended to use combineReducers to create a single root reducer out of many - and if you pass that to createStore, then the state is an object, and you can then do state.setOptReducer

Hope it's not too confusing

Upvotes: 4

Related Questions