When object stored in useContext updates DOM does not rerender - javascript

I am writing an application that basically is a game. The information about the game is stored in a context provider that wraps the entire application. I have written prototype methods for the game object and call them in the react app but the changes do not appear on the page.
const onDeckEl = useRef(null)
useEffect(() => {
let onDeck = round.getOnDeck()
onDeckEl.current = onDeck
console.log(onDeckEl)
}, [round])
I can see in the dev tools that the values of the arrays in the round object are changing when I want but the console.log is never fired. Can someone explain why?

The most likely reason the console.log is not firing is because the round variable isn't changing. The code:
useEffect(() => {
console.log('Effect!');
}, [round])
wont fire unless the round variable changes.

Related

Prompt Repeats Twice When Page Renders (Undesirable) React.js JavaScript

When I have a basic prompt setup such as
var tag = prompt("Enter Tag")
or
useEffect(() => {
tagP = window.prompt('Enter tag')
console.log(tagP)
});
The prompt repeats itself twice when the page loads. I only want this to happen once. I tried initializing the prompt with a loop without useEffect and I get the same results. When I try to initialize the prompt with a loop inside useEffect the prompt only shows up once as desired but the value for tagP is rendered as the initial value and not the new prompt value. I also get a warning suggesting that I use useRef. The warning message is below. I just would like to know why the prompt is repeating. If possible, I would like to just have a simple prompt statement without a loop or useEffect. I've tried with multiple environments and the results are constant. I appreciate all ideas! Thank you for your time!
"Assignments to the 'tagP' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.es"
var tagP = "Example"
useEffect(() => {
if (tagP === "Example") {
tagP = window.prompt('Enter tag')
console.log(tagP) }
});
You should give an empty array as second parameter to useEffect,
useEffect(() => {
tagP = window.prompt('Enter tag')
console.log(tagP)
}, []);
Prompt appearing twice can also be to react strict mode
React StrictMode renders components twice (on dev but not production) in order to detect any problems with your code and warn you about them.
You can remove the StrictMode and verify once.

useEffect for real time data update

I am creating an easy chat app, with different text channels. I am facing an infinite loop issue when using the useEffect hook to update the messagesList on real time. You can see the code below but this is what I am trying to achieve:
First useEffect is for the chat window to scroll to the last message every time there is a change in the messagesList array. This means: I am in the middle of the messages window, I write a new message and it takes me to the bottom. This is working fine.
Second useEffect is for the messagesList to be rendered whenever the channel is changed or there is any change in the messagesList. Adding the messagesList as a dependency is causing the infinite loop... but I think I need it cause otherwise the following happens: user1 is inside the chat channel and user2 writes a new message. User1 wont see the new message displayed as his chat is not being re-rendered. How would you make it for the new message to be displayed for user1?
Sorry for the confusing question and thanks a lot in advance!
useEffect(() => {
anchor.current.scrollIntoView(false);
}, [messagesList]);
useEffect(() => {
const unsubscribe = onSnapshot(
collection(firestore, `channels/${activChannel}/messages`),
(snapshot) => {
console.log(snapshot.docs);
getMessagesList();
}
);
return () => unsubscribe();
}, [activChannel, messagesList]);
I am not familiar with firestore, but perhaps you could tie the updating of the messages to the event that an user submits his message or use useSyncExternalStore. This piece of documentation on useEffect use cases might help you.
an excerpt from the docs:
Here, the component subscribes to an external data store (in this
case, the browser navigator.onLine API). Since this API does not exist
on the server (so it can’t be used to generate the initial HTML),
initially the state is set to true. Whenever the value of that data
store changes in the browser, the component updates its state.
Although it’s common to use Effects for this, React has a
purpose-built Hook for subscribing to an external store that is
preferred instead. Delete the Effect and replace it with a call to
useSyncExternalStore:

React component is picking the old state when used with hooks

