Unable to correctly update parent object state - javascript

I have multiple child components and need to communicate between these siblings. What im currently trying:
Parent has object state and pass setMessageItem as a prop to all children:
const [messageItem, setMessageItem] = useState({})
Children:
useEffect(() => {
if(condition) {
props.setMessageItem(prevState => ({...prevState, messageData}))
..}
}
}[])
However only data from one children reaches this object state. Is there any way I could make it work when two or more children are trying to change the parent's state at the same time?
So the end result would be that messageData would contain data from multiple children at the same time.

That useEffect() will only be run once, when the component is instantiated. If you want to use it like that you would do:
useEffect(() => {
props.setMessageItem(prevState => ({ ...prevState, messageData }));
}, [condition]);
Then it would trigger on each change of condition.

Related

Updating an array of objects with setTimeout with react and state

I have an array of objects, and each object has a false property value by default:
const [items, setItems] = useState([ { key: 1, has_x: false }, { key: 2, has_x: false }, { key: 3, has_x: false } ]);
Which is passed down to child components:
<Item key={item.key} hasX={item.has_x} />
In the parent component, I have a "Check All" button:
<button onClick={handleCheckAll}>Check All</button>
Which would loop through every item and modify item.has_x to true.
In the child components, there's also a "Check" button, but instead of checking all items, it just checks and sets that one specific item has_x to true.
I think I would know how to do each one. For the "Check All" function, I'd create a shadow copy, set the value, and then once the loop is done, set the state. For the child button, I really just need a useState there for it.
However, I am stuck on the "Check All" button because I'd like to have ui updates as has_x for each item gets updated and a setTimeout, as the actual functionality of check all will be expensive and needs about 200-300ms wait time for each check. Imagine a button's text changes to a checkmark as the function loops through each item.
Here's my attempt but the UI doesn't get updated and I only am setting the state once it's done:
const checkAllItems = () => {
let temp = [...items];
temp.map((item, index) => {
setTimeout(() => {
let tempEl = {...tempState[index]};
if (tempEl) item.has_x = true;
}, index * 200)
})
setItems(temp)
}
My only idea of how to do this is to use refs and loop through the refs to run a function in each child component that will do this, but I feel like there's a correct way to go about this. How is it possible?
I can't imagine needing a timeout for updating state. I am certain if you tweak your check-all items code a bit you won't need the timeout. You can use a functional state update to enqueue the state updates and correctly update from the previous state instead of the state value closed over in callback scope.
const checkAllItems = () => {
setItems(items => items.map(item => ({
...item,
has_x: true,
})));
};
This shallow copies the previous items state into a new array, and then you also shallow copy each item element and update the has_x property.
Update
If you are making backend API requests per checked update in items then I suggest doing this in a useEffect lifecycle hook. Loop over each item and enqueue the network request in a setTimeout. It's basically splitting out the timeout logic (you had previously) from the state update.
useEffect(() => {
items.forEach((item, index) => {
setTimeout(() => {
// make API request with `item`
}, index * 200);
});
}, [items]);
/update
I also don't recommend the child components to also have any "checked" state, you want a single source of truth as to the checked status of your items array. Pass a callback to the children for them to update their checked status in the parent component's state.
It could look something like this:
const updateStatus = (key, value) => {
setItems(items => items.map(item => item.key === key
? {
...item,
has_x: value,
}
: item
));
};
...
<Item key={item.key} hasX={item.has_x} updateX={updateStatus} />

Re-render component once on both With and Without Update of Props - REACT HOOKS

Is it possible to force re-render component once with or without any changes in props..
const SearchGenresResult = ({ Id }) => .....
useEffect(() => {
callUrl(url);
setTheNewInputValue(Id);
}, [Id]);
Explanation :
5 images with Id as 1, 2, 3, 4 and 5
On click of any images, the id is passed as a prop.
On receiving the Id, details of that image is displayed on a modal.
All is working except clicking same image two times.
Though the id passed as a prop but the useEffect is not
triggered as the value of previous and current prop remains same. Hence
the modal is not displayed.
How to solve this issue..
Tried using const forceUpdate = useState()[1] but no luck..
Well instead of passing id as a string you can pass an object with id as a key and add that as a reference to useEffect dependency.
In such a case whenever you setState for id in parent, a new reference of the object is created and hence the useEffect check breaks causing it to run again
const SearchGenresResult = ({ stateId }) => .....
useEffect(() => {
callUrl(url);
setTheNewInputValue(stateId.Id);
}, [stateId]);
...
}
and in Parent
...
const [stateId, setStateId] = useState({});
..
function fn(someId) {
setStateId({id: someId});
}
...
<SearchGenresResult stateId={stateId} />
However if you passing the sameId to child, you shouldn't actually be needed a re-render

