How can I see state within a function? using hooks - reactjs

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,
};
});
};

Related

How do i stop a dependency from re-rendering infinite times in a useEffect?

I have a react-select multiselect component which is required to have preselected values based on the user role. The react-select component is used to filter data in a react-table.
I have 2 user roles - Dev and tester.
If it the dev, I need to have open and Reopened issues to be filtered on load
If it is a tester, I need to have resolved issues on load
This is a part of the code that i am using to achieve preselected
async function loadInfo() {
const body = {
project_id: projDetails.id,
};
const response = await axios({
method: "post",
url: apilist.dropdownData,
data: body,
})
.catch((err) => console.log(err));
if (response) {
const getData = response.data.data;
// console.log("IsGeneralInfo:", getData)
setGeneralInfo(getData);
let filteredenv = getData.status.filter((item) => item.id == 8 || item.id == 6)
let envfiltered = filteredenv.map((k) => {
return ({ label: k.values, value: k.values });
})
// console.log("envfilter", envfiltered);
// handleMultiStatusFilter(envfiltered);
}
}
// const {current:myArray}=useRef([{ label: 'Closed', value: 'Closed' }])
useEffect(() => {
if(envfilter){
let myArray=[{ label: 'Closed', value: 'Closed' },{ label: 'Reopen', value: 'Reopen' }];
handleMultiStatusFilter(myArray);
}
}, [selectedOptions])
const handleStatusFilter = (e) => {
setFilterValue(e);
if (e.length > 0) {
dispatch(filterByValue({ type: e, viewIssue: viewIssue, }))
}
else {
dispatch(showAllStatus({ type: 'All', viewIssue: viewIssue, }))
}
}
const handleMultiStatusFilter = (e) => {
setFiltered([])
let arr = []
e.map((data) => {
setFiltered(prevState => [...prevState, data.value]);
arr.push(data.value);
})
setSelectedOptions(e)
handleStatusFilter(arr)
}
This is a part of the redux code used for filtering
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchIssueList.fulfilled, (state, action) => {
// Add user to the state array
state.issuesList = {
status: 'idle',
data: action.payload.data.data !== undefined ? action.payload.data.data : [],
dataContainer: action.payload.data.data !== undefined ? action.payload.data.data : [],
no_of_records: action.payload.data.data !== undefined ? action.payload.data.data.length : 0,
error: {}
}
})
The code works fine with the filtering once i login, but the rerendering keeps going to infinite loop
Is there any way i could stop the infinite rerendering of code and have the filtering happen on load of the screen?
while working with dependencies in useEffect, try to use the most primitive part you can find. no complex objects, as they change way too fast.
for example: use the length of an array not the array itself.
even though for arrays it's mostly safe to use itself.
sorry. correction: for arrays it's not safe either. complex objects are compared by reference not by value. for that you need primitive types like number, string or boolean.

Update Object Key Value with onClick [duplicate]

