How can I access React state in my eventHandler? - reactjs

This is my state:
const [markers, setMarkers] = useState([])
I initialise a Leaflet map in a useEffect hook. It has a click eventHandler.
useEffect(() => {
map.current = Leaflet.map('mapid').setView([46.378333, 13.836667], 12)
.
.
.
map.current.on('click', onMapClick)
}, []
Inside that onMapClick I create a marker on the map and add it to the state:
const onMapClick = useCallback((event) => {
console.log('onMapClick markers', markers)
const marker = Leaflet.marker(event.latlng, {
draggable: true,
icon: Leaflet.divIcon({
html: markers.length + 1,
className: 'marker-text',
}),
}).addTo(map.current).on('move', onMarkerMove)
setMarkers((existingMarkers) => [ ...existingMarkers, marker])
}, [markers, onMarkerMove])
But I would also like to access the markers state here. But I can't read markers here. It's always the initial state. I tried to call onMapClick via a onClick handler of a button. There I can read markers. Why can't I read markers if the original event starts at the map? How can I read the state variables inside onMapClick?
Here is an example: https://codesandbox.io/s/jolly-mendel-r58zp?file=/src/map4.js
When you click in the map and have a look at the console you see that the markers array in onMapClick stays empty while it gets filled in the useEffect that listens for markers.

React state is asynchronous and it won't immediately guarantee you to give you the new state, as for your question Why can't I read markers if the original event starts at the map its an asynchronous nature and the fact that state values are used by functions based on their current closures and state updates will reflect in the next re-render by which the existing closures are not affected but new ones are created, this problem you wont face on class components as you have this instance in it, which has global scope.
As a developing a component , we should make sure the components are controlled from where you are invoking it, instead of function closures dealing with state , it will re-render every time state changes . Your solution is viable you should pass a value whatever event or action you pass to a function, when its required.
Edit:- its Simple just pass params or deps to useEffect and wrap your callback inside, for your case it would be
useEffect(() => {
map.current = Leaflet.map('mapid').setView([46.378333, 13.836667], 12)
.
.
.
map.current.on('click',()=> onMapClick(markers)) //pass latest change
}, [markers] // when your state changes it will call this again
for more info check this one out https://dmitripavlutin.com/react-hooks-stale-closures/ , it will help you for longer term !!!

Long one but you'll understand why this is happening and the better fixes. Closures are especially an issue (also hard to understand), mostly when we set click handlers which are dependent on the state, if the handler function with the new scope is not re-attached to the click event, then closures remain un-updated and hence the stale state remains in the click handler function.
If you understand it perfectly in your component, useCallback is returning a new reference to the updated function i.e onMapClick having your updated markers ( the state) in its scope, but since you are setting the 'click' handler only in the beginning when the component is mounted, the click handler remains un-updated since you've put a check if(! map.current), which prevents any new handler to be attached on the map.
// in sandbox map.js line 40
useEffect(() => {
// this is the issue, only true when component is initialized
if (! map.current) {
map.current = Leaflet.map("mapid4").setView([46.378333, 13.836667], 12);
Leaflet.tileLayer({ ....}).addTo(map.current);
// we must update this since onMapClick was updated
// but you're preventing this from happening using the if statement
map.current.on("click", onMapClick);
}
}, [onMapClick]);
Now I tried moving map.current.on("click", onMapClick); out of the if block, but there's an issue, Leaflets instead of replacing the click handler with the new function, it adds another event handler ( basically stacking event handlers ), so we must remove the old one before adding the new one, otherwise we will end up adding multiple handlers each time onMapClick is updated. For which we have the off() function.
Here's the updated code
// in sandbox map.js line 40
useEffect(() => {
// this is the issue, only true when component is initialized
if (!map.current) {
map.current = Leaflet.map("mapid4").setView([46.378333, 13.836667], 12);
Leaflet.tileLayer({ ....
}).addTo(map.current);
}
// remove out of the condition block
// remove any stale click handlers and add the updated onMapClick handler
map.current.off('click').on("click", onMapClick);
}, [onMapClick]);
This is the link to the updated sandbox which is working just fine.
Now there's another Idea to solve it without replacing click handler each time. i.e some globals, which I believe is not really too bad.
For this add globalMarkers outside but above your component and update it each time.
let updatedMarkers = [];
const Map4 = () => {
let map = useRef(null);
let path = useRef({});
updatedMarkers = markers; // update this variable each and every time with the new markers value
......
const onMapClick = useCallback((event) => {
console.log('onMapClick markers', markers)
const marker = Leaflet.marker(event.latlng, {
draggable: true,
icon: Leaflet.divIcon({
// use updatedMarkers here
html: updatedMarkers.length + 1,
className: 'marker-text',
}),
}).addTo(map.current).on('move', onMarkerMove)
setMarkers((existingMarkers) => [ ...existingMarkers, marker])
}, [markers, onMarkerMove])
.....
} // component end
And this one works perfectly too, Link to the sandbox with this code. This one works faster.
And lastly, the above solution of passing it as a param is okay too! I prefer the one with updated if block since it's easy to modify and you get the logic behind it.

Related

How do I prevent unnecessary, repetitive side-effects from my React useEffect hook?

I am having trouble preventing unnecessary side-effects from a useEffect hook linked to a component that gets re-rendered/reused multiple times after being clicked. I want the side-effect to trigger an action once, but it is being triggered for every render.
I'm building a task viewer that displays nested task data. Please see screenshot #1 for reference.
For context, the main display shows the main tasks (see Event Data) and renders a clickable button if the task has sub-events. When this button is clicked, the selected main task is displayed at the top of the hierarchy view (see Event Hierarchy) and its sub-events are displayed below the hierarchy in a separate pane (see Event Details).
Like the main tasks, if these sub-events in 'Event Details' have their own sub-events, they are also rendered with a clickable button. When this sub-event button is clicked, this clicked sub-event is added to the bottom of the hierarchy, where the clicked main task is already displayed in bold. This selected sub-event's sub-events then replace the content in the 'Event Details' pane.
As the user clicks through the nested data, the clicked sub-event is added to the bottom of the hierarchy so that the user has an idea of where he is in the nested data and its sub-events displayed in 'Event Details'. All 'Event Hierarchy' and 'Event Details' data is cleared when the user selects a new main event or selects a new page.
The hierarchy events are held in an array managed via useState and every time another sub-event is clicked, it is added to this array. That's the idea, at least.
#1
My problem is this:
If I place my setHierarchy function inside a useEffect hook with the selectedTask as dependency, it renders the selectedTask in the hierarchy instantaneously, but the button component that triggers setHierarchy is re-rendered for every sub-event being displayed in 'Event Details' (as I want each event to be clickable) and in doing so, it adds that many copies of the event to my hierarchy array. This happens even though I am checking to see if the hierarchy array already contains the selected subevent before adding it. See result in screenshot #2.
I have tried various configurations of checking the array, but I cannot seem to stop it from adding these copies to and subsequently displaying them in the Hierarchy.
If I place the setHierarchy function inside my click handler, only one single event is added, but it executes before the selectedSubEvent has been updated. This means the hierarchy array is empty upon first render and stays one click 'behind' ie. a specific event is only displayed upon the following click event, after the click that selected it.
#2
This is all done inside my ExpandSubEvents button component (see code below) and also managed via a context provider.
I have tried moving the setHierarchy into a separate function, inside a useCallback, and triggering it from both the clickHandler and the useEffect that sets the selectedSubEvent. This did not resolve the issue.
I've also tried useRef to try and link it to the latest state. I'm not sure that's even doable/correct.
What am I doing wrong here? I am fairly new to coding, so any input on this would be much appreciated.
Sidenote: I suspect that my setup is perhaps beyond the intended scope of useContext. Is it? What can I do to make improvements? Is this perhaps in any way responsible for my issue?
Thank you for taking your time to read this far. I appreciate it!
Deon
ExpandSubEvents Component
import React, { useCallback, useContext, useEffect, useMemo } from 'react';
import SubEventContext from '../../store/sub-event-context';
import classes from './ExpandSubEvents.module.css';
const ExpandSubEvents: React.FC<{
id: number;
subEvents: number;
}> = React.memo((props) => {
// Extract context
const subEventCtx = useContext(SubEventContext);
const {
subEvents,
subEventParentId,
selectedSubEvent,
hierarchy,
setSubEventParentId,
setFetchId,
setSelectedSubEvent,
setHierarchy,
} = subEventCtx;
// Get id of event for when it is clicked
const id = React.useMemo(() => props.id, [props.id]);
let eventIds: number[] = useMemo(() => [], []);
if (hierarchy) {
for (const event of hierarchy) {
eventIds.push(event.id);
}
}
// Set CSS classes to style button if it has sub-events
let subEventQuantity = props.subEvents;
let importedClasses = `${classes['sub-event-button']}`;
if (subEventQuantity === 0) {
importedClasses = `${classes['no-sub-events']}`;
}
// Push the event to the Hierarchy display
// NOTE Tried moving the setHierarchy to a separate function, but it did not make a difference
// const triggerHierarchy = useCallback(() => {
// if (!eventIds.includes(id))
// setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
// }, [eventIds, id, selectedSubEvent, setHierarchy]);
// Respond to subevent button click event
const clickHandler = useCallback(() => {
setSubEventParentId(id);
setFetchId(id);
// This setHierarchy works, but executes before the selectedSubEVent has been updated
// Furthermore, if a new subevent is selected, it checks if the NEW clicked one has been added
// BUT sends the OLD event still in selectedSubEvent to the hierarchy before IT has been updated
// meaning that the check does not stop the same event being added twice
if (!eventIds.includes(id))
setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
}, [
eventIds,
id,
selectedSubEvent,
setFetchId,
setHierarchy,
setSubEventParentId,
]);
// NOTE Tried useRef to get setHierarchy to use the latest selectedSubEvent
// const subEventRef = useRef<Event[]>([]);
// subEventRef.current = hierarchy;
// Trying to setHierarchy directly from its own useEffect
// useEffect(() => {
// if (!eventIds.includes(id))
// setHierarchy((prevState) => [...prevState, ...selectedSubEvent]);
// }, [eventIds, hierarchy, id, selectedSubEvent, setHierarchy]);
// Filter the event from the subEvent array and set it to selectedSubEvent
useEffect(() => {
setSelectedSubEvent(
subEvents.filter((subEvent) => subEvent.id === subEventParentId)
);
}, [setSelectedSubEvent, subEventParentId, subEvents]);
return (
<button onClick={clickHandler} className={importedClasses}>
{subEventQuantity}
</button>
);
});
export default ExpandSubEvents;

useEffect not triggering when object property in dependence array

I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}

Issues accessing react state in firestore onSnapshot listener

I want to wait to apply state updates from the back-end if a certain animation is currently running. This animation could run multiple times depending on the game scenario. I'm using react-native with hooks and firestore.
My plan was to make an array that would store objects of the incoming snapshot and the function which would use that data to update the state. When the animation ended it would set that the animation was running to false and remove the first item of the array. I'd also write a useEffect, which would remove the first item from the array if the length of the array had changed.
I was going to implement this function by checking whether this animation is running or whether there's an item in the array of future updates when the latest snapshot arrives. If that condition was true I'd add the snapshot and the update function to my array, otherwise I'd apply the state update immediately. I need to access that piece of state in all 3 of my firestore listeners.
However, in onSnapshot if I try to access my state it'll give me the initial state from when the function rendered. The one exception is I can access the state if I use the function to set the state, in this case setPlayerIsBetting and access the previous state through the function passed in as a callback to setPlayerIsBetting.
I can think of a few possible solutions, but all of them feel hacky besides the first one, which I'm having trouble implementing.
Would I get the future state updates if I modify the useEffect for the snapshots to not just run when the component is mounted? I briefly tried this, but it seems to be breaking the snapshots. Would anyone know how to implement this?
access the state through calling setPlayerIsBetting in all 3 listeners and just set setPlayerIsBetting to the previous state 99% of the time when its not supposed to be updated. Would it even re-render if nothing is actually changed? Could this cause any other problems?
Throughout the component lifecycle add snapshots and the update functions to the queue instead of just when the animation is running. This might not be optimal for performance right? I wouldn't have needed to worry about it for my initial plan to make a few state updates after an animation runs since i needed to take time to wait for the animation anyway.
I could add the state I need everywhere on the back-end so it would come in with the snapshot.
Some sort of method that removes and then adds the listeners. This feels like a bad idea.
Could redux or some sort of state management tool solve this problem? It would be a lot of work to implement it for this one issue, but maybe my apps at the point where it'd be useful anyway?
Here's my relevant code:
const Game = ({ route }) => {
const [playerIsBetting, setPlayerIsBetting] = useState({
isBetting: false,
display: false,
step: Infinity,
minimumValue: -1000000,
maximumValue: -5000,
});
const [updatesAfterAnimations, setUpdatesAfterAnimations] = useState([]);
// updatesAfterAnimations is currently always empty because I can't access the updated playerIsBetting state easily
const chipsAnimationRunningOrItemsInQueue = (snapshot, updateFunction) => {
console.log(
"in chipsAnimationRunningOrItemsInQueue playerIsBetting is: ",
playerIsBetting
); // always logs the initial state since its called from the snapshots.
// So it doesn't know when runChipsAnimation is added to the state and becomes true.
// So playerIsBetting.runChipsAnimation is undefined
const addToQueue =
playerIsBetting.runChipsAnimation || updatesAfterAnimations.length;
if (addToQueue) {
setUpdatesAfterAnimations((prevState) => {
const nextState = cloneDeep(prevState);
nextState.push({ snapshot, updateFunction });
return nextState;
});
console.log("chipsAnimationRunningOrItemsInQueue returns true!");
return true;
}
console.log("chipsAnimationRunningOrItemsInQueue returns false!");
return false;
};
// listener 1
useEffect(() => {
const db = firebase.firestore();
const tableId = route.params.tableId;
const unsubscribeFromPlayerCards = db
.collection("tables")
.doc(tableId)
.collection("players")
.doc(player.uniqueId)
.collection("playerCards")
.doc(player.uniqueId)
.onSnapshot(
function (cardsSnapshot) {
if (!chipsAnimationRunningOrItemsInQueue(cardsSnapshot, updatePlayerCards)) {
updatePlayerCards(cardsSnapshot);
}
},
function (err) {
// console.log('error is: ', err);
}
);
return unsubscribeFromPlayerCards;
}, []);
};
// listener 2
useEffect(() => {
const tableId = route.params.tableId;
const db = firebase.firestore();
const unsubscribeFromPlayers = db
.collection("tables")
.doc(tableId)
.collection("players")
.onSnapshot(
function (playersSnapshot) {
console.log("in playerSnapshot playerIsBetting is: ", playerIsBetting); // also logs the initial state
console.log("in playerSnapshot playerIsBetting.runChipsAnimation is: "playerIsBetting.runChipsAnimation); // logs undefined
if (!chipsAnimationRunningOrItemsInQueue(playersSnapshot, updatePlayers)) {
updatePlayers(playersSnapshot);
}
},
(err) => {
console.log("error is: ", err);
}
);
return unsubscribeFromPlayers;
}, []);
// listener 3
useEffect(() => {
const db = firebase.firestore();
const tableId = route.params.tableId;
// console.log('tableId is: ', tableId);
const unsubscribeFromTable = db
.collection("tables")
.doc(tableId)
.onSnapshot(
(tableSnapshot) => {
if (!chipsAnimationRunningOrItemsInQueue(tableSnapshot, updateTable)) {
updateTable(tableSnapshot);
}
},
(err) => {
throw new err();
}
);
return unsubscribeFromTable;
}, []);
I ended up not going with any of the solutions I proposed.
I realized that I could access the up to date state by using a ref. How to do it is explained here: (https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559) And this is the relevant code sample from that post: (https://codesandbox.io/s/event-handler-use-ref-4hvxt?from-embed)
Solution #1 could've worked, but it would be difficult because I'd have to work around the cleanup function running when the animation state changes. (Why is the cleanup function from `useEffect` called on every render?)
I could work around this by having the cleanup function not call the function to unsubscribe from the listener and store the unsubscribe functions in state and put them all in a useEffect after the component mounts with a 2nd parameter that confirmed all 3 unsubscribe functions had been added to state.
But if a user went offline before those functions were in state I think there could be memory leaks.
I would go with solution #1: In the UseEffect() hooks you could put a boolean flag in so the snapshot listener is only set once per hook. Then put the animation state property in the useEffect dependency array so that each useEffect hook is triggered when the animation state changes and you can then run whatever logic you want from that condition.

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