react table: refresh table after new data received - reactjs

I want to refresh my react table after new data is received. I was expecting I trigger this when I change state, however it doesnt work.
Below is state and the update function:
this.state = {
switchListSelected: [],
switchListData: [],
interfaceStatusData: [],
interfaceErrorsData: []
}
updateParentState(data, element){
this.setState(prevState => {
return prevState[element].push(data)
}
}

You are using setState wrong. You should call setState with the new state (or an updater function which returns the new state) and optionally a callback which will be called when the update is done. You can check the usage of setState in the react docs here.
In your example the updateParentState function should look like this:
updateParentState = (data, element) => {
this.setState({
...this.state,
[element]: [...this.state[element], data]
})
}
Note that the state of a component should never be mutated directly. Hence the creation of a new array with the spread operator instead of Array.push which mutates the array.

2 issues here :
When using setState you need to return an object of the state's attributes to update, here you return the result of prevState[element].push(data) (the array length).
You are mutating the array by using Array.push(), you need to use immutable pattern updates to correctly trigger a render with your updated state.
This should work for your case :
this.setState(prevState => ({
[element]: [...prevState[element], data],
}));

Related

How can I set React component state based on previous state using Immer?

I'm trying to figure out how to set my React component's state based on the previous state, while also using the Immer library to allow for easier immutable state changes.
Normally, without Immer, I'd safely access the component's previous state using the updater function version of setState():
this.setState(prevState => {
prevState.counter = prevState.counter + 1;
});
However, when using the Immer library to allow for easier immutable state changes, you no longer have access to the updater function, or the prevState variable. This is fine for most operations, as the Immer draft proxy can be used for simple changes - but it doesn't work for more complex changes such as comparisons, as the proxy doesn't reflect the original state object:
this.setState(produce(draft => {
const index = draft.list.indexOf(item); // doesn't work - always returns -1
if (index > -1) {
draftState.list.splice(index, 1);
}
});
The problem is that since the draft state is a proxy, comparisons such as indexOf always fail, since the two objects are inherently different. But I don't want to just use this.state in my produce function, as the React docs are very clear that you shouldn't rely on its value when calculating the new state, and I don't want to do the state update without Immer, as complex state object changes are significantly simpler when working with a mutable proxy object.
Is there any way to access an up to date version of the component's previous state, while still using the Immer produce function in my setState calls?
Edit:
Updating to add a (simplified) example of my actual code:
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
fooBar: {
selectedOptions: []
}
};
}
handleClick(clickedOption) {
/*
if clickedOption is already selected
remove option from selectedOptions
else
add option to selectedOptions
*/
this.setState(produce(draft => {
const index = draft.fooBar.selectedOptions.indexOf(clickedOption); // The problem is here
if (index > -1) {
draft.fooBar.selectedOptions.splice(index, 1);
}
else {
draft.fooBar.selectedOptions.push(clickedOption);
}
}));
}
The problem is at the const index = ... line. If I use draft, then the indexOf function always returns -1, and if I use this.state, then I'm not guaranteed an up-to-date version of selectedOptions to compare against...
You could implement a usePrevious() hook yourself to store any previous variable:
function usePrevious(value) {
const ref = useRef();
// Store current value in ref
useEffect(() => {
ref.current = value;
}, [value]); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
}

React hooks - not updating consistently

So I'm semi-new to hooks. I want to run some basic validation. Running into a strange issue: when I run two hooks back-to-back, only the second of two hooks works.
const [validationTracking, setValidationTracking] = useState({});
const setValidation = (idx, field, value) => {
const validationCopy = cloneDeep(validationTracking);
if (!validationCopy[idx]) {
validationCopy[idx] = {};
}
validationCopy[idx][field] = value;
setValidationTracking(validationCopy);
};
const validateInputs = () => {
partnerInfo.forEach((object, idx) => {
if (!object['title']) {
setValidation(idx, 'title', true);
}
if (!object['body']) {
setValidation(idx, 'body', true);
}
});
};
In the above code partnerInfo=[{title: '', body: ''}]
The validation only gets triggered for body when I run validateInputs
If the array has more than one item, only the very last field will get its validation set to true [{body: true}]
The input above SHOULD set validationTracking to [{title: true, body: true}]but it seems to skip or override earlier items
I know this.setState() in class-based components is async. I'm wondering if something similar is happening here..?
There are a few things to be aware of with the useState hook:
the state change will not be immediately visible to your component logic until the next re-render of your component (and reevaluation of any closures)
if multiple calls are made to a state hook's "setter" in a single render cycle, only the last state update will be collected and applied in the subsequent render cycle
The second point is more relevant to your question, given the forEach iteration in your code makes multiple calls to the setValiadation setter of your state hook. Because these are made in a single render cycle, only the last call to setValiadation will have an observable effect.
The usual way to address this is to gather all state changes into a single object, and apply those with a single call to your setter. You could take the following approach to achieve that:
const [validationTracking, setValidationTracking] = useState({});
// Revised function
const updateValidation = (object, idx, field, value) => {
const validationCopy = cloneDeep(object);
if (!validationCopy[idx]) {
validationCopy[idx] = {};
}
validationCopy[idx][field] = value;
return validationCopy
};
const validateInputs = () => {
// Call setter via a callback that transforms current state
// into a new state object for the component
setValidationTracking(state => {
// Reduce partnerInfo array to a new state object
return partnerInfo.reduce((acc, infoObject, idx) => {
if (!infoObject['title']) {
acc = updateValidation(acc, idx, 'title', true);
}
if (!infoObject['body']) {
acc = updateValidation(acc, idx, 'body', true);
}
return acc;
}, state);
});
};
Hope that helps!
You need to understand that useState hook works in a functional way. When you call it, it triggers a re-render of the component, passing the new state value to it. State values are immutable, they are not references to values that can change. This is why we say that a React function components acts as pure functions with respect to their props.
So when you call setValidationTracking(validationCopy) twice during a single update, you send two state updates that are computed using the current state for this iteration.
I.e: when the second loop calls cloneDeep(validationTracking), validationTracking has not changed because the re-render triggered by the first loop has not happened and the state value is immutable any way.
To fix the problem you can instead pass a state updater function:
setValidationTracking(currentValidationTracking => ({
...currentValidationTracking,
[idx]: {
...(currentValidationTracking[idx] || {}),
[field]: value
}
}));

