MY QUESTION: Why updating the property of an object in an array in my immutable state (Map) does not cause Redux to update my component?
I am trying to create a widget that uploads files to my server, and my initial state (from my UploaderReducer object, which you will see below) looks like this:
let initState = Map({ files: List(), displayMode: 'grid', currentRequests: List() });
I have a thunk method that starts a download and dispatches actions when an event occurs (for example, updating a run). For example, the onProgress event looks like this:
onProgress: (data) => { dispatch(fileUploadProgressUpdated({ index, progress: data.percentage })); }
I use redux-actions to create and process my actions, so my reducer for this action looks like this:
export default UploaderReducer = handleActions({ // Other actions... FILE_UPLOAD_PROGRESS_UPDATED: (state, { payload }) => ( updateFilePropsAtIndex( state, payload.index, { status: FILE_UPLOAD_PROGRESS_UPDATED, progress: payload.progress } ) ) }, initState);
And updateFilePropsAtIndex looks like this:
export function updateFilePropsAtIndex (state, index, fileProps) { return state.updateIn(['files', index], file => { try { for (let prop in fileProps) { if (fileProps.hasOwnProperty(prop)) { if (Map.isMap(file)) { file = file.set(prop, fileProps[prop]); } else { file[prop] = fileProps[prop]; } } } } catch (e) { console.error(e); return file; } return file; }); }
So far, all this is working fine! In Redux DevTools, it appears as an action, as expected. However, none of my components are updated! Adding new elements to the files array re-displays my user interface with the new files added, so Redux, of course, has no problem with what I am doing this ...
My top-level component, which connects to the repository using connect , looks like this:
const mapStateToProps = function (state) { let uploadReducer = state.get('UploaderReducer'); let props = { files: uploadReducer.get('files'), displayMode: uploadReducer.get('displayMode'), uploadsInProgress: uploadReducer.get('currentRequests').size > 0 }; return props; }; class UploaderContainer extends Component { constructor (props, context) { super(props, context);
uploadActions is an object with actions created using redux-actions .
An object
A file in the files array is basically like this:
{ name: '', progress: 0, status }
UploadWidget is basically a div n drop div and a files array printed on the screen.
I tried using redux-immutablejs to help, as I saw in many posts on GitHub, but I have no idea if this helps ... This is my root reducer:
import { combineReducers } from 'redux-immutablejs'; import { routeReducer as router } from 'redux-simple-router'; import UploaderReducer from './modules/UploaderReducer'; export default combineReducers({ UploaderReducer, router });
My entry point to the application is as follows:
const store = configureStore(Map({})); syncReduxAndRouter(history, store, (state) => { return state.get('router'); }); // Render the React application to the DOM ReactDOM.render( <Root history={history} routes={routes} store={store}/>, document.getElementById('root') );
Finally, my <Root/> component looks like this:
import React, { PropTypes } from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router'; export default class Root extends React.Component { static propTypes = { history: PropTypes.object.isRequired, routes: PropTypes.element.isRequired, store: PropTypes.object.isRequired }; get content () { return ( <Router history={this.props.history}> {this.props.routes} </Router> ); }
So ultimately, if I try to update the βprogressβ in the following state object, React / Redux does not update my components:
{ UploaderReducer: { files: [{progress: 0}] } }
Why is this? I thought the whole idea of ββusing Immutable.js was that it was easier to compare changed objects no matter how deep you update them?
Getting Immutable to work with Redux doesn't seem as easy as it sounds: How to use Immutable.js with a shorthand? https://github.com/reactjs/redux/issues/548
However, the exaggerated benefits of using Immutable seem worthy of this battle, and I LOVE to understand what I'm doing wrong!
UPDATE April 10, 2016 The selected answer told me what I am doing wrong and for the sake of completeness, my updateFilePropsAtIndex function now simply contains the following:
return state.updateIn(['files', index], file => Object.assign({}, file, fileProps) );
This works great! :)