Passing State to parent immediately after updating React - reactjs

Since react useState works like a queue ,I have issue accessing the data passed to the parent component by child component.
Input.js
const Input = (props) => {
const [isValid, setIsValid] = useState(false);
const [value, setValue] = useState("");
<input id={props.id} className="self-start justify-self-start border-gray-400 border-solid border rounded shadow"
name={props.name}
type={props.type}
onChange={(e) => {
let valid = isNaN(value);
setIsValid(valid);
props.handleChange(e, valid);
}}/>
}
parent.js contains a function which access the data from the child component
const handleChange = (e, valid) => {
setFormData({
...formData,
[addr.name]: { value: e.target.value, isValid: valid },
});
};
The parent component always gets the previous validity of the input component. Is there any other way of passing data to parent component with latest state immediately after a state change in child component.

Update the valid argument in the handleChange callback based on the current value of the input field.
onChange={(e) => {
let valid = isNaN(e.target.value);
setIsValid(valid);
props.handleChange(e, valid);
}}

Related

React initial state from props and any updates to local state should reflect in parent

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?

Does pass all [state, setState] to child component is an anti pattern?

I have a code like this
const ChildComponent = ({ products, setProducts }) => (
<form>
<input type="text" value={products.name} onChange={(e) => setProducts(e.target.value)} />
<input type="submit" value="Finish" />
</form>
)
const ParentComponent = () => {
const [products, setProducts] = useState(
{
id: 1,
name: "Test",
}
);
useEffect(() => {
// Where i call API to get list product and set it to child component
}, [])
return <ChildComponent products={products} setProducts={setProducts} />
}
for some reason , i can ONLY update state of ParentComponent in ChildComponent. It's work but i think it's so weird, that look like i change props of child component everytime when i make a edit of input. Can any one tell me that is an anti pattern or not.Sorry about my bad English. Thank you so much!
It's not an anti-pattern to pass the state object and state updater function as props, but this offloads the responsibility to update state correctly and maintain the state invariant to consuming components.
As you can see, your child component already messes up and changes the state shape/invariant from Object to String.
const [products, setProducts] = useState({ // <-- object
id: 1,
name: "Test",
});
... child
onChange={(e) => setProducts(e.target.value)} // <-- string value
On the subsequent render attempting to access value={products.name} in the child will fail as now products is a string.
I typically suggest declaring a handler function to do the state update and pass that instead.
In your snippet it seems the child component is more a "controlled input" meaning it's an input tag with a value and onChange handler. This is an example refactor I would do.
const ChildComponent = ({ value, onChange, onSubmit }) => (
<form onSubmit={onSubmit}>
<input type="text" value={value} onChange={onChange} />
<input type="submit" value="Finish" />
</form>
)
const ParentComponent = () => {
const [products, setProducts] = useState({
id: 1,
name: "Test",
});
const changeHandler = e => {
setProducts(products => ({
...products,
name: e.target.value,
}));
};
const onSubmit = e => {
e.preventDefault();
// handle the form submission
};
useEffect(() => {
// Where i call API to get list product and set it to child component
}, []);
return (
<ChildComponent
value={products}
onChange={changeHandler}
onSubmit={submitHandler}
/>
);
}
This way the parent maintains control over both the state updates and how the form data is submitted. The child hasn't any idea what the value represents and it isn't trying to update anything in any way, but simply passing back out the events.

Unable to update state for a component

