Immutable object is not being set in state - reactjs

The immutable object being returned is correct but this.setState({...}) does not seem to set it.
I've tried the various ways to set state and all seem to have failed.
state = {
onboarding3: fromJS({
selection: {
neverBeen: false,
noConvert: false,
both: false
},
noConvertOptions: {
dynamicCreative: false,
noPurchase: false,
abandoned: false
},
bothOptions: {
dynamicCreative: false,
noPurchase1: false,
noPurchase2: false
}
})
};
updateToggle = (field, option) => {
return () => {
const currentValue = this.state.onboarding3.getIn([field, option]);
const onboarding3 = this.state.onboarding3.setIn([field, option], !currentValue);
this.setState({ onboarding3 });
};
};
<Component
roundedSwitchFunc={this.updateToggle("noConvertOptions", "dynamicCreative")}
defaultChecked={onboarding3.getIn(["noConvertOptions", "dynamicCreative"])}
/>

It was an issue of event bubbling and the data not being in sync. The purpose of this component was for my state to mimic the store but since each component has their own 'store', it does not accurately mimic Redux.

Related

Reactjs and Redux update state mutable or immutable?

I'm extremely new with Reactjs and Redux, anyone can explain what is Mutable and Immutable in reactjs/redux?
I'm using Redux to update state, and using it to control my UI layout, but I found one of the method below will not working, although both are also updating the object.
I've a store object such as below:
const initialAppState = {
showSideBar: false,
showSwitcher: false,
showRightPane: false,
menu: [
{
name: "Home",
redirect: "/",
},
{
name: "About",
redirect: "/about",
expanded: false,
childs: [{
name: "Child one",
redirect: "/Child"
}]
},
]
}
Beside that, I have a Pane.js and Menu.js to render my menu list.
Pane.js
const LeftPane = (props) => {
return <List>
{links.map((o, index) => {
return <Menus key={o.name} props={o} index={index} />
})}
</List>
}
Menu.js
const Menus = ({ props, index, onToggleSubMenu }) => {
return <ListItem> ...
}
I'm trying to update the expanded value to true for the menu object, but when I using state.menu.map to change the value, my react component won't re-render? It will execute to the Pane.js, and I can see the expanded = true from the Pane.js props. but it won't execute to the Menu.js?
const AppReducer = (state = initialAppState, action) => {
return {...state,
menu: [
...state.menu.map((m, i) => {
if (i === action.index) {
m.expanded = !state.menu[action.index].expanded;
}
return m;
})
]
}
}
On the other hand, if I update the expanded value from the code below, it works.
const AppReducer = (state = initialAppState, action) => {
state.menu[action.index] = Object.assign({}, state.menu[action.index], {
expanded: !state.menu[action.index].expanded,
});
return {...state, menu: state.menu}
}
What is the different between these two? What is the correct way to update state? Why we should use Object.assign or spread (...) operator to update state? I've read the Redux Immutale Update Patterns, the working code above is more like the Common Mistake mentioned in the link.
There are two things which you should do differently in your AppReducer.
Map function returns a new array and does not mutate the original array, so no need to destruct there.
Inside your map function, you have the reference to the object m, and you are mutating the m by changing m.expanded to !m.expanded. This is where you should actually be returning a new object.
You should write AppReducer as following.
const AppReducer = (state = initialAppState, action) => {
return {
...state,
// No need to destruct when using map, map always returns a new array
menu: state.menu.map((m, i) => {
if (i === action.index) {
// Return a new object, with all properties copied, and expanded's value toggled
return {
...m,
expanded: !m.expanded;
}
}
// Return the original object because no change has been made
return m;
}),
};
};
As for the difference between the spread operator and Object.assign, according to object-rest-spread-proposal, one is syntactic sugar of the other, i.e. {...a} is pretty much an easier way to write Object.assign({}, a);

How can I see state within a function? using hooks

I'm trying to update the uploadFiles state inside my updateFile function, when reloading the file, I'm rewriting this component in hooks, but inside the function the state is given as empty.
const [uploadedFiles, setUploadedFiles] = useState({
slides: [],
material: [],
});
const updateFile = useCallback(
(id, data) => {
const value = uploadedFiles.slides.map(uploadedFile => {
return id === uploadedFile.id
? { ...uploadedFile, ...data }
: uploadedFile;
});
console.log('value', value);
console.log('uploadedFilesOnFunction', uploadedFiles);
},
[uploadedFiles]
);
function processUpload(upFile, type) {
const data = new FormData();
data.append('file', upFile.file, upFile.name);
api
.post('dropbox', data, {
onUploadProgress: e => {
const progress = parseInt(Math.round((e.loaded * 100) / e.total), 10);
updateFile(upFile.id, {
progress,
});
},
})
.then(response => {
updateFile(upFile.id, {
uploaded: true,
id: response.data.id,
url: response.data.url,
type,
});
})
.catch(response => {
updateFile(upFile.id, {
error: true,
});
});
}
function handleUpload(files, type) {
const uploaded = files.map(file => ({
file,
id: uniqueId(),
name: file.name,
readableSize: filesize(file.size),
preview: URL.createObjectURL(file),
progress: 0,
uploaded: false,
error: false,
url: null,
type,
}));
setUploadedFiles({
slides: uploadedFiles.slides.concat(uploaded),
});
uploaded.forEach(e => processUpload(e, type));
}
console.log('slides', uploadedFiles);
I expected the state values to be viewed by the function. For me to manipulate and set the state.
There might be other issues, but one thing I've noticed is:
const [uploadedFiles, setUploadedFiles] = useState({
slides: [],
material: [],
});
// A setState CALL FROM THE useState HOOK REPLACES THE STATE WITH THE NEW VALUE
setUploadedFiles({
slides: uploadedFiles.slides.concat(uploaded),
});
From: https://reactjs.org/docs/hooks-state.html
State variables can hold objects and arrays just fine, so you can still group related data together. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
The setState from the useState hook doesn't merge the state. Because it can hold any type of value, not only objects, like we used to do with classes.
From your code you can see that you're erasing some property from state when you're updating like that.
Instead, you should use the functional form of the setState and access the current state prevState, like:
setUploadedFiles((prevState) => {
return({
...prevState,
slides: uploadedFiles.slides.concat(uploaded)
});
});
The updated updateFiles function:
const updateFile = (id, data) => {
setUploadedFiles(prevState => {
const newSlide = prevState.slides.map(slide => {
return id === slide.id ? { ...slide, ...data } : slide;
});
return {
...prevState,
slides: newSlide,
};
});
};

