I'm having a question to Immer.js with React.js and Redux. I'm familiar with React.js. As far as I know it is not "allowed" to update a property itself by
props.someValue= 'someValue'
So you need to pass a callback function to the component itself like
<SomeComponent
key={"componentKey"}
someValue={"someValue"}
onSomeValueChange={this.handleSomeValueChange.bind(this)}
/>
and in SomeComponent
you call the function like this:
...
this.props.onSomeValueChange('someNewValue');
...
Or you can handle this with Redux:
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { updateSomeValue } from '../actions/index';
function SomeComponent(props) {
function onTextChanged(event) {
props.updateSomeValue(event.target.value);
}
return (
<>
<input type="text" value={someValue} onChange={onTextChanged} />
</>
)
}
function mapStateToProps({ someValue }) {
return {
someValue
};
}
function mapDispatchToProps(dispatch) {
return {
updateSomeValue: someValue => dispatch(updateSomeValue(someValue)),
};
}
const Form = connect(
mapStateToProps,
mapDispatchToProps
)(SomeComponent);
export default Form;
The code above is a very simple example, because someValue
is just a string
and no complex object. Now, if it is getting more complex and you have objects with subobjects, I'm still searching for the best way to update the subobjects.
In the past, I've used lodash
to create a "clone" of the object and modified the "clone", before I updated the original property.
function onTextChanged(event) {
let updatedJob = _.cloneDeep(props.currentJob);
for(let a = 0; a < updatedJob.job_texts.length; a++) {
if(updatedJob.job_texts[a].id === props.jobText.id) {
updatedJob.job_texts[a].text = event.target.value;
}
}
props.updateCurrentJob(updatedJob);
}
This solution did work, but as you can see, it is probably not best way to handle this.
Now I read today, that my solution is not recommended as well. You need to create a "copy" of each subobject as far as I understood.
Then I stumbled across redux
page about immer.js
, but I'm not quite sure, how to use this:
The situation is as follows:
I have an object currentJob
, which have several properties. One of these properties is a subobject (array
) called job_texts
.
One job_text
has a property text
which I need to update.
I thought, I can handle this, this way:
let updatedJob = props.currentJob;
props.updateCurrentJob(
produce(updatedJob.job_texts, draftState => {
if(draftState.id === props.jobText.id) {
draftState.text = text;
}
})
);
...but of course this won't work, because with the code above I'm updating currentJob
with the subobject array. How can I use immer.js
to update one job_text
within the object currentJob
?
The best way would be to use the official Redux Toolkit, which already includes immer
in reducers created with createReducer
and createSlice
.
It's also generally the recommended way of using Redux since two years.
Generally, you should also have that "subobject update logic" in your reducer, not your component.
So with RTK, a "slice" would for example look like
const objectSlice = createSlice({
name: 'object',
reducers: {
updateSubObjectText(state, action) {
const jobText = state.job_texts.find(text => text.id === action.payload.id)
if (jobText) {
jobText.text = action.payload.text
}
}
}
})
export const { updateSubObjectText } = objectSlice.actions
export default objectSlice.reducer
This will create a reducer and an action creator updateSubObjectText
to be used in your component. They will be hooked together and action types are an implementation detail you do not care about.
In your component you would now simply do
dispatch(updateSubObjectText({ id, text }))
For more infos see this quick into to Redux Toolkit or the full official Redux "Essentials" tutorial which nowadays covers Redux Toolkit as the recommended way of writing real Redux logic.
There is also a full docs page on writing Reducers with immer.