Is it at all possible to update object's properties with setState?
Something like:
this.state = {
jasper: { name: 'jasper', age: 28 },
}
I have tried:
this.setState({jasper.name: 'someOtherName'});
and this:
this.setState({jasper: {name: 'someothername'}})
The first results in a syntax error and the second just does nothing. Any ideas?
There are multiple ways of doing this, since state update is a async operation, so to update the state object, we need to use updater function with setState.
1- Simplest one:
First create a copy of jasper then do the changes in that:
this.setState(prevState => {
let jasper = Object.assign({}, prevState.jasper); // creating copy of state variable jasper
jasper.name = 'someothername'; // update the name property, assign a new value
return { jasper }; // return new object jasper object
})
Instead of using Object.assign we can also write it like this:
let jasper = { ...prevState.jasper };
2- Using spread syntax:
this.setState(prevState => ({
jasper: { // object that we want to update
...prevState.jasper, // keep all other key-value pairs
name: 'something' // update the value of specific key
}
}))
Note: Object.assign and Spread Operator creates only shallow copy, so if you have defined nested object or array of objects, you need a different approach.
Updating nested state object:
Assume you have defined state as:
this.state = {
food: {
sandwich: {
capsicum: true,
crackers: true,
mayonnaise: true
},
pizza: {
jalapeno: true,
extraCheese: false
}
}
}
To update extraCheese of pizza object:
this.setState(prevState => ({
food: {
...prevState.food, // copy all other key-value pairs of food object
pizza: { // specific object of food object
...prevState.food.pizza, // copy all pizza key-value pairs
extraCheese: true // update value of specific key
}
}
}))
Updating array of objects:
Lets assume you have a todo app, and you are managing the data in this form:
this.state = {
todoItems: [
{
name: 'Learn React Basics',
status: 'pending'
}, {
name: 'Check Codebase',
status: 'pending'
}
]
}
To update the status of any todo object, run a map on the array and check for some unique value of each object, in case of condition=true, return the new object with updated value, else same object.
let key = 2;
this.setState(prevState => ({
todoItems: prevState.todoItems.map(
el => el.key === key? { ...el, status: 'done' }: el
)
}))
Suggestion: If object doesn't have a unique value, then use array index.
This is the fastest and the most readable way:
this.setState({...this.state.jasper, name: 'someothername'});
Even if this.state.jasper already contains a name property, the new name name: 'someothername' with be used.
Use spread operator and some ES6 here
this.setState({
jasper: {
...this.state.jasper,
name: 'something'
}
})
I know there are a lot of answers here, but I'm surprised none of them create a copy of the new object outside of setState, and then simply setState({newObject}). Clean, concise and reliable. So in this case:
const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))
Or for a dynamic property (very useful for forms)
const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))
Using hook we can do following way
const [student, setStudent] = React.useState({name: 'jasper', age: 28});
setStudent((prevState) => ({
...prevState,
name: 'newName',
}));
I used this solution.
If you have a nested state like this:
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
}
you can declare the handleChange function that copy current status and re-assigns it with changed values
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let statusCopy = Object.assign({}, this.state);
statusCopy.formInputs[inputName].value = inputValue;
this.setState(statusCopy);
}
here the html with the event listener. Make sure to use the same name used into state object (in this case 'friendName')
<input type="text" onChange={this.handleChange} " name="friendName" />
try this,it should work fine
this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Create a state object
this.state = {
objName: {
propertyOne: "",
propertyTwo: ""
}
};
Update state using setState
this.setState(prevState => ({
objName: {
...prevState.objName,
propertyOne: "Updated Value",
propertyTwo: "Updated value"
}
}));
this is another solution using immer immutabe utility, very suited for deeply nested objects with ease, and you should not care about mutation
this.setState(
produce(draft => {
draft.jasper.name = 'someothername'
})
)
Using hooks in Functional Component:
const [state, setState] = useState({jasper: { name: 'jasper', age: 28 }})
const nameChangeHandler = () => {
setState(prevState => ({
...prevState,
prevState.jasper.name = "Anurag",
prevState.jasper.age = 28
})
)
}
In these cases It is recommended to use callback-based approach to update the state , because using this approach it is ensured that previously states are fully updated and we're updating based on previously updated state.
The first case is indeed a syntax error.
Since I can't see the rest of your component, it's hard to see why you're nesting objects in your state here. It's not a good idea to nest objects in component state. Try setting your initial state to be:
this.state = {
name: 'jasper',
age: 28
}
That way, if you want to update the name, you can just call:
this.setState({
name: 'Sean'
});
Will that achieve what you're aiming for?
For larger, more complex data stores, I would use something like Redux. But that's much more advanced.
The general rule with component state is to use it only to manage UI state of the component (e.g. active, timers, etc.)
Check out these references:
https://facebook.github.io/react/docs/react-component.html#state
https://facebook.github.io/react/docs/state-and-lifecycle.html
In case of updating an object where keys are string
e.g. let say your state object is
serviceDays: {
Sunday: true,
Monday: true,
Tuesday: false,
Wednesday: true,
Thurday: false,
Friday: true,
Saturday: true
}
so you can update in following way
const onDayClick = day => {
const { serviceDays } = this.state
this.setState(prevState => ({
serviceDays: {
...prevState.serviceDays,
[day]: serviceDays[day] ? false : true
}
}))
}
Another option: define your variable out of the Jasper object and then just call a variable.
Spread operator: ES6
this.state = { jasper: { name: 'jasper', age: 28 } }
let foo = "something that needs to be saved into state"
this.setState(prevState => ({
jasper: {
...jasper.entity,
foo
}
})
You can try with this:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.name = 'someOtherName';
return {jasper: prevState}
})
or for other property:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.age = 'someOtherAge';
return {jasper: prevState}
})
Or you can use handleChage function:
handleChage(event) {
const {name, value} = event.target;
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState[name] = value;
return {jasper: prevState}
})
}
and HTML code:
<input
type={"text"}
name={"name"}
value={this.state.jasper.name}
onChange={this.handleChange}
/>
<br/>
<input
type={"text"}
name={"age"}
value={this.state.jasper.age}
onChange={this.handleChange}
/>
You can try with this:
(Note: name of input tag === field of object)
<input name="myField" type="text"
value={this.state.myObject.myField}
onChange={this.handleChangeInpForm}>
</input>
-----------------------------------------------------------
handleChangeInpForm = (e) => {
let newObject = this.state.myObject;
newObject[e.target.name] = e.target.value;
this.setState({
myObject: newObject
})
}
Simple and dynamic way.
This will do the job, but you need to set all the ids to the parent so the parent will point to the name of the object, being id = "jasper" and name the name of the input element = property inside of the object jasper.
handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Without using Async and Await Use this...
funCall(){
this.setState({...this.state.jasper, name: 'someothername'});
}
If you using with Async And Await use this...
async funCall(){
await this.setState({...this.state.jasper, name: 'someothername'});
}
Also, following Alberto Piras solution, if you don't want to copy all the "state" object:
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let jasperCopy = Object.assign({}, this.state.jasper);
jasperCopy[inputName].name = inputValue;
this.setState({jasper: jasperCopy});
}
Try with this:
const { jasper } = this.state; //Gets the object from state
jasper.name = 'A new name'; //do whatever you want with the object
this.setState({jasper}); //Replace the object in state
By using the input html input name attribute we can have a more dynamic approach in order to update an object properties.
DOM
html input name attribute
<input type="text" name="fname" handleChange={(e: any) => { updatePerson(e) }}/>
<input type="text" name="lname" handleChange={(e: any) => { updatePerson(e) }}/>
React / TSX
object.assign
const [person, setPerson] = useState<IPerson>({});
function updatePerson(e: React.ChangeEvent<HTMLInputElement>): void {
const { name, value } = e.currentTarget;
setPerson(prevState => {
const newState = Object.assign(person, { [name]: value })
return { ...prevState, ...newState };
});
}
Sample FC:
const [formData, setformData] = useState({
project_admin_permissions: {
task_forms: false,
auto_assign_rules: false,
project_notes: true,
alerts: false,
update_criteria: true,
project_flow: false,
reports: false,
}
})
const handleChangeCheckBox = (e) => {
setformData({
...formData, project_admin_permissions: { ...formData.project_admin_permissions, [e.target.name]: e.target.checked }
})
}
This setup worked for me:
let newState = this.state.jasper;
newState.name = 'someOtherName';
this.setState({newState: newState});
console.log(this.state.jasper.name); //someOtherName
Your second approach doesn't work because {name: 'someothername'} equals {name: 'someothername', age: undefined}, so theundefined would overwrite original age value.
When it comes to change state in nested objects, a good approach would be Immutable.js
this.state = {
jasper: Record({name: 'jasper', age: 28})
}
const {jasper} = this.state
this.setState({jasper: jasper.set(name, 'someothername')})

