I am confused as to how my dependencies change every render? - reactjs

// We are only running this at initial render and anytime
// yelpResults gets updated (only once per food and location submit)
useEffect(() => {
// Creating a temp array so our restaurantIndexes is immutable
let tempArray = [];
// Concatenating the value of each index into our state
Object.keys(yelpResults).map((index) => tempArray.push(index));
// Saving the results of our restaurant indexes
setRestaurantIndexes(tempArray);
}, [yelpResults, restaurantIndexes]);
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

I think what you are doing wrong is you are re-rendering things when one of these values changes [yelpResults, restaurantIndexes] because useEffect renders again when value changes.So a better solution would be to put an if statement which will check if the value has changed or not.
const [restaurantIndexes, setRestaurantIndexes] = useState("")
useEffect(() => {
// Creating a temp array so our restaurantIndexes is immutable
let tempArray = [];
// Concatenating the value of each index into our state
Object.keys(yelpResults).map((index) => tempArray.push(index));
console.log(tempArray);
//Check if tempArray has changed
if(restaurantIndexes !== tempArray) return
// Set the RestaurantIndex state
setRestaurantIndexes(tempArray);
}, [yelpResults,restaurantIndexes]);

Related

useTracker dependency array? react meteor

I have a question about useTracker from meteor and react.
const sample = useTracker(
() => func1(),
[slug],
);
Does this mean every time sulg gets changed, fun1 gets running and sample will get a new value? like a dependency array with useEffect?
Thank you
You are partially right, if slug updates your function re-runs but it would also re-run if the function func1 is using a reactive data source.
The documentation for useTracker has a pretty good example:
// This computation uses no value from the outer scope,
// and thus does not needs to pass a 'deps' argument.
// However, we can optimize the use of the computation
// by providing an empty deps array. With it, the
// computation will be retained instead of torn down and
// rebuilt on every render. useTracker will produce the
// same results either way.
const currentUser = useTracker(() => Meteor.user(), []);
// The following two computations both depend on the
// listId prop. When deps are specified, the computation
// will be retained.
const listLoading = useTracker(() => {
// Note that this subscription will get cleaned up
// when your component is unmounted or deps change.
const handle = Meteor.subscribe('todoList', listId);
return !handle.ready();
}, [listId]);
// my personal note: this function updates either if
// lsitId changes OR if Tasks get updated by pub/sub
// for example because documents have been inserted/updated/removed
// on the server
const tasks = useTracker(() => Tasks.find({ listId }).fetch(), [listId]);
see: https://github.com/meteor/react-packages/tree/master/packages/react-meteor-data#usetrackerreactivefn-deps-hook-with-deps

Managing state of individual rows in react js table

I have a requirement where for each row of a table(rows are dynamically populated), I have a 'Details' button, which is supposed to pop up a modal upon clicking. How do I maintain a state for each of the rows of this table, so that React knows which row I am clicking, and hence pass the props accordingly to the modal component?
What I tried out was create an array of all false values, with length same as my data's. The plan is to update the boolean for a particular row when the button is clicked. However, I'm unable to execute the same.
Here's what I've tried so far:
let initTableState = new Array(data.length).fill(false)
const [tableState, setTableState] = useState(initTableState)
const detailsShow = (index) => {
setTableState(...tableState, tableState[index] = true)
}
I get the 'data' from DB. In the function detailsShow, I'm trying to somehow get the index of the row, so that I can update the corresponding state in 'tableState'
Also, here's what my code to put in modal component looks like, placed right after the row entries are made:
{tableState[index] && DetailModal}
Here DetailModal is the modal component. Any help in resolving this use case is greatly appreciated!
The tableState is a single array object. You are also spreading the array into the setTableState updater function, the state updater function also takes only a single state value argument or callback function to update state.
You can use a functional state update and map the previous state to the next state, using the mapped index to match and update the specific element's boolean value.
const detailsShow = (index) => {
setTableState(tableState => tableState.map((el, i) => i === index ? true : el))
}
If you don't want to map the previous state and prefer to shallow copy the array first and update the specific index:
const detailsShow = (index) => {
setTableState(tableState => {
const nextTableState = [...tableState];
nextTableState[index] = true;
return nextTableState;
})
}