I'm still getting a hang of hooks in React. I am making a tic tac toe game to understand the concept of the useState hook. Everything works fine but I need to make an extra move to find the winner. This is because I'm calling a function on every user move, and that function is unable to get the updated state, it always has one step older state of the game.
Here is the link to the CodeSandbox. Here is how the flow works:
First Render: Game function calls -> Board function -> Board uses a custom hook, useBoardState -> useBoardState initializes an empty array of size 9 in the state, and returns array and a function updateSquares to update the state(+ there is some other stuff to render the UI but unrelated to the issue).
When the user clicks on an empty box: selectSquare function gets called -> selectSquare calls updateSquares to update the state(+ some other stuff to make the game usable.)
Observe that the console in the selectSquare function has the stale state even when the updateSquares was executed. I was expecting that the state would be updated function was called.
As requested in the comments, I'm adding the relevant part of the code here:
function useBoardState() {
const [squares, setSquares] = useState(() => Array(9).fill(null));
function updateSquares(index, mark) {
setSquares((prevSquares) => {
const newSquares = [...prevSquares];
newSquares[index] = mark;
return newSquares;
});
}
return [squares, updateSquares];
}
I think it has something to do with this custom hook, I think the updateSquares state dispatcher updates the state when the render is complete.
not sure what am I missing here.
the setter returned from useState as well as the class based component equivelant setState are not called synchronously. That is why if you do something like
const updateSomething = () => {
setFoo(1)
setBar(2)
setFooBar(3)
console.log('hey')
}
React will log hey before it starts setting the state, furthermore it will actually batch setFoo, setBar and setFooBar together, so it does a single rerender with all the above state updates.

Call function only after multiple states have completed updating

Logic:
I have a dialog for converting units. It has two stages of choice for the user: units to convert from and units to convert to. I keep this stage as a state, dialogStage, for maintainability as I'm likely going to need to reference what stage the dialog is in for more features in the future. Right now it's being used to determine what action to take based on what unit is clicked.
I also have a state, dialogUnits, that causes the component to rerender when it's updated. It's an array of JSX elements and it's updated via either foundUnitsArray or convertToUnitsArray, depending on what stage the dialog is at. Currently both states, dialogStage and dialogUnits, are updated at the same moment the problem occurs.
Problem:
When choosing the convertTo units, displayConversionTo() was still being called, as though dialogStage was still set to 'initial' rather than 'concertTo'. Some debugging led to confusion as to why the if (dialogStage == 'initial') was true when I'd set the state to 'convertTo'.
I believe that my problem was that the dialogStage state wasn't updated in time when handleUnitClick() was called as it's asynchronous. So I set up a new useEffect that's only called when dialogStage is updated.
The problem now is that the dialog shows no 'convertTo' units after the initial selection. I believe it's now because dialogUnits hasn't updated in time? I've swapped my original problem from one state not being ready to another state not being ready.
Question
How do I wait until both states are updated before continuing to call a function here (e.g. handleUnitClick()?).
Or have I mistaken what the problem is?
I'm new to react and, so far, I'm only familiar with the practice of state updates automatically rerendering a component when ready, unless overridden. Updating dialogUnits was displaying new units in the dialog until I tried to update it only when dialogStage was ready. It feels like an either/or situation right now (in terms of waiting for states to be updated) and it's quite possible I've overlooked something more obvious, as it doesn't seem to fit to be listening for state updates when so much of ReactJs is built around that already being catered for with rerenders, etc.
Component code:
function DialogConvert(props) {
const units = props.pageUnits;
const [dialogUnits, setDialogUnits] = useState([]);
const [dialogStage, setDialogStage] = useState('initial');
let foundUnitsArray = [];
let convertToUnitsArray = [];
units.unitsFound.forEach(element => {
foundUnitsArray.push(<DialogGroupChoice homogName={element} pcbOnClick={handleUnitClick} />);
});
useEffect(() => {
setDialogUnits(foundUnitsArray);
}, []);
useEffect(() => {
if (dialogStage == "convertTo") {
setDialogUnits(convertToUnitsArray);
}
}, [dialogStage]);
function handleClickClose(event) {
setDialogStage('initial');
props.callbackFunction("none");
}
function handleUnitClick(homogName) {
if (dialogStage == "initial") {
// getConversionChoices is an external function that returns an array. This returns fine and as expected
const choices = getConversionChoices(homogName);
displayConversionTo(choices);
} else if (dialogStage == "convertTo") {
// Can't get this far
// Will call a function not displayed here once it works
}
}
function displayConversionTo(choices) {
let canConvertTo = choices[0]["canconvertto"];
if (canConvertTo.length > 0) {
canConvertTo.forEach(element => {
convertToUnitsArray.push(<DialogGroupChoice homogName={element} pcbOnClick={handleUnitClick} />);
});
setDialogStage('convertTo');
}
}
return (
<React.Fragment>
<div className="dialog dialog__convertunits" style={divStyle}>
<h2 className="dialogheader">Convert Which unit?</h2>
<div className='js-dialogspace-convertunits'>
<ul className="list list__convertunits">
{dialogUnits}
</ul>
</div>
<button className='button button__under js-close-dialog' onClick={handleClickClose}>Close</button>
</div>
</React.Fragment>
)
}
So, there are some issues with your implementations:
Using non-state variables to update the state in your useEffect:
Explanation:
In displayConversionTo when you run the loop to push elements in convertToUnitsArray, and then set the state dialogStage to convertTo, you should be facing the issue that the updated values are not being rendered, as the change in state triggers a re-render and the convertToUnitsArray is reset to an empty array because of the line:
let convertToUnitsArray = [];
thus when your useEffect runs that is supposed to update the
dialogUnits to convertToUnitsArray, it should actually set the dialogueUnits to an empty array, thus in any case the updated units should not be visible on click of the initial units list.
useEffect(() => {
if (dialogStage == "convertTo") {
// as your convertToUnitsArray is an empty array
// your dialogue units should be set to an empty array.
setDialogUnits(convertToUnitsArray)
}
}, [dalogStage]);
You are trying to store an array of react components in the state which is not advisable:
http://web.archive.org/web/20150419023006/http://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html#what-components-should-have-state
Also, refer https://stackoverflow.com/a/53976730/10844020
Solution: What you can do is try to save your data in a state, and then render the components using that state,
I have created a code sandbox example how this should look for your application.
I have also made some changes for this example to work correctly.
In your code , since you are passing units as props from parent, can you also pass the foundUnitsArray calculated from parent itself.
setDialogUnits(props.foundUnitsArray);
and remove the below operation,
units.unitsFound.forEach(element => {
foundUnitsArray.push(<DialogGroupChoice homogName={element} pcbOnClick={handleUnitClick} />);
});