How to handle array state filter clashes

I am currently having an issue where multiple setStates that use the filtering of an array are interfering with each other. Basically if a user uploads two files, and they complete around the same time, one of the incomplete files may fail to be filtered from the array.
My best guess is that this is happening because they are separately filtering out the one that needs to be filtered, when the second one finishes and goes to filter itself out of the array, it still has the copy of the old incomplete array where the first file has not been filtered out yet. What would be a better way to approach this? Am I missing something obvious? I am thinking of using an object to hold the files instead, but then I would need to create a custom mapping function for the rendering part so that it can still be rendered as if were an array.
fileHandler = (index, event) =>{
let incompleteFiles = this.state.incompleteFiles
incompleteFiles[index].loading = true
incompleteFiles[index].file = event.target.files[0]
this.setState({ incompleteFiles: incompleteFiles },()=>{
const fileData = new FormData()
fileData.append('file', event.targets[0].file)
let incompleteFiles = this.state.incompleteFiles
let completeFiles = this.state.completeFiles
api.uploadFile(fileData)
.then(res=>{
if(res.data.success){
this.setState(state=>{
let completeFile = {
name : res.data.file.name,
}
completeFiles.push(completeFile)
incompleteFiles = incompleteFiles.filter(inc=>inc.label !== res.data.file.name)
return{
completeFiles,
incompleteFiles
}
})
}
})
})
}
Updated with accepted answer with a minor tweak
fileHandler = (index, event) =>{
this.setState(({ incompleteFiles }) => ({
// Update the state in an immutable way.
incompleteFiles: [
...incompleteFiles.slice(0, index),
{
...incompleteFiles[index],
loading: true,
file: event.target.files[0],
},
...incompleteFiles.slice(index+1)
],
}), () => {
const fileData = new FormData()
fileData.append('file', event.targets[0].file)
api.uploadFile(fileData)
.then(res => {
if(res.data.success){
this.setState(({ incompleteFiles, completeFiles }) => ({
completeFiles: [
...completeFiles, // Again, avoiding the .push since it mutates the array.
{ // The new file.
name: res.data.file.name,
}
],
incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name),
})))
}
})
});
}
In class components in React, when setting the state which is derived from the current state, you should always pass a "state updater" function instead of just giving it an object of state to update.
// Bad
this.setState({ counter: this.state.counter + 1 });
// Good
this.setState((currentState) => ({ counter: currentState.counter + 1 }));
This ensures that you are getting the most up-to-date version of the state. The fact that this is needed is a side-effect of how React pools state updates under the hood (which makes it more performant).
I think if you were to re-write your code to make use of this pattern, it would be something like this:
fileHandler = (index, event) =>{
this.setState(({ incompleteFiles }) => ({
// Update the state in an immutable way.
incompleteFiles: {
[index]: {
...incompleteFiles[index],
loading: true,
file: event.target.files[0],
},
},
}), () => {
const fileData = new FormData()
fileData.append('file', event.targets[0].file)
api.uploadFile(fileData)
.then(res => {
if(res.data.success){
this.setState(({ incompleteFiles, completeFiles }) => ({
completeFiles: [
...completeFiles, // Again, avoiding the .push since it mutates the array.
{ // The new file.
name: res.data.file.name,
}
],
incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name),
})))
}
})
});
}
Another thing to keep in mind is to avoid mutating your state objects. Methods like Array.push will mutate the array in-place, which can cause issues and headaches.
I think change code to this can solve your problem and make code easy to read.
fileHandler = async (index, event) =>{
const incompleteFiles = [...this.state.incompleteFiles]
incompleteFiles[index].loading = true
incompleteFiles[index].file = event.target.files[0]
this.setState(
{
incompleteFiles
},
async (prev) => {
const fileData = new FormData()
fileData.append('file', event.targets[0].file)
const res = await api.uploadFile(fileData)
/// set loading state to false
incompleteFiles[index].loading = false
if (!res.data.success) {
return { ...prev, incompleteFiles }
}
// add new file name into completeFiles and remove uploaded file name from incompleteFiles
return {
...prev,
completeFiles: [...prev.completeFiles, { name : res.data.file.name }],
incompleteFiles: incompleteFiles.filter(inc=>inc.label !== res.data.file.name)
}
})
)
}