Infinite loop in useEffect fix?

I am currently pulling data from a firebase realtime database and the data will initial populate. When I refresh the page though, the contents disappear. I added an empty array at the end of the UseEffect() to stop the infinite loop issue that we were having, but it seems to stop updating our array when refreshed.
useEffect(() => {
let jobs = [];
firebase.database().ref("1h5GOL1WIfNEOtcxJVFQ0x_bgJxsPN5zJgVJOePmgJOY/Jobs").on("value", snapshot => {
snapshot.forEach(snap => {
jobs.push(snap.val());
});
})
populateJobs(jobs);
},[]);
As ray commented, it does matter how populateJobs is defined. But at a first guess, you'll need to call that from inside the callback:
useEffect(() => {
firebase.database().ref("1h5GOL1WIfNEOtcxJVFQ0x_bgJxsPN5zJgVJOePmgJOY/Jobs").on("value", snapshot => {
let jobs = [];
snapshot.forEach(snap => {
jobs.push(snap.val());
});
populateJobs(jobs);
})
},[]);
I assume populateJobs is function declared in your scope.
If so, you may want to wrap it in useCallback to ensure the function reference doesn't change.
const populateJobsFixed= useCallback(populateJobs, [])
useEffect(() => {
...
populateJobsFixed(jobs);
},[populateJobsFixed]);
populateJobs is a dependency of the useEffect
You need to have the dependency list be
},[populateJobs]);
Instead of an empty array
The second argument of useEffect(()=>{},[]) which takes an array of argument tells react on what changes your callback passes there should run.
If passed an empty array it runs only once, behaving like componentdidmount method
When a variable is passed, it runs every time the value of the variable is changed.
Also if we pass an object as second parameter it will check for the reference change too.

Input element's value in react component is not getting re-rendered when the state changes

My state object is a Map
const [voucherSet, setVoucherSet] = useState(initialVoucherSet);
initialVoucherSet is map I create at the beginning of the stateless component function.
const initialVoucherSet = new Map();
activeVoucher.voucherDenominations.forEach(voucherDemonination=> {
initialVoucherSet.set(voucherDemonination, 0);
});
const [voucherSet, setVoucherSet] = useState(initialVoucherSet);
activeVoucher.voucherDenominations an array of numbers.
I have a input which triggers a function on onChange.
const handleChange = (e)=>{
const voucherDemonination = parseInt(e.target.id);
const voucherQuantity = parseInt(e.target.value);
if (voucherQuantity >= 0) { setVoucherSet(voucherSet.set(voucherDemonination, voucherQuantity)); }
}
The state object voucherSet is getting updated, but my input's value is not getting re-rendered.
Below is the input element:
<CounterInput type='number' id={voucherDemonination} onChange={handleChange} value={voucherSet.get(voucherDemonination)} />
What I already tried
I thought that it might be because I was not setting a different object to the voucherSet state variable. So I tried something a bit hacky...
const handleChange = (e)=>{
const voucherDemonination = parseInt(e.target.id);
const voucherQuantity = parseInt(e.target.value);
if (voucherQuantity >= 0) {
const tempVoucherSet = voucherSet;
tempVoucherSet.set(voucherDemonination, voucherQuantity);
setVoucherSet(tempVoucherSet);
}
}
but it still didn't work.
Where am I wrong?
Much Thanks in advance! :)
So what is happening is that the Map itself is not changing (eg. every time you update the Map, you still have a reference to the same exact Map in memory), so react is not rerendering.
This falls under the whole "immutable" thing with react. Any time a state change happens, a new object or array ow whatever should be created so that react and easily detect that something changed and thus rerender. This makes it so react doesn't have to iterate over every key in your object/array to see if anything changed (which would kill your performance).
Try this in the code which updates your map:
tempVoucherSet.set(voucherDemonination, voucherQuantity);
setVoucherSet(new Map(tempVoucherSet)); // -> notice the new Map()
This is analogous to other code you may have seen with react and state changes where new objects/arrays are created any time a new property/item is added:
setState({ ...oldState, newProperty: 'newValue' })
setState([ ...oldArray, newItem ]);
I had the same issue in the past. Set your state this way:
setVoucherSet(new Map(voucherSet.set(voucherDemonination, voucherQuantity)));
That will cause a re-render.