React hooks how to only execute side effect function only once if I need to update 2 dependency variables

Lets say I have the following component using react hooks
const Component = (props) => {
[state1, setState1] = useState();
[state2, setState2] = useState();
useEffect(()=>{
// use state1 and state2 as param to fetch data
...
}, [state1, state2])
onSomethingChange = () => {
setState1(...);
setState2(...);
}
return (..);
}
When onSomethingChange triggers, I thought it would call the side effect function twice since I update 2 different state vars in the same dependency array. And I was going to refactor those 2 vars into 1 object but I thought I would test them as 2 separate vars first.
What I observed is the side effect function only gets executed once even when I update 2 different state vars in the same dependency array, this is what I wanted but I don't know why. Could someone please explain how it works under the bonnet?
That's because React sometimes may batch multiple state changes into one update for performance. React will batch state updates if they're triggered from within a React-based event, that's the reason why your side effect block is called only once. For more information, you may refer this thread: https://github.com/facebook/react/issues/14259
React batches state updates under the hood.
That simply means that calling
setState1(...);
setState2(...);
in the same synchronous (!) execution cycle (e.g. in the same function) will NOT trigger two component re-render cycles.
Instead, the component will only re-render once and both state updates will be applied simultaneously.
Not directly related, but also sometimes misunderstood, is when the new state value is available.
Consider this code:
console.log(name); // prints name state, e.g. 'Doe'
setName('John');
console.log(name); // ??? what gets printed? 'John'?
You could think that accessing the name state after setName('John');
should yield the new value (e.g. 'John') but this is NOT the case.
The new state value is only available in the next component render cycle (which gets scheduled by calling setName()).

Categories