MY QUESTION: Why doesn't updating a property of an object in an array in my Immutable state (Map) not cause Redux to update my component?
I'm trying to create a widget that uploads files to my server, and my initial state (from inside my UploaderReducer which you will see below) object looks like this:
let initState = Map({
files: List(),
displayMode: 'grid',
currentRequests: List()
});
I have a thunk method that starts uploads and dispatches actions when an event occurs (such as a progress update). For example, the onProgress event looks like this:
onProgress: (data) => {
dispatch(fileUploadProgressUpdated({
index,
progress: data.percentage
}));
}
I'm using redux-actions
to create and handle my actions, so my reducer for that 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:
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, this all seems to work fine! In Redux DevTools, it shows up as an action as expected. However, none of my components update! Adding new items to the files
array re-renders my UI with the new files added, so Redux certainly doesn't have a problem with me doing that...
My top level component that connects to the store 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);
// Constructor things!
}
// Some events n stuff...
render(){
return (
<div>
<UploadWidget
//other props
files={this.props.files} />
</div>
);
}
}
export default connect(mapStateToProps, uploadActions)(UploaderContainer);
uploadActions
is an object with actions created using redux-actions
.
A file
object in the files
array is basically this:
{
name: '',
progress: 0,
status
}
The UploadWidget
is basically a drag n drop div and a the files
array printed out on the screen.
I tried using redux-immutablejs
to help out as I've seen in many posts on GitHub, but I have no idea if it 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 app entry point looks like this:
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')
);
Lastly, 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>
);
}
//Prep devTools, etc...
render () {
return (
<Provider store={this.props.store}>
<div style={{ height: '100%' }}>
{this.content}
{this.devTools}
</div>
</Provider>
);
}
}
So, ultimately, if I try to update a '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 modified objects regardless of how deeply you update them?
It seems generally getting Immutable to work with Redux is not as simple as it seems: How to use Immutable.js with redux? https://github.com/reactjs/redux/issues/548
However, the touted benefits of using Immutable seem to be worth this battle and I'd LOVE to figure out what I'm doing wrong!
UPDATE April 10 2016
The selected answer told me what I was doing wrong and for the sake of completeness, my updateFilePropsAtIndex
function now contains simply this:
return state.updateIn(['files', index], file =>
Object.assign({}, file, fileProps)
);
This works perfectly well! :)