Can I use dynamic properties in dependency array for useEffect?

So I have a question regarding useEffect dependenices
This is from the react docs:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
What does this mean exactly, does React keep track of the count variable and its value, and reacts when the value changes, or does React keep track of the first element in the array and its value.
What do I mean by this? Let me explain more. So if we have something like this [name] as dependencies. At the moment of evaluation, the array might result with ['Bob'] or ['Steve']. Clearly this is a change and the useEffect will rerender the component. But how does it check it?
Does it keep track of name or does it keep track of dependencyArray[0]. If we take a look in the previous example, both of these would result to true, name and the first element both changed their values from 'Bob' to 'Steve'. But how does it actually work?
Currently in my code I am using something like this [employees[selectedEmployee].name], where selectedEmployee is something clickable on the UI and it becomes 'Bob' or 'Steve'
ex:
const employees = {
Bob: {
name: 'Bob'
},
Steve: {
name: 'Steve'
}
}
This means that in the end, when evaluated, the dependency array will still result with ['Bob'] --> ['Steve'], and if React is evaluating the dependencyArray[0] then that has clearly changed and component should rerender, but If it keeps track of the reference, then I am changing the reference altogether and it may cause problems.
So what's the correct approach? Can I use dynamic properties like employees[selectedEmployee].name as a dependency?
count is a value, not a reference.
It's just good old Javascript, nothing fancy:
const myArray = [ count ]; // new array containing the value of variable 'count'
const myFunction = () => {
document.title = `You clicked ${count} times`;
}
useEffect(
myFunction,
myArray
);
// Means actually:
// "Run this function if any value in the array
// is different to what it was last time this useEffect() was called"
does React keep track of the ... value, or ... the reference ?
React doesn't really 'keep track' of any of them. It only checks the difference to a previous call, and forgets about everything else.
Can I use dynamic properties as a dependency?
Yes, you can (because they are not as 'dynamic' as you think).
So what's the correct approach?
Better think less of any react-magic going on, but
understand that the component is a function, and believe React calls it when necessary and
think about the variables (properties and state) used inside it, from a plain Javascript perspective.
Then your 'dynamic properties' become 'constant variables during one function call'. No matter which variables change dynamically and how, it will always be one value last time and one value now.
Explaination:
The important 'trick' here is, that the component is just a javascript function, that is called like 'whenever anything might have changed', and consequently useEffect() is also called (as useEffect() is just a function call inside the component).
Only the callback function passed to useEffect is not always called.
useEffect does not render the component, useEffect is called when the component is called, and then just calls the function given to it, or not, depending on if any value in the dependencies array is different to what it was last time useEffect() was called.
React might rerender the component if in the function given to useEffect there are any changes made to the state or something (anything that makes React to think it has to rerender), but that's as a result of this state change, where ever it came from, not because of the useEffect call.
Example:
const MyComponent = (props) => {
// I'm assigning many const here to show we are dealing with local constants.
// Usually you would use this form (using array destructuring):
// const [ selectedEmployee, setSelectedEmployee ] = useState( someInitialValue );
const myStateValueAndSetter = useState( 'Bob' );
const selectedEmployee = myStateValueAndSetter[0];
const setSelectedEmployee = myStateValueAndSetter[1];
const employees = {
Bob: { name: 'Bob' },
Steve: { name: 'Steve' }
};
const currentName = employees[ selectedEmployee ].name;
useEffect(() => {
document.title = 'current name: ' + currentName;
}, [ currentName ]);
return <MyClickableComponent onClick={( newValue ) => {
setSelectedEmployee( newValue )
}}>;
};
click on MyClickableComponent calls the current setSelectedEmployee( newValue ) function.
(The constant selectedEmployee is not changed!)
MyComponent() is called again.
(This is a new function call. All the constants are gone! Only React stores some state in the background.)
useState() is called, the result is stored in a new constant selectedEmployee.
useEffect() is called, and decides if its callback should be called, depending on the previous and the current value of selectedEmployee.
If the callback is not called and nothing else is changed, you might not notice that anything has happened at all.
<MyClickableComponent ... /> is rendered.

Resources