I have component which receives data from other file and setting into state:
const [sortedPlans, setSortedPlans] = useState(
data.Plans.sort((a, b) => b.IndustryGrade - a.IndustryGrade)
);//data is from external file
After setting the state, sorting the data and rendering the initial screen, I have function that sorts the sortedPlans whenever it is called:
const sort = (event) => {
console.log(event);
const newArr = sortedPlans.sort((a, b) => {
return b[event] - a[event];
});
console.log(newArr);
return setSortedPlans(newArr);
};
The problem is that this is never triggering a re-render of the component. I want when I set the new state to be able to see it inside the jsx. Why when I console.log(newArr) the results are correctly sorted but this setting of the state not triggering re-render? Here is the sandbox:
https://codesandbox.io/s/charming-shape-r9ps3p?file=/src/App.js
Here you go: Codesandbox demo.
You should first make a shallow copy of the array you want to modify. Then set that array as the new state. Then it re-renders the component and you are able to filter like you want.
const sort = (event) => {
console.log(event);
//shallow copying the state.
const newArr = [...sortedPlans];
//modifying the copy
newArr.sort((a, b) => {
return b[event] - a[event];
});
console.log(newArr); //results here are correctly sorted as per event
//setting the state to the copy triggers the re-render.
return setSortedPlans(newArr);
};
Related
I am using an array of components that are interested depending on various conditions i.e the order and number of elements in the array are dynamic as shown below:
useEffect(() => {
const comp = [];
// if(condition1===true){
comp.push(<MyComp onChange={onValueChange} />);
// }
// if(condition2===true){
comp.push(<YourComp onChange={onValueChange} />);
// }
// if(condition3===true){
comp.push(<HisComp onChange={onValueChange} />);
// }
setComponents(comp);
}, []);
To each of the components in the array, there could be some sort of input control like input-text, input-number, text-area, chips, radio, checkbox, etc.
So there is an onChange event linked to each of these components.
I am using a common onValueChange function which is passed as a callback to these components. In the onValueChange I need 2 things:
changed value (from child component)
activeIndex (from same component)
const onValueChange = (val) => {
console.log("onChange Valled", val, "activeIndex==", activeIndex);
};
But here I am not able to fetch the updated value on activeIndex, it always gives zero no matter in what active step I am in.
Sandbox DEMO
useEffect(() => {
setComponents((previousValues)=>{
// if you want to add on previous state
const comp = [...previousValues];
// if you want to overwrite previous state
const comp = [];
if(condition1===true){
comp.push();
}
if(condition2===true){
comp.push();
}
if(condition3===true){
comp.push();
}
return comp;
});
}, []);
Try using useCallback with dependency array. Also try to avoid storing components in state - the office advice - what shouldn’t go in state?
const onValueChange = useCallback((val) => {
console.log("onChange Valled", val, "activeIndex==", activeIndex);
},[activeIndex];
For rendering try something like below.
condition1===true && <MyComp onChange={onValueChange} />
or create a function which returns the component eg: getComponent(condition) and use this in render/return. Make sure you wrap getComponent in useCallback with empty dependency array []
I'm trying to set state of array from grand child component which is located in parent component.
I tried to use setNumberOfVar([...varsLines]); this method but it's not updating the state immediately.it updates but one step back.
My Code
const removeVar = (e, val) => {
e.preventDefault();
var varsLines = numberOfVar;
varsLines = numberOfVar.filter((item) => {
return item.postion != val;
});
varsLines = varsLines.map((item) => {
return {
...item,
postion: item.postion > val ? item.postion - 1 : item.postion,
};
});
console.log(varsLines);
setNumberOfVar([...varsLines]); // <== this line is not updating the state immediately;
console.log(numberOfVar);
};
setNumberOfVar() is an async operation and will not update state immediately.
If you want to get something like a callback when the state is updated, you can use the useEffect hook that runs on state update.
import {useEffect } from 'react';
useEffect(() => {
console.log(numberOfVar);
},[numberOfVar]);
try to do something like this
setNumberOfVar(prevState=>prevState.filter().map())
use your own conditions in filter and map and see if it's working or not.
This function is added to a button click.
const listChangeHandler = (e) => {
e.preventDefault();
setIngredientList((prevArray) => [...prevArray, ingredient]);
inputIngredient.current.value = "";
props.data(ingredientList);
};
Problem is: The received state (ingredientList) that I get in the parent comp is the previous, not the latest thats shown in the child component
Why and what could be the workaround?
It is due to this fact that state always updates after rerendering, so when you pass it to upper component, it still has its previous value!
To solve this problem you have two solutions:
update your code as follow:
const listChangeHandler = (e) => {
e.preventDefault();
let temp = [...ingredientList, ingredient];
setIngredientList(temp);
inputIngredient.current.value = "";
props.data(temp);
};
use useRef instead of useState because in contrast to state, ref updates imediately.
I am using useEffect in react to listen to redux(easy-peasy) state change, but I want to listen to 1st value change only.
Because when my page loads the state has a default value and then API call is made and hence data changes but the API is a polling API, hence it keeps getting the data again and again in a short interval of time. But one of my requirement is to listen only to the 1st API data.
This is what I tried:
1st Approach with empty dependency
useEffect(() => {
// my code
},[])
In this case, I get the default value of my state and not the 1st API response.
2nd Approach with state property in the dependency
useEffect(() => {
// my code
},[myState])
In this case, I keep getting the updated response from the API
both of these approaches didn't work for me. So please suggest a better solution.
You can do so using a ref variable and comparing the state with initial state (which could be null, undefined, empty object depending on your implementation):
const hasRun = useRef(false)
useEffect(() => {
if (!hasRun.current && myState !== initialState) {
hasRun.current = true
// run my code
}
},[myState])
A ref variable won't participate in re-rendering.
What I usually do with this is to have a "previous" state.
You can use this hook for to do that one:
const usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]); // only re-run if value changes
// return previous value (happens before update in useEffect)
return ref.current;
}
You can then do:
// I usually create a distinct initial state
const [var, setVar] = useState(null);
const prevVar = usePrevious(var);
useEffect(() => {
if (var !== prevVar && var !== null) {
// logic here
}
}, [prevVar, var]);
Yers you can simplify this by not using the usePrevious hook, but the hook is quite handy and can be used to check the previous value and the current one.
I'm using React.js with hooks and trying to change a nested state value.
Since I don't want to mutate the state directly, I'm using lodash's "CloneDeep" function.
The state changes successfully, but a re-render doesn't happen so I don't see the change in the page until I re-enter it.
More info (checks results in my web console):
console.log(logicalLinesDeepCopy == logicalLines); => false
console.log(logicalLinesDeepCopy === logicalLines); => false
console.log(logicalLines[menuLineId].content[menuSentenceId].triggers); => []
console.log(logicalLinesDeepCopy[menuLineId].content[menuSentenceId].triggers); => [(object)]
My code:
// relevant imports:
import CloneDeep from "lodash/cloneDeep";
// relevant state initiation: (the value is filled before I call the relevant function)
const [logicalLines, setLogicalLines] = useState(null);
// relevant function:
const addTriggerToListInState = (trigger) => {
// Deep copy:
let logicalLinesDeepCopy = CloneDeep(logicalLines);
// Change the deep copy:
const lineCopy = logicalLinesDeepCopy[menuLineId];
const triggersListCopy = lineCopy.content[menuSentenceId].triggers;
triggersListCopy.push(trigger);
// Some debug checks:
console.log(logicalLinesDeepCopy == logicalLines);
console.log(logicalLinesDeepCopy === logicalLines);
console.log(logicalLines[menuLineId].content[menuSentenceId].triggers);
console.log(logicalLinesDeepCopy[menuLineId].content[menuSentenceId].triggers);
// Setting the state:
setLogicalLines(logicalLinesDeepCopy);
};
So it looks like both the reference and the nested content of the new state are different from the previous one, then why doesn't it cause a re-render and how can I change it to cause it?
Thanks!
Sapir