I am trying to update a variable when the scores state changes. At the moment I have a function inside a useEffect hook which calculate the sum of the scores array and updates the global totalScore variable. For some reason the totalScore variable doesn't seem to be updating and displaying correctly on the screen - it just stays at 0.
let totalScore = 0
const [scores, setScores] = useState([])
useEffect(() => {
scores.forEach((score) => {
totalScore += score
}
}, [scores])
return (
<>
<p>{totalScore}</p>
</>
)
Issue
For some reason the totalScore variable doesn't seem to be updating
and displaying correctly on the screen - it just stays at 0.
This is because the useEffect hook runs after the component has rendered to the DOM. The totalScore variable was updated, but React needs another rerender to get it to the DOM.
Suggested Solution
Since it's derived "state" from the scores state, it would be better to just compute the totalScore from the scores state in the component render.
const [scores, setScores] = useState([]);
const totalScore = scores.reduce((total, score) => score + total, 0);
return (
<>
<p>{totalScore}</p>
</>
);
You can memoize totalScore with the useMemo hook if necessary.
const totalScore = useMemo(() => {
return scores.reduce((total, score) => score + total, 0);
}, [scores]);
You need to have totalScore in your state for it to get updated.
const [totalScore, setTotalScore] = useState(0)
const [scores, setScores] = useState([])
useEffect(() => {
let ts = 0;
scores.forEach((score) => {
ts += score
}
setTotalScore(ts);
}, [scores])
Related
I have a dynamic Countdown Timer In React:
This Timer is a component (MovementTimer.js) & the main component (GameRoom.js) is calling Timer's render method, passing thru props.
This is the render Timer function that's being called below upon every GameRoom render
renderTimer = () => {
console.log('*** Timer RERENDER ***');
console.log(this.state.turn_time);
console.log(this.state.last_time);
if(this.state.turn_time != null && this.state.last_time != null){
const props = {
time : this.state.turn_time,
last_time : this.state.last_time
}
return(
<MovementTimer {...props}/>
)
} else {
return(
<p>Undefined Timer.</p>
)
}
}
This is the render method of GameRoom.js
render() {
if(this.state.guest){
console.log('loaded in ! ' + this.state.guest);
// Put into return statement for DEBUG {this.state.turn ? "TRUE" : "FALSE"}
return (
<div className="main">
{this.renderBoard()}
{this.renderTimer()}
{}
<p className="welcome">
{this.state.userResponseData}
{
this.state.guest[0] ? (this.state.turn ? "It's your opponent's turn." : "It's your turn, move!") : (this.state.turn ? "It's your turn, move!" : "It's your opponent's turn.")
}</p>
</div>
)
}
return (
<div className="main">
{this.renderBoard()}
<p className="welcome"> {this.state.turn ? "TRUE" : "FALSE"} Loading {this.state.userResponseData}</p>
</div>
)
}
}
Basically the issue is, whenever GameRoom.js rerender's, the renderTimer() function passes in updated props to the MovementTimer, however the timer doesn't reset. I was trying to use a useRef state boolean but it was buggy and wasn't working.
MovementTimer.js component
import React, {useRef, clearState,useState,useCallback} from 'react'
const MovementTimer = (props) => {
console.log('Re initizalized')
const time = parseInt(props.time);
const last_time = parseInt(props.last_time);
//clearState()
console.log(
"Estimated Time:\n"+
((last_time+time*1000) - Date.now() )/1000
);
let [counter, setCounter] = React.useState(Math.round( ((last_time+time*1000) - Date.now() )/1000 ) );
// const reinit = () => {
// setCounter(time)
// }
console.log(counter);
console.log(last_time);
React.useEffect(() => {
counter > 0 && setTimeout(() => setCounter(
counter - 1
), 1000); //Set this too Due time - last time / 1000 (for secs)
if(counter === 0){
const socketProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const socket = new WebSocket(socketProtocol+(window.location.href).split(':')[1]+':5000');
socket.onopen = async () => {
await socket.send(JSON.stringify({
query_type : 't0',
room_id: (window.location.pathname).slice(6)
}));
socket.close();
}
setCounter(Math.round( ((last_time+time*1000) - Date.now() )/1000 ))
}
}, [counter]);
return (
<div>
{counter}
</div>
)
}
export default MovementTimer
In this component, I useEffect for the timer countdown, and pass in the time difference in seconds (using props that have been passed down) Is there anyway to check if props change, and reset the useEffect?
In that if statement (counter === 0), I manually set it back using setCounter. however i dont know how to check for prop change. whenever last_time prop is updated.
The second parameter in your useEffect is what the function listens to for changes. If you just want to rerun the function in useEffect when a specific variable changes, add it to the array. For example, if you want to rerun it when variable x changes, you would write it like this:
React.useEffect(() => { /* do stuff */ }, [counter, x]);
Documentation: Conditionally firing an effect
Is there a way where I can use for loops and if statements without breaking the hook rule? To elaborate, I am currently trying to compare two lists (allData and currentSelection) and if there are similarities, I will add them to another list (favData). However, I am constantly either having visibility issues or errors. If I can get some help, I would much appreciate it!
const [favData, setFavData] = useState([]);
useEffect(() => {
getFilterFavMeal();
}, []);
function getFilterFavMeal() {
allData.forEach((mealList) => {
currentSelection.forEach((mealList2) => {
if (mealList["menu_item"]["menu_item_id"] === mealList2.value) {
// with push, I have visibility issues
// favData.push(mealList);
setFavData(mealList);
}
});
});
setFavData(favData);
}
The set function that useState returns updates the state and schedules a re-render of the component so that the UI can update. It doesn't make sense to call the set function many times in one render.
You also don't want to mutate React state by using functions like push.
Since it looks like favData is deterministic, you can simply remove it from the component state and calculate it in the render loop.
const favData = allData.filter(a => currentSelection.some(c => c.value === a.menu_item.menu_item_id));
Answering your original question, of course you can use loops. As long as you don't mutate the existing state object. And don't set the state more than once per render.
const FF = () => {
const [list, setList] = useState([]);
const addStuffToList = () => {
const tail = Array.from(new Array(3)).map((_e, i) => i);
// Build a new array object and use that when setting state
setList([...list, ...tail]);
}
const forLoop = () => {
const tail = [];
for (let i = 0; i < 4; i++) {
tail.push(i);
}
// Same thing
setList([...list, ...tail]);
}
return ...
};
I have a working animation of an object made with the "useRef" hook. Part of the code in this animation will be repeated several times, so I moved it into a separate function, but when I try to call this function, when rendering the component, I get the error "Can't assign to property" scrollLeft "on 1: not an object" what could be the problem?
Full code on codesandbox
https://codesandbox.io/s/peaceful-silence-bm6hx?file=/src/scroll.js
import React, {useState, useEffect, useRef} from 'react'
const Scrollable = props => {
const items = props.items;
let ref = useRef()
const [state, setState] = useState({
isScrolling:false,
clientX:0,
scrollX:0
})
const [touchStart, setTouchStart] = useState(0);
let frameId;
const onMouseDown = e =>{...}
const onMouseUp = e =>{
if(ref && ref.current && !ref.current.contains(e.target)) {
return;
}
e.preventDefault()
let touchShift = touchStart - state.clientX
let rez;
let shift;
if(touchShift > 0) {
shift = 300 - touchShift
rez = state.scrollX + shift
if(rez>2100){
rez =1800
cancelAnimationFrame(frameId)
}
let speed = shift / 20
let cur = state.scrollX
frameId = requestAnimationFrame(animate)
animate(cur,speed,rez)
}
}
const animate = (cur, speed,rez) => {
frameId = requestAnimationFrame(animate)
cur = cur + speed
ref.current.scrollLeft = cur.toFixed(2)
if (Math.round(cur) === rez) {
cancelAnimationFrame(frameId)
setState({
...state,
scrollX:rez,
isScrolling:false,
})
}
}
useEffect(() =>{
document.addEventListener('mousedown',onMouseDown)
document.addEventListener('mouseup',onMouseUp)
return () => {
document.removeEventListener('mousedown',onMouseDown)
document.removeEventListener('mouseup',onMouseUp)
}
})
useEffect(() =>{
ref.current = requestAnimationFrame(animate)
return () => {
cancelAnimationFrame(ref.current)
},[]})
return (
<div className={classes.charPage}>
<div
ref={ref}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}>
</div>
</div>
)
}
export default Scrollable;
This error means you're trying to set a property on a number. In your useEffect you're doing this:
ref.current = requestAnimationFrame(animate)
requestAnimationFrame returns, according to MDN:
A long integer value, the request id, that uniquely identifies the entry in the callback list. This is a non-zero value, but you may not make any other assumptions about its value.
But you're also using the same ref for your DOM element. After your useEffect runs it will have set your ref to the rAF id which is a number causing your error when you try to set the scrollLeft property on the ref.
What you can try next to solve this is to use 2 separate refs, one for the requestAnimationFrame and one for your DOM element.
What would be the implementation for the code in Vanilla JS that allows us to declare and update state like the way useState does so in React:
const [x, setX] = useState(12);
setX(14);
console.log(x); // 14
This question is strictly get better at JS. Naively it would make sense to go with:
// Solution 1
function update(value, newValue) {
value = newValue;
return value;
}
function state(value) {
return [ value, update ];
}
let [value, setValue] = state(12)
value = setValue(value, 14)
console.log(value); // 14
// Solution 2
class State {
constructor(value) {
this.value = value;
}
update(newValue) {
this.value = newValue;
}
}
const x = new State(12);
x.update(14);
console.log(x.value); // 14
But I don't understand how the array [x, setX] has a callback (setX) that can affect x when declared with a const? I hope that makes sense.
I wanted to learn how to accomplish this as well. I found how to do it here. I refactored the code to use arrow functions instead, which can make the code snippet harder to read & understand. If it's the case, head to the resources shared in the link above.
This is the implementation:
const useState = (defaultValue) => {
// 👆 We create a function useState with a default value
let value = defaultValue;
// 👆 We create a local variable value = defaultValue
const getValue = () => value
// 👇 We create a function to set the value with parameter newValue
const setValue = newValue => value = newValue // 👈 We change the value for newValue
return [getValue, setValue]; // 👈 We return an array with the value and the function
}
const [counter, setCounter] = useState(0);
// 👆 We destructure the array as a return of the useState function into two value
console.log(counter()); // 👈 returns 0 which it's the value of counter()
I added the comments for easier understanding. This is the implementation withouth the comments:
const useState = (defaultValue) => {
let value = defaultValue;
const getValue = () => value
const setValue = newValue => value = newValue
return [getValue, setValue];
}
const [counter, setCounter] = useState(0);
console.log(counter());
For better reading & understanding, I have included the snippet using regular functions:
function useState(defaultValue) {
let value = defaultValue
function getValue() {
return value
}
function setValue(newValue) {
value = newValue
}
return [getValue, setValue];
}
const [counter, setCounter] = useState(0);
There is something very important you are missing - all react hooks use something "backing" them which allows you to provide what are effectively instance variables when you don't have an instance, you only have a function.
This thing in React is called a fiber and it effectively represents the lifecycle of a React component - it's not tied to the function itself, per se, it's tied to the component react is rendering (and re-rendering). Which is why you can have one functional component declaration, render that same function multiple times, and each of those will be able to maintain their own state - the state isn't part of the function, the state is part of the React fiber.
But I don't understand how the array [x, setX] has a callback (setX)
that can affect x when declared with a const?
You aren't simply mutating the value of x when you call setX, what you are doing is telling React to re-render the component (fiber) with a new value for x.
EDIT:
A tremendously simplistic example, where the function itself is used as the backing instance of state (which is not the case in React) could look something like this:
// this line is example only so we can access the stateSetter external to the function
let stateSetter;
const states = new Map();
const useState = (value,context) => {
const dispatch = v => {
const currentState = states.get(context.callee);
currentState[0] = typeof v === 'function' ? v(currentState[0]) : v
// we re-call the function with the same arguments it was originally called with - "re-rendering it" of sorts...
context.callee.call(context);
}
const current = states.get(context.callee) || [value,dispatch];
states.set(context.callee,current);
return current;
}
const MyFunction = function(value) {
const [state,setState] = useState(value, arguments)
stateSetter = setState;
console.log('current value of state is: ',state)
}
MyFunction(10);
MyFunction(20); // state hasn't changed
stateSetter('new state'); // state has been updated!
A simple solution to mock the useState() using a constructor. This may not be the best solution as the constructor returns a copy of the function every time but achieves the problem in question.
function Hook(){
return function (initialState){
this.state = initialState;
return [
this.state,
function(newState){
this.state = newState;
}
];
}
}
const useState = new Hook();
Now, destructure the useState() which is an instace of Hook()
const [state, setState] = useState(0);
console.log(state); // 0
setState({x:20});
console.log(state); // { x: 20 }
setState({x:30});
console.log(state); // { x: 30 }
1.- Destructuring for values returned by a function.
We apply it to destructure two values of an array
returned in a function.
The first value will return the current data of a variable and the second one will have the change function for said value.
// Main function useState (similar to react Hook)
function useState(value){
// Using first func to simulate initial value
const getValue = () => {
return value;
};
// The second function is to return the new value
const updateValue = (newValue) => {
// console.log(`Value 1 is now: ${newValue}`);
return value = newValue;
};
// Returning results in array
return [getValue, updateValue];
}
// Without destructuring
const initialValue = useState(3);
const firstValue = initialValue[0];
const secondValue = initialValue[1];
// Set new data
console.log("Initial State", firstValue()); // 3
console.log("Final", secondValue(firstValue() + 5)); // 8
console.log("===========================");
// With destructuring
const [counter, setCounter] = useState(0);
console.log("Initial State", counter()); // 0
setCounter(counter() + 20);
console.log("Final", counter());
The new React Hooks feature is cool but it sometimes makes me confused. In particular, I have this code wrapped in useEffect hook:
const compA = ({ num }) => {
const [isPositive, check] = useState(false);
useEffect(() => {
if (num > 0) check(true);
}, []);
return (//...JSX);
};
The code inside the above useEffect will be executed only once. So what are the differences if I bring the code out of the useEffect, like below:
const compA = ({ num }) => {
const [isPositive, check] = useState(false);
if (num > 0) check(true);
return (//...JSX);
};
in the second case the code will be executed at every re-render.
this is a better version of the component:
const compA = ({ num }) => {
const [isPositive, check] = useState(false);
useEffect(() => {
if (num > 0) check(true);
}, [num]);
return (//...JSX);
};
In this case the effect (which depends heavily on num) is used only when the num prop has changed.
for reference:
https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
Anyway, in my opinion using a side effect in this very simple case is overkill!
The code will run faster by checking if num > 0 at every render than checking first if num changed and then if it's > 0..
So you should probably just avoid useEffect and stick to your second piece of code