React: Child State isn't updating after Parent state gets Updated - javascript

I am beginner in Reactjs. I was building an form application using the same. There I was asked to set value of input field from the server, which can be updated by user i.e. an controlled input component.
I fetched the value in parent state then I passed the value to the child state and from there I set value of input field. Now the problem arises when I update the value in parent state then the value isn't getting updated in the child state.
See the code below -
App.jsx
import { useEffect, useState } from "react";
import { Child } from "./child";
import "./styles.css";
export default function App() {
const [details, setDetails] = useState({});
useEffect(() => {
fetch("https://reqres.in/api/users/2")
.then((res) => res.json())
.then((data) => setDetails(data));
}, []);
useEffect(() => {
console.log("data of details", details?.data);
}, [details]);
return (
<div className="App">
<h1>Testing</h1>
<Child details={details} setDetails={setDetails} val={details?.data} />
</div>
);
}
Child.jsx
import { useState } from "react";
export const Child = ({ details, setDetails, val }) => {
const [value, setValue] = useState({
save: true,
...val
});
const handleChange = (e) => {
setValue({ ...value, email: e.target.value });
};
const handleSave = () => {
setDetails({
...details,
data: { ...details.data, email: value.email }
});
console.log("Data",value);
};
const handleDelete = () => {
setDetails({ ...details, data: { ...details.data, email: "" } });
console.log("Data",value);
};
return (
<div className="cont">
<input type="text" value={value.email} onChange={handleChange} />
{value.save && <button onClick={handleSave}>save</button>}
<button onClick={handleDelete}>Delete</button>
</div>
);
};
Codesandbox Link:
https://codesandbox.io/s/testing-m3mc6?file=/src/child.jsx:0-801
N.B. I have googled for solution I saw one stackoverflow question also but that wasn't helpful for me as I am using functional way of react.
Any other method of accomplishing this would be appreciated.

Try this in child component:
useEffect(()=>{
setValue({
value,
...val
});
}, [val])

Related

How to update state of parent component with a lifted up props?

I have a problem with updating the state of parent component with a props from child component. It seems the following code is not working, however it looks fine
setUsersList(prevState => {
return [...prevState, data];
});
My parent component receives an object. Console.log(data) outputs the object received from child component. However, when console logging updated state (console.log(usersList)) it returns an empty array
Parent component:
import React, { useState } from "react";
import AddUser from "./components/Users/AddUser";
import UsersList from "./components/Users/UsersList";
function App() {
const [usersList, setUsersList] = useState([]);
const addUserHandler = data => {
console.log(data);
setUsersList(prevState => {
return [...prevState, data];
});
console.log(usersList);
};
return (
<div>
<AddUser onAddUser={addUserHandler}></AddUser>
<UsersList users={usersList}></UsersList>
</div>
);
}
export default App;
Child component:
import React, { useState } from "react";
import Button from "../UI/Button";
import Card from "../UI/Card";
import styles from "./AddUser.module.css";
const AddUser = props => {
const [inputData, setInputData] = useState({ name: "", age: "" });
const addUserHandler = event => {
event.preventDefault();
if (
inputData.age.trim().length === 0 ||
inputData.name.trim().length === 0
) {
return;
}
if (+inputData.age < 1) {
return;
}
props.onAddUser(inputData);
console.log(inputData.name, inputData.age);
setInputData({ name: "", age: "" });
};
const usernameChangeHandler = event => {
setInputData({ ...inputData, name: event.target.value });
};
const ageChangeHandler = event => {
setInputData({ ...inputData, age: event.target.value });
};
return (
<Card className={styles.input}>
<form onSubmit={addUserHandler}>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
onChange={usernameChangeHandler}
value={inputData.name}
></input>
<label htmlFor="age">Age (Years)</label>
<input
id="age"
type="number"
onChange={ageChangeHandler}
value={inputData.age}
></input>
<Button type="submit">Add User</Button>
</form>
</Card>
);
};
export default AddUser;
Due to the way react re-renders components, your console may not log with the expected state change. Instead you can use useEffect for debugging purposes:
parent component
useEffect(() => {
console.log("usersList", usersList);
}, [usersList])
alternatively, having a console.log statement in the body of your functional component should log the correct 'usersList'.
const [usersList, setUsersList] = useState([]);
console.log("usersList", usersList);
The state variable won't change right away when you call setState function from the useState hook. Since it is an asynchronous event.
So you might need to write your code like this to see the right console.log
const addUserHandler = data => {
console.log(data);
setUsersList(prevState => {
const temp = [...prevState, data];
console.log(temp); // like this
return temp;
});
};
If the state is not updating in the UI. Please paste the error or the warning message.
Since setUserList is async function, you can not see the changes on console in the addUserHandler function.
function App() {
const [usersList, setUsersList] = useState([]);
const addUserHandler = data => {
console.log(data);
setUsersList(prevState => {
return [...prevState, data];
});
};
console.log(usersList);
return (
<div>
<AddUser onAddUser={addUserHandler}></AddUser>
<UsersList users={usersList}></UsersList>
</div>
);
}
export default App;
This will work. Thanks.