How do I pass a prop to a react component yet not update that prop in the child when parent changes?

const Parent = () => {
const [thing, setThing] = useState('a string');
// code to update thing
return <Child thing={thing} />
}
const Child = props => {
return <div>I want {props.thing} to be initial value without updating</div>
}
If I want 'thing' to be passed from parent to child but not update when parent changes it, how do I accomplish this?
I've tried useEffect, tried cloning 'thing' to a constant within Child...
I would use useEffect with the empty [] for dependencies, so that it only runs once.
From Reactjs.org:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
const Child = props => {
let thing = props.thing;
let [initialValue, setInitialValue] = useState("");
useEffect(() => {
setInitialValue(thing);
}, []);
return (
<div>
I want <strong>{initialValue}</strong> to be initial value without
updating
</div>
);
};
CodeSandbox
Maybe you can try setting the thing to a variable in the child component when it's null. So when the parent update, you don't have to update the child.
let childThing = null;
const Child = props => {
if(childThing === null)
childThing = props.thing
return <div>I want {childThing} to be initial value without updating</div>
}

How to sort React components based on specific value?

I have React component. This components take 'units' - (array of objects) prop. Based on that I render component for each of item. I want to sort my components based on 'price' value, which is one of state items property. But when i trigger the sorting - state changes correctly but my components order not changing.
const SearchBoxes = ({units}) => {
const [unitsState, setUnitsState] = useState([])
useEffect(() => {
setUnitsState(units)
}, [units])
const sortByPrice = () => {
const sortedUnits = sort(unitsState).desc(u => u.price); // sorting is correct
setUnitsState(sortedUnits) // state is changing correctly
}
return (
<div>
{unitsState.map((u,i) => {
return <UnitBox key={u.price} unit={u} />
})}
</div>
)
}
Can somebody help me, please ?
Why my components order do not changing when the state is changing after sort triggering ?
You aren't calling sortByPrice anywhere--all you've done is to define the function. I haven't tried it, but what if you changed useEffect to:
useEffect(() => {
setUnitsState(sort(unitsState).desc(u => u.price));
}, [units])
Then you don't need the sort method at all.

ReactJS - loop througt child and than in parent display first 10 child objects

I have a problem with getting data from child to parent that is fetched from API
Child:
class Carton extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
matchData: []
};
}
Here I'm fetching data from API and updating the state
async componentDidMount() {
setTimeout(() => {
this.loading = true;
fetch(url)
.then(res => res.json())
.then(data => {
this.setState({
matchData: data.doc[0].data[this.props.sportId].realcategories
});
});
}, 500);
I have looped through nested objects from API and displayed it in the child but I'm stuck how to get data of nested objects to parent. Here is an example how deep I needed to go to get values that I needed
getData() {
this.state.matchData.map((data, i) =>
data.tournaments.map((tour, j) =>
tour.matches.map((matc, k) =>
//values of objects that i need to render a card in Child component
)//...
My first question is if it's better to practice to fetch data from API in parent and then pass objects to the child?
How to pass those objects to parent so I can iterate over and select the first 5 of them for
example
If anybody has any idea or suggestions, please let me know
Br
Stevo
My first question is if it's better to practice to fetch data from API in parent and then pass objects to the child?
It depends. If your parent object needs to be aware of the children without any event occurring inside the child (such as a click, focus, blur, etc.), then yes, the API call should occur inside the parent. If that is not the case, and the parent only needs to be aware of what child elements were acted up on the children, then you can use a function passed into the child from the parent.
For instance:
class Parent {
const handleSelection = childData => {
doSomething(childData)
}
render() {
return(
<Child handleSelection={handleSelection} />
)
}
}
class Child {
render() {
const { myData } = this.state;
return(
<div onClick={() => this.props.handleSelection(myData)} />
)
}
}
This will pass the data from the child component back out to the parent component when the user clicks on the div in child. Hope this helps.
You can use a callback for the purpose, and create a state variable for matchData in the parent component instead. Then pass the matchData state to the child. When you make the API call in componentDidMount, make a call to the callback function (defined in the parent), sending the data you fetched from the API as args. And further in the callback function, set the matchData state variable, which will then automatically re-render both the parent & the child.

Categories

Resources