Unable to setState with array of objects

I'm trying to setState of an empty array with an array of objects on component load.
I've tried new ES6 syntax, I've tried mapping, join, etc but can't get it to work.
The console output of the array I'm trying to insert (not push) into my state looks correct.
mapped arrayObj : [{"word":"teacher","correct":true,"image":"/Assets/Art/Icons/teacher.png"},{"word":"backpack","correct":false,"image":"/Assets/Art/Icons/backpack.png"},{"word":"paper","correct":false,"image":"/Assets/Art/Icons/paper.jpg"}]
Here's the function where I'm mapping my array of objects and then I'm trying to setState of my empty answersObj.
mapArray(){
const arrayObj = this.state.data.answers.map(obj => obj);
let shuffledObjArray = [{}];
shuffledObjArray = this.shuffleArray(arrayObj)
this.setState({
answersObj: shuffledObjArray
})
return shuffledObjArray;
}
I call the mapArray function when the component loads
componentDidMount() {
this.mapArray();
}
Don't forget that setState is async function, so the state doesn't sets immediately.
For example this piece of code works for me:
async componentDidMount() {
await this.mapArray();
console.log(this.state)
}
the obj state gets the value it needs, while w/o the async/await it would print empty state.
Therefore, if you need to render the data from that state I'd suggest making 'dataLoaded' bool and render the data only if the data finished loading.

React - how to add new element in array type state?

I have refereed this , this and this link to find solution but none of them work for me.
I have decleared state with array type and other state with null.
this.state = { from: '', to: '',journeyDate: '',searchDetails: [] }
As user fill up search form with source city , destination city and journey date I set these state like below .
let from = this.state.from;
let to = this.state.to;
let journeyDate = moment(this.state.journeyDate).format('YYYY-MM-DD');
final step to push these all variables into searchDetails array state. for that I have tried below options but none of them worked.
OPTION 1
this.setState({ searchDetails: [...this.state.searchDetails, from] });
OPTION 2
this.setState(prevState => ({
searchDetails: [...prevState.searchDetails, from]
}))
OPTION 3
let newState = this.state.searchDetails.slice();
newState.push(from);
this.setState({
searchDetails: newState
});
console.log('final state is : ' + this.state.searchDetails);
every time console log remains empty.
any advice ?
Actually setState is an asynchronous method. That means right after writing setState, you cannot expect the state to be changed immediately. So console.log right after setState may show you last state instead of new state. If you want to log the updated state then use the callback of setState which is called when state is updated like this
this.setState({ searchDetails: [...this.state.searchDetails, from] }, () => {
console.log(this.state.searchDetails)
});
Try :
this.setState({
searchDetails: newState // use any of your 3 methods to set state
},() => {
console.log('final state is : ' + this.state.searchDetails);
});
setState() does not always immediately update the component. It may
batch or defer the update until later.
This makes reading this.state
right after calling setState() a potential pitfall.
Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
For More Detail, Please read : https://reactjs.org/docs/react-component.html#setstate

React forceupload not resetting state

I have in my constructor set the state of key to 0 and on CommponentDidMount have the following code:
this.setState({ key: Math.random() });
console.log(this.state.key)
this.forceUpdate();
console.log(this.state.key)
But I get the same value for state. How is that possible?
The (setState) method is behaving like async, so any console.logs or logic after it doesn't mean it will wait till the state is updated, any logic you want to make sure it's executed after the state is updated wrap it in function and pass it as a second param to (setState) like this:
this.setState({ key: Math.random() }, () => {
console.log(this.state.key)
});
Also note that you can force update by using the (setState) with empty object like this: this.setState({});

Resources