react state is not updating the UI

I have a Form Component where it contains a state that should be updated (on input change) and it looks like this:
import { useState } from 'react';
export const Test = () => {
const [state, setState] = useState({
name: 'khaled',
age: 18
})
const handleInputChange = (e) => {
let stateCopy = state
for(let key in stateCopy) {
if(key === 'name') {
stateCopy[key] = e.target.value;
}
}
setState(stateCopy);
}
return(
<div>
<span>Name</span>
<input onChange={ handleInputChange } />
<span>{state.name}</span>
</div>
)
}
and it imported in the app component
import { Test } from '../../components/Test';
function App() {
return (
<Test />
);
}
export default App;
and whenever i try to change the name inout it not update the ui
To make the input a controlled component, both value and onChange props should be assigned.
<input value={state.name} onChange={handleInputChange} />
handleInputChange function can be improved to make sure that the state is updated immutably:
const handleInputChange = ({ target: { value } }) => {
setState(prevState => ({...prevState, name: value}));
}
This does not work because your "stateCopy" object isn't actually a copy, its the actual state object. you are setting the state to the same object which causes react to think the state didn't change at all.
instead you should copy the state like this
const handleInputChange = (e) => {
let stateCopy = {...state}
state.name = e.target.value
setState(stateCopy);
}
You should also note that unless there is a good reason for your choice of state in my opinion you should use a seperate useState for each element in the state which results in the much simpler
import { useState } from 'react';
export const Test = () => {
const [name, setName] = useState('khalad')
const [age, setAge] = useState(18)
const handleInputChange = (e) => {
setName(e.target.value)
}
return(
<div>
<span>Name</span>
<input onChange={ handleInputChange } />
<span>{state.name}</span>
</div>
)
}
simply do it like this, it will work
const handleInputChange = (e) => {
setState({...state, name: e.target.value})
}

React add to props object

I have a component that uses a prop that recieves data from its parent. I need to edit the value of the prop Im not sure if its possible. I tried using state to copy
const Form = ({ details, form }) => {
useEffect(() => {
details.name = 'greg'
}, [])
}
Is it possible to do something like this? I get an error something like object is not extensible.
Assuming, details is a state variable, you can't update state that way. State is supposed to be immutable. To update state, you can do something like:
import React, {useState, useEffect} from "react";
const Parent = () => {
const [form, setForm] = useState({});
const [details, setDetails] = useState({ name: "bill" });
return (
<Form details={details} form={form} setDetails={setDetails} />
);
};
const Form = ({ details, form, setDetails }) => {
useEffect(() => {
setDetails({
...details,
name: "greg",
});
}, []);
return (
<div>Hello</div>
);
}

How to access the latest state value in the functional component in React

