Titouan Launay Titouan Launay - 1 month ago 12
Javascript Question

React/Redux connect does not fire mapStateToProps on store change

I am experiencing an issue with Redux (used along with React). Here is my code:

/// <reference path="../../typings/index.d.ts"/>

import "./polyfill.ts";

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { createStore, combineReducers } from 'redux'
import { Provider, dispatch } from 'react-redux'
import { Router, Route, browserHistory, IndexRedirect } from 'react-router'
import { syncHistoryWithStore, routerReducer } from 'react-router-redux'

import { Form, Input } from './components/form.tsx'

import app from './reducers.ts'

const store = createStore(
combineReducers({
app,
routing: routerReducer
}),
{},
window.devToolsExtension && window.devToolsExtension()
);

import App from './views/app.tsx';
import Reminders from './views/reminders.tsx';
import Calendar from './views/calendar.tsx';
import Settings from './views/settings.tsx';
import Groups from './views/groups.tsx';
import Courses from './views/courses.tsx';
import Homework from './views/homework.tsx';

// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(browserHistory, store);

class Application extends React.Component<any,any> {
constructor(props){
super(props);
}
render() {
var socket = io();
socket.on('connect', function () {
setTimeout(function(){
store.dispatch({
type: "SOCKET_ESTABLISHED",
socket: socket
});
}, 1000);
});
socket.on('DATA', function(data){
console.log("DATA");
console.log(data);
})
console.log(Groups);
return <Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRedirect to="/reminders" />
<Route path="reminders" component={Reminders}/>
<Route path="calendar" component={Calendar}/>
<Route path="settings" component={Settings}>
<Route path="groups" component={Groups}/>
</Route>
<Route path="homework" component={Homework} />
</Route>
</Router>
</Provider>
}
}


ReactDOM.render(
<Application/>,
document.getElementById('mount')
);


My reducer file looks like the following:

export default function app(state={
connection: {
socket: null,
online: false
}
}, action: any) {
console.log(action);
switch(action.type){
case "SOCKET_ESTABLISHED":
console.log(state.connection);
return Object.assign(state, {
connection: {
socket: action.socket,
online: true
}
});
default:
return state;
}
};


And my App component is the following:

import * as React from 'react';
import { connect } from 'react-redux';

import Top from '../components/top.tsx';
import Nav from '../components/nav.tsx';

class AppClass extends React.Component<any,any> {
constructor(props){
super(props);
}
render() : JSX.Element {
return <div>
<Top/>
<Nav/>
{this.props.connection.online ? this.props.children : "Offline." }
</div>
}
}

var App = connect(function(state){
return {
connection: state.app.connection
};
})(AppClass);

export default App;


So let me be clear, I am using the connect function to sync store and App props. Upon page load, a socket connection is created and the action SOCKET_ESTABLISHED which changes
store.connection.online
to
true
. Everything works fine, the Redux dev tools show that the store is correctly updated and online is now true. If I look with the React dev tools the
Connect(AppClass)
component, its storeState is up to date, but its child component, which is AppClass, does not have the right props,
this.props.app.connection.online
is false where it should be true. I have seen that it could come from a state mutation, but using Object.assign a new store is returned (The function is the MDN Polyfill).

Hope I was clear and thank you !

(Note that I am using Typescript for compiling although I might have been neglecting some interface definitions)

Answer

Your modifying the state object inside of the reducer. You need to copy state to a new object, and then modify the new one.

Update this:

return Object.assign(state, {
    connection: {
        socket: action.socket,
        online: true
    }
});

To this:

return Object.assign({}, state, {
    connection: {
        socket: action.socket,
        online: true
    }
});

// or this
return {
    ...state,
    connection: {
        socket: action.socket,
        online: true
    }
}

Redux receives the new state from the reducer and compares it to the previous state. If you modify the state directly, the new state and previous state are the same object. It's similar to doing this.

var state = { foo: 'bar' };
state.test = 'test';
var newState = state;
// state === newState # true

Instead, you want to copy the object before modifying

var state = { foo: 'bar' };
var newState = { ...state, test: 'test' };
// state === newState # false
Comments