I am trying to display data to and update data in child component from parent component. When I press edit button to update data, input element gets value from object but value in child component remains unchanged. Why is child component not getting value from parent component?
Parent component
import "./styles.css";
import { NumberInput} from "./NumberInput";
import { useState } from "react";
class Item {
id: number = 0
name: string = ''
price: number = 0;
}
export default function App() {
let [item, setItem] = useState(new Item());
let tempItems = [];
let tempItem = new Item();
tempItem.id = 1;
tempItem.name = "fish";
tempItem.price = 5.5;
tempItems.push(tempItem);
let [items, setItems] = useState(tempItems);
console.log(items);
return (
<div className="App"><input
onChange={e => {
let temp = {...item};
temp.name = e.currentTarget.value;
setItem(temp);
}}
value={item.name}
/> Name
<NumberInput item={item} name="price" setter={setItem}/> Price
<button onClick={e => {
let temp = [...items, item];
setItems(temp);
setItem(new Item());
}}>Add</button>
{items.map(x => {
return (<div key={x.id}>
<span>{x.name} => {x.price}</span>
<button onClick={e => setItem(x)}>Edit</button>
</div>)
})}
</div>
);
}
Child component
interface NumberInputProps extends InputHTMLAttributes<HTMLInputElement> {
item: any;
setter: React.Dispatch<React.SetStateAction<any>>;
}
export const NumberInput: FC<NumberInputProps> = ({
item,
setter,
...rest
}) => {
let tempValue = item[rest.name as string].toString();
const [value, setValue] = useState(tempValue);
const assignValue = (e: React.ChangeEvent<HTMLInputElement>) => {
let value = e.currentTarget.value;
if (!isNaN(parseFloat(value))) {
let temp = { ...item };
temp[rest.name as string] = parseFloat(value);
setter(temp);
}
setValue(value);
};
return <input value={value} onChange={assignValue} {...rest} />;
};
in your child component
// item pass in as props, if item changed, child will re-render this is fine
let tempValue = item[rest.name as string].toString();
// but you put item's value to this local state as default
// when item's value changed, your child value does not change
// because "tempValue" only used as default when component first rendered
const [value, setValue] = useState(tempValue);
Suggestion:
You can use the pass in item's value directly in your child
Put the value into a child local state is not necessary, and it may cause bug
if you really need to change the value and put it into another local state, you can do like this:
(please beware that my code in JS not TS)
const [value, setValue] = useState(null);
// when ever item change, do setValue to refresh the value
useEffect(() => {
const tempValue = item[rest.name as string].toString();
setValue(tempvalue);
} ,[item])
Related
I have two children Components, when I onChange in first children, then the second children re render, I don't want to the second children re render. Online code example: https://codesandbox.io/s/billowing-glitter-r5gnh3?file=/src/App.js:1287-1297
const EditSceneModalStore = React.createContext(undefined);
const Parent = () => {
const [saveValue, setSaveValue] = React.useState({});
const initValue = {
name: "zhang",
age: 3
};
const onSave = () => {
console.log("===saveValue==", saveValue);
};
const onChangeValue = (key, value) => {
const newValue = {
...saveValue,
[key]: value
};
setSaveValue(newValue);
};
return (
<EditSceneModalStore.Provider
value={{
initValue,
onChangeValue
}}
>
<ChildInput1 />
<ChildInput2 />
<Button onClick={onSave} type="primary">
save
</Button>
</EditSceneModalStore.Provider>
);
};
const ChildInput1 = () => {
const { onChangeValue, initValue } = React.useContext(EditSceneModalStore);
const [value, setValue] = React.useState(initValue.name);
return (
<Input
value={value}
onChange={(v) => {
setValue(v.target.value);
onChangeValue("name", v.target.value);
}}
/>
);
};
const ChildInput2 = () => {
const { initValue, onChangeValue } = React.useContext(EditSceneModalStore);
const [value, setValue] = React.useState(initValue.InputNumber);
console.log("====ChildInput2===");
return (
<InputNumber
value={value}
onChange={(v) => {
setValue(v.target.value);
onChangeValue("age", v.target.value);
}}
/>
);
};
when I onChange in ChildInput1, then ChildInput2 re-render, I don't want to the ChildInput2 re-render. Example image
As Andrey explained, you should fix the following line:
//you have
const [value, setValue] = React.useState(initValue.InputNumber);
// should be
const [value, setValue] = React.useState(initValue.age);
Additionally, initValue gets unnecessarily re-computed on every re-render, so it should be outside the scope of Parent:
const initValue = {
name: "zhang",
age: 3
};
const Parent = () => {...}
Regarding re renderings, it is ok. When a Provider gets the value changed, all their childs wrapped in a consumer rerender. This is natural. This post explains why.
A component calling useContext will always re-render when the context
value changes. If re-rendering the component is expensive, you can
optimize it by using memoization.
In this case, it is not expensive enough to consider memoization.
I Hope it helps
You have a typo in your code:
//you have
const [value, setValue] = React.useState(initValue.InputNumber);
// should be
const [value, setValue] = React.useState(initValue.age);
also update like that
<InputNumber
value={value}
onChange={(value) => {
setValue(value);
onChangeValue("age", value);
}}
/>
and when you fix like that do not worry about re-render as state of ChildInput2 will no be changed
i want to pass the data of text-input from child to parent to submit the dynamic form. when i use useEffect the phone blocked but i don't know why.please can someone help me to solve this problem.thanks to tell me if there are another way to pass the data.
child component
const RenderComponents = ({ sendChildToParent) => {
const [inputsVal, setInputsVal] = useState({});
const handleChange = (name, value) => {
setInputsVal({ ...inputsVal, [name]: value });
};
const senddata = () => {
sendChildToParent(inputsVal);
};
useEffect(senddata);
return (
<>
{getData.length === 0 ? (
<Empty />
) : (
getData.map((item, index) => {
switch (item.type) {
case "TextInput":
return (
<>
<InputText
onChangeText={(text) => handleChange(item.nameC, text)}
ModuleName={item.nameC}
placeholder={item.options.placeholder}
required={item.options.required}
key={index}
/>
</>
);
case "Phone":...
Parent Component
export function TemplateScreen(props) {
const navigation = useNavigation();
const [getData, setData] = React.useState(Mydata);
const [childData, setChildData] = useState([]);
const sendChildToParent = (dataFromChild) => {
setChildData(dataFromChild);
};
//*************************************Child Componenet*************** */
const RenderComponents = () => {
const [userTeam, setUserTeam] = useState({});
[...other code here...];
**********Parent Component*******
return (
<ScrollView>
<RenderComponents />
<Button
title="Submit"
onPress={()=>null}
/>...
The structure of your parent component is fine. The issues are in your child component, in the following lines:
const RenderComponents = ({ sendChildToParent) => {
const [inputsVal, setInputsVal] = useState({});
const handleChange = (name, value) => {
setInputsVal({ ...inputsVal, [name]: value });
};
const senddata = () => {
sendChildToParent(inputsVal);
};
useEffect(senddata);
it's not good practice to duplicate the input value in local state. Pass the value down from the parent component as well as the setter function.
you're not passing a dependency array to your useEffect function, so it runs on every render of the component. This sets off the following chain of events:
the parent renders
the child renders
useEffect runs, setting the value of the state in the parent
the parent re-renders
This is an endless loop and what causes your app to lock.
there's no need to wrap the state setting functions in your own functions unless you are planning to do additional work there later. There's also no need to run those functions in your component lifecycle (useEffect), because they will run when the input changes.
missing bracket in the first line.
You could rewrite the components in the following way:
// parent component
export function TemplateScreen(props) {
const navigation = useNavigation();
const [getData, setData] = React.useState(Mydata);
const [childData, setChildData] = useState({});
return (
<ScrollView>
<RenderComponents childData={childData} setChildData={setChildData} />
...
// child component
const RenderComponents = ({ childData, setChildData }) => {
const handleChange = (name, value) => {
setChildData({ ...childData, [name]: value });
};
return (
...
I have a logic in my code that i don't understand, i can update a state (useState) throught my child components but without the "set" function.
Here is my (simplified) code :
const Main = () =>{
const [mission, setMission] = useState({activity:"", env:""})
const see = () =>{
console.log(mission)
}
return (
<Child1 data={mission} />
<button onClick={see}>TEST</button>
)
}
const Child1 = (props) =>{
const {data} = props
const [mission, setMission] = useState(data)
const handleChange = (value) =>{
mission["activity"] = value
}
return (
<Child2 data={mission} onChange={handleChange} />
)
}
const Child2 = (props) =>{
const {data} = props
const [activity, setActivity] = useState(data.activity)
const handleChange = (e) =>{
setActivity(e.target.value)
props.onChange(e.target.value)
}
return (
<input value={data} onChange={handleChange} />
)
}
I tried in sandbox and it work too, "mission" did update it's value throught all childs without any "setMission".
I'm relativily new to react so i miss something but i don't know what, can someone explain ?
Thank you
You probably want to "lift the state up". Only the Main component should useState. Then pass both the state value and update function to the child component. The child component will call this update function when it updates. This will update the state on the parent properly and re-render.
Using your example:
const Main = () => {
// Only one state at the top level
const [mission, setMission] = useState({ activity: "", env: "" });
const see = () => {
console.log(mission);
};
return (
<>
{/* Pass both the value and state update function to the child */}
<Child1 data={mission} update={setMission} />
<button onClick={see}>TEST</button>
<div>{mission.activity}</div>
</>
);
};
const Child1 = (props) => {
const { data, update } = props;
const handleChange = (e) => {
// This will set parent's state
update({ ...data, activity: e.target.value });
};
// You can follow the similar pattern to the next child
return <Child2 data={data} onChange={handleChange} />;
};
You can see a complete working example in this sandbox.
What is the correct way to handle the case with React Hooks, where a child's state is set from parent prop, but any changes to the child should update the parent. The parent prop can keep changing.
For example,
const ParentComponent = () => {
const [container, setContainer] = useState({ name: 'hello'});
//some other user action causes container to change
//which should reflect in the child. Any subsequent update to child's input field
//should update parent again.
onChange = (payload) => {
container.name = payload.name
}
return (
<ChildComponent container={container} dispatch={onChange} />
);
};
const ChildComponent = (props) => {
const { container, dispatch } = props;
const [name, setName] = useState(container.name);
useEffect(()=>{
const payload = { name };
dispatch(payload);
},[name]);
return (
<input value={name} onChange= {(e)=>setName(e.target.value)} />
);
};
Would this not cause an infinite loop?
Playing with React those days. I know that calling setState in async. But setting an initial value like that :
const [data, setData] = useState(mapData(props.data))
should'nt it be updated directly ?
Bellow a codesandbox to illustrate my current issue and here the code :
import React, { useState } from "react";
const data = [{ id: "LION", label: "Lion" }, { id: "MOUSE", label: "Mouse" }];
const mapData = updatedData => {
const mappedData = {};
updatedData.forEach(element => (mappedData[element.id] = element));
return mappedData;
};
const ChildComponent = ({ dataProp }) => {
const [mappedData, setMappedData] = useState(mapData(dataProp));
console.log("** Render Child Component **");
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
export default function App() {
const [loadedData, setLoadedData] = useState(data);
const [filter, setFilter] = useState("");
const filterData = () => {
return loadedData.filter(element =>
filter ? element.id === filter : true
);
};
//loaded comes from a useEffect http call but for easier understanding I removed it
return (
<div className="App">
<button onClick={() => setFilter("LION")}>change filter state</button>
<ChildComponent dataProp={filterData()} />
</div>
);
}
So in my understanding, when I click on the button I call setFilter so App should rerender and so ChildComponent with the new filtered data.
I could see it is re-rendering and mapData(updatedData) returns the correct filtered data BUT ChildComponent keeps the old state data.
Why is that ? Also for some reason it's rerendering two times ?
I know that I could make use of useEffect(() => setMappedData(mapData(dataProp)), [dataProp]) but I would like to understand what's happening here.
EDIT: I simplified a lot the code, but mappedData in ChildComponent must be in the state because it is updated at some point by users actions in my real use case
https://codesandbox.io/s/beautiful-mestorf-kpe8c?file=/src/App.js
The useState hook gets its argument on the very first initialization. So when the function is called again, the hook yields always the original set.
By the way, you do not need a state there:
const ChildComponent = ({ dataProp }) => {
//const [mappedData, setMappedData] = useState(mapData(dataProp));
const mappedData = mapData(dataProp);
console.log("** Render Child Component **");
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
EDIT: this is a modified version in order to keep the useState you said to need. I don't like this code so much, though! :(
const ChildComponent = ({ dataProp }) => {
const [mappedData, setMappedData] = useState(mapData(dataProp));
let actualMappedData = mappedData;
useMemo(() => {
actualMappedData =mapData(dataProp);
},
[dataProp]
)
console.log("** Render Child Component **");
return Object.values(actualMappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
Your child component is storing the mappedData in state but it never get changed.
you could just use a regular variable instead of using state here:
const ChildComponent = ({ dataProp }) => {
const mappedData = mapData(dataProp);
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};