import React, { useState } from "react";
import Child from "./Child";
import "./styles.css";
export default function App() {
let [state, setState] = useState({
value: ""
});
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
import React from "react";
function Child(props) {
return (
<input
type="text"
placeholder="type..."
onChange={e => {
let newValue = e.target.value;
props.handleChange(newValue);
}}
value={props.value}
/>
);
}
export default Child;
Here I am passing the data from the input field to the parent component. However, while displaying it on the page with the h1 tag, I am able to see the latest state. But while using console.log() the output is the previous state. How do I solve this in the functional React component?
React state updates are asynchronous, i.e. queued up for the next render, so the log is displaying the state value from the current render cycle. You can use an effect to log the value when it updates. This way you log the same state.value as is being rendered, in the same render cycle.
export default function App() {
const [state, setState] = useState({
value: ""
});
useEffect(() => {
console.log(state.value);
}, [state.value]);
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
Two solution for you:
- use input value in the handleChange function
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
use a useEffect on the state
useEffect(()=>{
console.log(state.value)
},[state])
Maybe it is helpful for others I found this way...
I want all updated projects in my state as soon as I added them
so that I use use effect hook like this.
useEffect(() => {
[temp_variable] = projects //projects get from useSelector
let newFormValues = {...data}; //data from useState
newFormValues.Projects = pro; //update my data object
setData(newFormValues); //set data using useState
},[projects])

How to access state when component unmount with React Hooks?

With regular React it's possible to have something like this:
class NoteEditor extends React.PureComponent {
constructor() {
super();
this.state = {
noteId: 123,
};
}
componentWillUnmount() {
logger('This note has been closed: ' + this.state.noteId);
}
// ... more code to load and save note
}
In React Hooks, one could write this:
function NoteEditor {
const [noteId, setNoteId] = useState(123);
useEffect(() => {
return () => {
logger('This note has been closed: ' + noteId); // bug!!
}
}, [])
return '...';
}
What's returned from useEffect will be executed only once before the component unmount, however the state (as in the code above) would be stale.
A solution would be to pass noteId as a dependency, but then the effect would run on every render, not just once. Or to use a reference, but this is very hard to maintain.
So is there any recommended pattern to implement this using React Hook?
With regular React, it's possible to access the state from anywhere in the component, but with hooks it seems there are only convoluted ways, each with serious drawbacks, or maybe I'm just missing something.
Any suggestion?
useRef() to the rescue.
Since the ref is mutable and exists for the lifetime of the component, we can use it to store the current value whenever it is updated and still access that value in the cleanup function of our useEffect via the ref's value .current property.
So there will be an additional useEffect() to keep the ref's value updated whenever the state changes.
Sample snippet
const [value, setValue] = useState();
const valueRef = useRef();
useEffect(() => {
valueRef.current = value;
}, [value]);
useEffect(() => {
return function cleanup() {
console.log(valueRef.current);
};
}, []);
Thanks to the author of https://www.timveletta.com/blog/2020-07-14-accessing-react-state-in-your-component-cleanup-with-hooks/. Please refer this link for deep diving.
useState() is a specialized form of useReducer(), so you can substitute a full reducer to get the current state and get around the closure problem.
NoteEditor
import React, { useEffect, useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "set":
return action.payload;
case "unMount":
console.log("This note has been closed: " + state); // This note has been closed: 201
break;
default:
throw new Error();
}
}
function NoteEditor({ initialNoteId }) {
const [noteId, dispatch] = useReducer(reducer, initialNoteId);
useEffect(function logBeforeUnMount() {
return () => dispatch({ type: "unMount" });
}, []);
useEffect(function changeIdSideEffect() {
setTimeout(() => {
dispatch({ type: "set", payload: noteId + 1 });
}, 1000);
}, []);
return <div>{noteId}</div>;
}
export default NoteEditor;
App
import React, { useState, useEffect } from "react";
import "./styles.css";
import NoteEditor from "./note-editor";
export default function App() {
const [notes, setNotes] = useState([100, 200, 300]);
useEffect(function removeNote() {
setTimeout(() => {
setNotes([100, 300]);
}, 2000);
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{notes.map(note => (
<NoteEditor key={`Note${note}`} initialNoteId={note} />
))}
</div>
);
}
I wanted to chime in with an answer for this in case someone else runs into this. If you need more than one value in your useEffect unmount function, it's important to make sure that the correct dependencies are being used. So the accepted answer works fine because it's just one dependency, but start including more dependencies, and it gets complicated. The amount of useRef's you need get out of hand. So instead, what you can do is a useRef that is the unmount function itself, and call that when you unmount the component:
import React, { useRef, useState, useContext, useCallback, useEffect } from 'react';
import { Heading, Input } from '../components';
import { AppContext } from '../../AppContext';
export const TitleSection: React.FC = ({ thing }) => {
const { updateThing } = useContext(AppContext);
const [name, setName] = useState(thing.name);
const timer = useRef(null);
const onUnmount = useRef();
const handleChangeName = useCallback((event) => {
setName(event.target.value);
timer.current !== null && clearTimeout(timer.current);
timer.current = setTimeout(() => {
updateThing({
name: name || ''
});
timer.current = null;
}, 1000);
}, [name, updateThing]);
useEffect(() => {
onUnmount.current = () => {
if (thing?.name !== name) {
timer.current !== null && clearTimeout(timer.current);
updateThing({
name: name || '',
});
timer.current = null;
}
};
}, [thing?.name, name, updateThing]);
useEffect(() => {
return () => {
onUnmount.current?.();
};
}, []);
return (
<>
<Heading as="h1" fontSize="md" style={{ marginBottom: 5 }}>
Name
</Heading>
<Input
placeholder='Grab eggs from the store...'
value={name}
onChange={handleChangeName}
variant='white'
/>
</>
);
};

Categories

Resources