simulate change not working with trim() enzyme

I was using this test when I had a bug, so I used the trim function for resolve it, and the these test fail, tried in different ways but didn't found the solution
const generalWrapper = shallow(<AddVehiclesTable {...generalProps} />)
const generalInstance = generalWrapper.instance()
describe('onSearchChange', () => {
test('should change the "search" state', () => {
const theFilterValue = 'a new filter value'
generalWrapper.find('.filter-input').simulate('change', { target: { value: theFilterValue } })
const expectedState = Object.assign({}, generalInstance.state)
expectedState.searchValue = { 'target': { 'value': theFilterValue } }
expect(generalInstance.state).toEqual(expectedState)
expect(generalInstance.state.userInteractedWithComponent).toBe(true)
})
})
onSearchChange (searchValue) {
const value = searchValue.trim()
this.setState({ searchValue: value, userInteractedWithComponent: true })
}
Error message
TypeError: searchValue.trim is not a function
Any suggestions
Your function gets the Object as a parameter.
Expose field that you needed
I don't see the whole picture, but can guess that you need something like
onSearchChange ({ target: { value: incomeValue } }) {
const value = incomeValue.trim()
this.setState({ searchValue: value, userInteractedWithComponent: true })
}

Updating an object with setState in React

Is it at all possible to update object's properties with setState?
Something like:
this.state = {
jasper: { name: 'jasper', age: 28 },
}
I have tried:
this.setState({jasper.name: 'someOtherName'});
and this:
this.setState({jasper: {name: 'someothername'}})
The first results in a syntax error and the second just does nothing. Any ideas?
There are multiple ways of doing this, since state update is a async operation, so to update the state object, we need to use updater function with setState.
1- Simplest one:
First create a copy of jasper then do the changes in that:
this.setState(prevState => {
let jasper = Object.assign({}, prevState.jasper); // creating copy of state variable jasper
jasper.name = 'someothername'; // update the name property, assign a new value
return { jasper }; // return new object jasper object
})
Instead of using Object.assign we can also write it like this:
let jasper = { ...prevState.jasper };
2- Using spread syntax:
this.setState(prevState => ({
jasper: { // object that we want to update
...prevState.jasper, // keep all other key-value pairs
name: 'something' // update the value of specific key
}
}))
Note: Object.assign and Spread Operator creates only shallow copy, so if you have defined nested object or array of objects, you need a different approach.
Updating nested state object:
Assume you have defined state as:
this.state = {
food: {
sandwich: {
capsicum: true,
crackers: true,
mayonnaise: true
},
pizza: {
jalapeno: true,
extraCheese: false
}
}
}
To update extraCheese of pizza object:
this.setState(prevState => ({
food: {
...prevState.food, // copy all other key-value pairs of food object
pizza: { // specific object of food object
...prevState.food.pizza, // copy all pizza key-value pairs
extraCheese: true // update value of specific key
}
}
}))
Updating array of objects:
Lets assume you have a todo app, and you are managing the data in this form:
this.state = {
todoItems: [
{
name: 'Learn React Basics',
status: 'pending'
}, {
name: 'Check Codebase',
status: 'pending'
}
]
}
To update the status of any todo object, run a map on the array and check for some unique value of each object, in case of condition=true, return the new object with updated value, else same object.
let key = 2;
this.setState(prevState => ({
todoItems: prevState.todoItems.map(
el => el.key === key? { ...el, status: 'done' }: el
)
}))
Suggestion: If object doesn't have a unique value, then use array index.
This is the fastest and the most readable way:
this.setState({...this.state.jasper, name: 'someothername'});
Even if this.state.jasper already contains a name property, the new name name: 'someothername' with be used.
Use spread operator and some ES6 here
this.setState({
jasper: {
...this.state.jasper,
name: 'something'
}
})
I know there are a lot of answers here, but I'm surprised none of them create a copy of the new object outside of setState, and then simply setState({newObject}). Clean, concise and reliable. So in this case:
const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))
Or for a dynamic property (very useful for forms)
const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))
Using hook we can do following way
const [student, setStudent] = React.useState({name: 'jasper', age: 28});
setStudent((prevState) => ({
...prevState,
name: 'newName',
}));
I used this solution.
If you have a nested state like this:
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
}
you can declare the handleChange function that copy current status and re-assigns it with changed values
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let statusCopy = Object.assign({}, this.state);
statusCopy.formInputs[inputName].value = inputValue;
this.setState(statusCopy);
}
here the html with the event listener. Make sure to use the same name used into state object (in this case 'friendName')
<input type="text" onChange={this.handleChange} " name="friendName" />
try this,it should work fine
this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Create a state object
this.state = {
objName: {
propertyOne: "",
propertyTwo: ""
}
};
Update state using setState
this.setState(prevState => ({
objName: {
...prevState.objName,
propertyOne: "Updated Value",
propertyTwo: "Updated value"
}
}));
this is another solution using immer immutabe utility, very suited for deeply nested objects with ease, and you should not care about mutation
this.setState(
produce(draft => {
draft.jasper.name = 'someothername'
})
)
Using hooks in Functional Component:
const [state, setState] = useState({jasper: { name: 'jasper', age: 28 }})
const nameChangeHandler = () => {
setState(prevState => ({
...prevState,
prevState.jasper.name = "Anurag",
prevState.jasper.age = 28
})
)
}
In these cases It is recommended to use callback-based approach to update the state , because using this approach it is ensured that previously states are fully updated and we're updating based on previously updated state.
The first case is indeed a syntax error.
Since I can't see the rest of your component, it's hard to see why you're nesting objects in your state here. It's not a good idea to nest objects in component state. Try setting your initial state to be:
this.state = {
name: 'jasper',
age: 28
}
That way, if you want to update the name, you can just call:
this.setState({
name: 'Sean'
});
Will that achieve what you're aiming for?
For larger, more complex data stores, I would use something like Redux. But that's much more advanced.
The general rule with component state is to use it only to manage UI state of the component (e.g. active, timers, etc.)
Check out these references:
https://facebook.github.io/react/docs/react-component.html#state
https://facebook.github.io/react/docs/state-and-lifecycle.html
In case of updating an object where keys are string
e.g. let say your state object is
serviceDays: {
Sunday: true,
Monday: true,
Tuesday: false,
Wednesday: true,
Thurday: false,
Friday: true,
Saturday: true
}
so you can update in following way
const onDayClick = day => {
const { serviceDays } = this.state
this.setState(prevState => ({
serviceDays: {
...prevState.serviceDays,
[day]: serviceDays[day] ? false : true
}
}))
}
Another option: define your variable out of the Jasper object and then just call a variable.
Spread operator: ES6
this.state = { jasper: { name: 'jasper', age: 28 } }
let foo = "something that needs to be saved into state"
this.setState(prevState => ({
jasper: {
...jasper.entity,
foo
}
})
You can try with this:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.name = 'someOtherName';
return {jasper: prevState}
})
or for other property:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.age = 'someOtherAge';
return {jasper: prevState}
})
Or you can use handleChage function:
handleChage(event) {
const {name, value} = event.target;
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState[name] = value;
return {jasper: prevState}
})
}
and HTML code:
<input
type={"text"}
name={"name"}
value={this.state.jasper.name}
onChange={this.handleChange}
/>
<br/>
<input
type={"text"}
name={"age"}
value={this.state.jasper.age}
onChange={this.handleChange}
/>
You can try with this:
(Note: name of input tag === field of object)
<input name="myField" type="text"
value={this.state.myObject.myField}
onChange={this.handleChangeInpForm}>
</input>
-----------------------------------------------------------
handleChangeInpForm = (e) => {
let newObject = this.state.myObject;
newObject[e.target.name] = e.target.value;
this.setState({
myObject: newObject
})
}
Simple and dynamic way.
This will do the job, but you need to set all the ids to the parent so the parent will point to the name of the object, being id = "jasper" and name the name of the input element = property inside of the object jasper.
handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Without using Async and Await Use this...
funCall(){
this.setState({...this.state.jasper, name: 'someothername'});
}
If you using with Async And Await use this...
async funCall(){
await this.setState({...this.state.jasper, name: 'someothername'});
}
Also, following Alberto Piras solution, if you don't want to copy all the "state" object:
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let jasperCopy = Object.assign({}, this.state.jasper);
jasperCopy[inputName].name = inputValue;
this.setState({jasper: jasperCopy});
}
Try with this:
const { jasper } = this.state; //Gets the object from state
jasper.name = 'A new name'; //do whatever you want with the object
this.setState({jasper}); //Replace the object in state
By using the input html input name attribute we can have a more dynamic approach in order to update an object properties.
DOM
html input name attribute
<input type="text" name="fname" handleChange={(e: any) => { updatePerson(e) }}/>
<input type="text" name="lname" handleChange={(e: any) => { updatePerson(e) }}/>
React / TSX
object.assign
const [person, setPerson] = useState<IPerson>({});
function updatePerson(e: React.ChangeEvent<HTMLInputElement>): void {
const { name, value } = e.currentTarget;
setPerson(prevState => {
const newState = Object.assign(person, { [name]: value })
return { ...prevState, ...newState };
});
}
Sample FC:
const [formData, setformData] = useState({
project_admin_permissions: {
task_forms: false,
auto_assign_rules: false,
project_notes: true,
alerts: false,
update_criteria: true,
project_flow: false,
reports: false,
}
})
const handleChangeCheckBox = (e) => {
setformData({
...formData, project_admin_permissions: { ...formData.project_admin_permissions, [e.target.name]: e.target.checked }
})
}
This setup worked for me:
let newState = this.state.jasper;
newState.name = 'someOtherName';
this.setState({newState: newState});
console.log(this.state.jasper.name); //someOtherName
Your second approach doesn't work because {name: 'someothername'} equals {name: 'someothername', age: undefined}, so theundefined would overwrite original age value.
When it comes to change state in nested objects, a good approach would be Immutable.js
this.state = {
jasper: Record({name: 'jasper', age: 28})
}
const {jasper} = this.state
this.setState({jasper: jasper.set(name, 'someothername')})

Resources