I have just started learning react.
I have a component which is calling the weather API and fetching data for user provided location, user input is getting updated under userLoc but somehow the state is not getting updated for finalLoc and whenever I console log it, it is showing undefined.
const Inputs = props => {
const [userLocation, setUserLocation] = React.useState('')
const [finalLocation, setFinalLocation] = React.useState('')
function fetchLocation(e) {
setUserLocation (e.target.value)
}
function fetchDetails(e) {
e.preventDefault()
let baseURL = '//api.openweathermap.org/data/2.5/weather?q='
const API_KEY = '&API_KEY'
let total = baseURL + userLocation + API_KEY
console.log(userLocation) // Outputs the input value
setFinalLocation(userLocation)
console.log(finalLocation) // Comes as blank
console.log(total);
}
return (
<div className='inputs'>
<p className="label">Enter the location to find the weather.</p>
<input type="text" className='loc-input' autoFocus placeholder='Enter a location' name="" id="location" onChange={fetchLocation} value={loc.userLoc || ""} />
<button onClick={fetchDetails}>Get Details</button>
<Outputs loc={loc.finalLoc} results={loc.result} />
</div>
)
}
const Inputs = props => {
const [finalLoc, setFinalLoc] = React.useState("");
const [userLoc, setUserLoc] = React.useState("");
const [data, setData] = React.useState([]);
function fetchLocation(e) {
userLoc( e.target.value )
}
function fetchDetails(e) {
let baseURL = '//api.openweathermap.org/data/2.5/weather?q='
let API_KEY = '&appid=API_KEY'
let total = baseURL + loc.userLoc + API_KEY
setFinalLoc( userLoc )
fetch(total)
.then(response => response.json())
.then(data => {
setData( data )
})
}
return (
<div className='inputs'>
<p className="label">Enter the location to find the weather.</p>
<input type="text" className='loc-input' autoFocus placeholder='Enter a location' name="" id="location" onChange={fetchLocation} value={ userLoc || ""} />
<button onClick={fetchDetails}>Get Details</button>
<Outputs loc={ finalLoc} results={data} />
</div>
)
}
If you update a state when using useState you don't write it as
setState({state: //data})
But
setState(//data)
The updating method in useState is similar to class component setState method but you have to update it differently.
See more about useState here.
If your state is an object you should update it as an object:
const [state, setState] = useState({
name: "John",
lastName: "Due",
});
setState({
name: "Liam",
lastName: "call",
});
BTW,
If you are updating multiple values in your state, You should update it in one function.#1
Note:
Each time you're updating your state cuase React to re-render and slow your app.
For better performance you should always try to re-render your app as less as possible.
Edit:
#1 if your state is an object you should update it in one function.
You can use the functional form. The function will receive the previous value, and return an updated value. so to update the state we have to make copy of previous value with updated one and then set the state with updated values
setLoc((loc) => ({ ...loc, finalLoc: loc.userLoc }));
same when you get result data keep copy of previous state
setLoc((loc) => ({ ...loc, result: data }));

React collect child component data on some event from the parent component

In react best practice is Data flow from parent to child, event's will be passed from child to parent.
In this UI we have a parent component which contains 2 child components with forms. We now have to gather data from child components when the user clicks on the submit button in the parent.
Possible solutions (Bad solutions | anti pattern):
Pass ref from child and trigger child method from parent to collect data (Accessing child method from parent not a good idea)
<Parent>
onFormData(data) {
//here collect data from child
}
onSubmit() {
//Trigger a method onData in child
aRef.collectData()
}
<child-A ref={aRef} onData={onFormData}></Child-A>
<Child-B ref={bRef} onData={onFormData}></Child-B>
<button onClick={onSubmit}>Submit</Submit>
</Parent>
Bind a props to child, on submit click change value of prop with dummy value. Observe same prop in useEffect hook ( Really bad solution)
<Parent>
onFormData(data) {
//here collect data from child
}
onSubmit() {
//update randomValue, which will be observed in useEffect that calls onData method from child
randomValue = Math.random();
}
<child-A triggerSubmit={randomValue} onData={onFormData}></Child-A>
<Child-B triggerSubmit={randomValue} onData={onFormData}></Child-B>
<button onClick={onSubmit}>Submit</Submit>
</Parent>
Is there any other best approach for handling these scenario? How to a avoid anti pattern for this UI?
What I usually do is to "lift the state up" to the parent. This means I would not break the natural React flow, that is passing props from the parent to the children. Following your example, I would put ALL the logic in the parent (submit function, form state, etc)
Const Parent = () => {
const [formData, setFormData] = useState({})
const onSubmitForm = () => {
// send formData to somewhere
}
return (
<ChildrenForm onChange={setFormData} formData={formData} />
<button onSubmit={() => onSubmitForm()}>my button</button>
)
}
Now I would use the onChange function inside the Children to update the formData everytime an input in the ChildrenForm changes. So all my state will be in the parent and I don't need to worry about having to pass everything up, from children to parent (antipattern as you mentioned)
there is the third option (which is the standard way): you don't collect data, you pass your formData and setFormData to each Child as props. using lift state approach.
Each Child populates its input values with formData and uses setFormData to update formData that lives on Parent. Finally, at on submit you only have to set formDatato you request call.
below an example:
const ChildA = ({ formData, setFormData }) => {
const { name, age } = formData
const onChange = ({ target: { name, value } }) => { // destructuring 'name' and 'value'
setFormData(formData => ({ ...formData, [name]: value })) // spread formData, update field with 'name' key
}
return (
<>
<label>Name<input type="text" onChange={onChange} name="name" value={name} /></label>
<label>Age<input type="number" onChange={onChange} name="age" value={age} /></label>
</>
);
}
const ChildB = ({ formData, setFormData }) => {
const { email, acceptTerms } = formData
const onChange = ({ target: { name, value } }) => {
setFormData(formData => ({ ...formData, [name]: value }))
}
const onClick = ({ target: { name, checked } }) => {
setFormData(formData => ({ ...formData, [name]: checked }))
}
return (
<>
<label>email<input type="email" onChange={onChange} name="email" value={email} /></label>
<label>Accept Terms<input type="checkbox" onChange={onClick} name="acceptTerms" checked={acceptTerms} /></label>
</>
);
}
const Parent = () => {
// used one formData. you could break down into more if you prefer
const [formData, setFormData] = useState({ name: '', age: null, acceptTerms: false, email: undefined })
const onSubmit = (e) => {
e.preventDefault()
// here you implement logic to submit form
console.log(formData)
}
return (
<>
<ChildA formData={formData} setFormData={setFormData} />
<ChildB formData={formData} setFormData={setFormData} />
<button type="submit" onClick={onSubmit}>Submit</button>
</>
);
}
Approach 2) with random is Not Really So Bad! And only this one preserves encapsulation not involving DOM/withRef round trip. Suggested Up State way leads to hard coded dependency and undermines whole idea of re-usability. The only thing I have to mention is: code as it was typed above will not work - because usual variables will not trigger. Right way to make it like that:
// Parent component
let [randomValue, setRandomValue] = React.useState(Math.random());
const onSubmit = () => {
setRandomValue(Math.random());
console.log("click");
}
const onFormData = data => {
console.log(`${data}`);
}
...
<Child triggerSubmit={randomValue} onData={onFormData}/>
// Child component
useEffect(() => {
console.log("child id here");
prop.onData(`Hey Ho! Lets go!`)
}, [prop.triggerSubmit]);
At least this works for me!

React hook not setting the state

Hey all trying to use a useState react hook to set a state but it does not work, I gone through the official documentation
Seems like i have followed it correctly but still cannot get the hook to set the state:
const [search, setSearch] = useState('');
const { films } = props;
const matchMovieSearch = (films) => {
return films.forEach(item => {
return item.find(({ title }) => title === search);
});
}
const handleSearch = (e) => {
setSearch(e.target.value);
matchMovieSearch(films);
}
<Form.Control
type="text"
placeholder="Search Film"
onChange={(e) => {handleSearch(e)}}
/>
Search var in useState is allways empty even when i debug and can see that e.target.value has to correct data inputed from the html field
setSearch is an async call, you won't be able to get the search immediately after setting the state.
useEffect is here for rescue.
useEffect(() => {
// your action
}, [search]);
Are you sure you are using the hooks inside a component, hooks can only be used in a Functional React Component.
If that is not the case, there must be something wrong with the Form.Control component, possibly like that component did not implement the onChanged parameter properly.
This is the one I tested with the html input element, and it is working fine. I used the useEffect hook to track the changes on the search variable, and the you can see that the variable is being properly updated.
https://codesandbox.io/s/bitter-browser-c4nrg
export default function App() {
const [search, setSearch] = useState("");
useEffect(() => {
console.log(`search was changed to ${search}`);
}, [search]);
const handleSearch = e => {
setSearch(e.target.value);
};
return (
<input
type="text"
onChange={e => {
handleSearch(e);
}}
/>
);
}

Resources