Expensive function blocks render() update

In my (class) component I want to show Loading spinner for the duration of the _expensiveFunction.
Value isLoading was changed in the state before the function was executed, but it will not be re-rendered (spinner does not spin) until _expensiveFunction is complete.
I tried it with componentDidUpdate and forceUpdate, but without success.
_makeCalculation() {
this.setState(
{ isLoading: true },
() => this._expensiveFunction(),
);
}
_expensiveFunction() {
console.log(this.state.isLoading); // => true
// ***
this.setState({ isLoading: false });
}
A common trick is to rely on setTimeout():
_makeCalculation() {
this.setState(
{ isLoading: true },
() => setTimeout(this._expensiveFunction, 0),
);
}

React set button to disabled with state

I am in a React class-based component and a property on my state like so:
state = {
controls: {
email: {
validation: {
required: true,
isEmail: true
},
invalid: false,
},
password: {
validation: {
required: true,
minLength: 6
},
invalid: true,
},
disabledSubmit: true,
}
};
I have an inputChangedHandler triggered from an input component:
inputChangedHandler = (event, controlName) => {
console.log("[STATE]", this.state.controls);
const updatedControls = {
...this.state.controls,
[controlName]: {
...this.state.controls[controlName],
value: event.target.value,
invalid: !this.checkValidity(
event.target.value,
this.state.controls[controlName].validation
)
},
touched: true
};
console.log("[UPDATEDCONTROLS]", updatedControls);
this.setState({ controls: updatedControls }, () => {
this.disabledSubmitHandler();
});
};
And a disabledSubmitHandler that should be being called from within my inputChangedHandler :
disabledSubmitHandler() {
if (
!this.state.controls["email"].invalid &&
!this.state.controls["password"].invalid
) {
this.setState(
{ disabledSubmit: true },
console.log("[DISABLEDHANDLER] TRUE")
);
}
}
The prop is set on my button component in my JSX like so:
<Button
value="submit"
clicked={this.submitHandler}
disabled={this.state.disabledSubmit}
/>
This does not work, but I'm not sure what's happening?
I think maybe this bit seems to need fixing:
disabledSubmitHandler() {
if (
!this.state.controls["email"].invalid && //if email is not invalid
!this.state.controls["password"].invalid //if password is not invalid
) {
this.setState(
{ disabledSubmit: true },
console.log("[DISABLEDHANDLER] TRUE")
);
}
}
That code says if the email and password are valid, disable the input. I think it should be:
disabledSubmitHandler() {
if (
!this.state.controls["email"].invalid &&
!this.state.controls["password"].invalid
) {
this.setState(
{ disabledSubmit: false },
console.log("[DISABLEDHANDLER] FALSE")
);
}
}
Plas as #an0nym0us mentioned, disabledSubmit is nested inside controls.
Also, on a side note, it seems a little odd that you would call a function which sets state, only to call another function which sets state inside that function, as a callback to set state (inputChangedHandler calling disabledSubmitHandler'). It seems you could call that disabled check from withininputChangedHandlerpassing it yourupdatedControls, and return true/false fordisabledSubmit, resulting in a single call tosetState`

SetState not working with spread operator

I have a language switcher in a React native app, it has the selected language ticked, which is stored in state. However this is not working, any advice appreciated. The code is as below. The selectedLanguage is being populated so it knows what needs updating but it's not updating the state object.
constructor(props) {
super(props);
this.state = {
languages: [
{
language: 'Dutch',
selected: false,
},
{
language: 'English',
selected: true,
},
{
language: 'French',
selected: false,
},
{
language: 'German',
selected: false,
},
{
language: 'Polish',
selected: false,
}
],
};
};
changeLanguage(selectedLanguage){
this.state.languages.map((language) => {
if(language.language === selectedLanguage){
this.setState(prevState => ([
...prevState.languages, {
language: selectedLanguage,
selected: true,
}
]));
}
});
}
The spread operator isn’t going to deep compare objects in an array. You’ve got to either move from a languages array to a languages object, as in:
languages: {
Dutch: false,
English: true,
...
}
Or, you need to copy and replace:
changeLanguage(selectedLanguage){
this.state.languages.map((language, index) => {
if(language.language === selectedLanguage){
this.setState(prevState => {
const newLangs = [...prevState.languages];
newLangs[index].selected = true;
return newLangs;
});
}
});
}
First need to find out the right object for given selectedLanguage from an array.
Use Array#map to traverse and update the matched object selected prop value with boolean true and rest to false.
const {languages} = this.state;
const updatedLang = languages.map((lang, key) => {
if (lang.language == selectedLanguage) {
lang.selected = true;
} else {
lang.selected = false;
}
return lang;
})
Now do setState.
this.setState(prevState => ({languages: updatedLang}));

Resources