Why useState causes re-execution of component with default value also - javascript

I am new to React native development. I want to understand how useState works.
import React from 'react'
import { View, Text } from 'react-native'
import Styles from './AdsStyle'
import { useContextSelector } from 'use-context-selector'
import { StateContext } from './State'
import { useState } from 'react'
import { useRef } from 'react'
import moment from 'moment'
import { useEffect } from 'react'
//To-do: Ads cx countdown timer.
const CountDownTimer = () => {
const myState = useContextSelector(StateContext, (state) => state.myState)
const timerRef = useRef(null)
const [timer, setTime] = useState(myState == null ? 0: myState.endTime)
if(myState === null){
clearInterval(timerRef.current)
return null
}
useEffect(()=>{
updateTimer()
return () => {
clearTimeout(timerRef.current)
}
})
let remainingTime
const updateTimer = ()=>{
timerRef.current = setInterval(()=>{
remainingTime = timer - 1000
if(remainingTime <0){
clearInterval(timerRef.current)
remainingTime = 0
}
setTime(remainingTime)
},1000)
}
const renderCountDownTimer = () => {
return (
<View>
<Text>{moment(timer).format('mm:ss')} </Text>
</View>
)}
return renderCountDownTimer()
}
export default React.memo(CountDownTimer, () => true)
I am simply building the count down timer. There are two issues i am facing for now.
When control comes to const [timer, setTime] = useState(myState == null ? 0: myState.endTime) it simply goes to first line to re-execute the component again. Every thing works fine if i have hardcoded value in useState.
if i move the code const [timer, setTime] = useState(myState == null ? 0: myState.endTime) after the
if(myState === null){
clearInterval(timerRef.current)
return null
}
then react fails with an error that Rendered more hooks than during the previous render
Can someone pls explain both these cases.
Thanks in advance

Related

How to fix: Assignments to the 'theme' variable from inside React Hook useEffect will be lost after each render

I am a newbie in React, and I am working on a dark-mode function. It has a warning and I would like to ask what is the best way to fix it. Thanks
20:21 warning Assignments to the 'theme' 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
import React from "react";
import { RiContrastLine } from 'react-icons/ri';
import { useEffect } from "react";
const DarkMode = () => {
let body = "";
if (typeof document !== "undefined") {
body = document.body;
}
let clickedClass = "clicked";
const lightTheme = "light-mode";
const darkTheme = "dark-mode";
let theme;
useEffect(() => {
if (localStorage) {
theme = localStorage.getItem("theme");
if (theme === darkTheme) {
}
}
if (theme === lightTheme || theme === darkTheme) {
body.classList.add(theme);
} else {
body.classList.add(lightTheme);
}
}, [])
const switchTheme = (e) => {
if (theme === darkTheme) {
body.classList.replace(darkTheme, lightTheme);
localStorage.setItem("theme", "light-mode");
e.target.classList.remove(clickedClass);
theme = lightTheme;
} else {
body.classList.replace(lightTheme, darkTheme);
localStorage.setItem("theme", "dark-mode");
e.target.classList.add(clickedClass);
theme = darkTheme;
}
};
return (
<button className='general-menu-button' type="button" onClick={(e) => switchTheme(e)}>
<RiContrastLine />
</button>
);
};
export default DarkMode;
I have tried to add useRef, but with my limited understanding, it turned out another error.
I would like to know how to resolve the warning
Why not store the theme in state?
This will persist the value across rerenders
Import useState at the top of your file:
import { useState } from 'react'
replace
let theme;
with
const [theme, setTheme] = useState(/* initial theme here */)
then replace anytime you assign theme to a setTheme call
e.g.
theme = localStorage.getItem('theme')
// becomes
setTheme(localStorage.getItem('theme'))

React Native state not resetting properly on navigating between screens

I am trying to implement the following case with two screens:
Adding players screen
Game screen: Challenges are read from JSON file and filled in with random variables during runtime. Example: in "p(1) drinks sips(2,4)" p(1) will be replaced with a random choice of a list of players, and sips will be replaced by either 2, 3 or 4 sips.
I am using stack navigation, passing the result of adding players in the player screen to the challenge screen. It works perfectly fine when I first start the game, but when I pop screen 2, and go back to screen 2, the challenges appear in random order (as expected), but they are already filled in.
The initialstate reads the non-filled in challenges from a JSON file, so why are the challenges already filled in when I rerender screen 2.
Gamescreen:
import React, { useState } from 'react';
import { StyleSheet, View, Text, TouchableWithoutFeedback, Alert } from 'react-native';
import ChallengeComponent from '../components/gameScreenComponents/challengeComponent'
import { shuffle } from '../helpers/shuffle'
import { fillInSips, fillInChars, fillInNumbers, fillInPlayers, fillInChoice} from '../helpers/challengeRegex'
const GameScreen = ({ route, navigation }) => {
const playersNeeded = (challenge) => {
return 0
}
const popChallengeHandler = () => {
if (challenges.length > 1) {
let newChallenges = [...challenges];
newChallenges.shift()
processedChallenges = playChallenge(newChallenges)
setChallenges(processedChallenges)
}
else {
setChallenges([])
}
}
const playChallenge = (currentChallenges) => {
currentChallenges[0] = fillInChallenge(currentChallenges[0]);
currentChallenges[0]['nextRounds'].forEach(round => currentChallenges.splice(round[0],0,{
initialRound: round[1],
nextRounds: []
}))
return currentChallenges
}
const fillInChallenge = (challenge) => {
return fillInChoice(players)
}
const endGameHandler = () => {
Alert.alert("Ending game")
}
const players = route.params;
const [challenges, setChallenges] = useState(() => {
const initialState = playChallenge(shuffle(require('../data/challenges.json').deck.filter(challenge => players.length >= playersNeeded(challenge))));
return initialState;
});
if (challenges.length > 0)
return (<ChallengeComponent pressHandler={popChallengeHandler} text={challenges[0]['initialRound']} navigation={navigation}/>)
else
return (<ChallengeComponent pressHandler={endGameHandler} text="Het spel is afgelopen" navigation={navigation}/>)
}
export default GameScreen;
Navigator:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import AddPlayerScreen from './screens/addPlayers';
import GameScreen from './screens/game';
console.disableYellowBox = true;
const RootStack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<RootStack.Navigator screenOptions={{headerShown: true}}>
<RootStack.Screen name="AddPlayers" component={AddPlayerScreen} />
<RootStack.Screen name="Game" component={GameScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
};
export default App;
You can use this event listeners to doing some updates or else
navigation.addListener('blur', () => {console.log('focused Out')});
navigation.addListener('focus', () => {console.log('focused')});

Bug with setInterval in React

What is wrong with this case. I want to display a random name and change it for every 2 seconds but after few seconds is changing continuously and look like the names are overwriting even when I clean the setName?
import React, {useState} from "react";
import "./styles.css";
export default function App() {
const [name, setName] = useState();
const arrayName = ['Tom','Alice','Matt','Chris'];
const nameChange = () => {
const rand = Math.floor(Math.random()*arrayName.length);
setName(arrayName[rand])
}
setInterval(()=>{
setName('');
nameChange();
console.log(name);
}, 2000)
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}
It's creating a new interval every time your component renders, which causes it to render again and you end up with an infinite loop.
Try this:
import React, {useState, useEffect, useCallback} from "react";
import "./styles.css";
const arrayName = ['Tom','Alice','Matt','Chris'];
export default function App() {
const [name, setName] = useState();
const nameChange = useCallback(() => {
const rand = Math.floor(Math.random()*arrayName.length);
setName(arrayName[rand])
}, []);
useEffect(() => {
const interval = setInterval(() => {
setName('');
nameChange();
}, 2000)
return () => clearInterval(interval)
}, [nameChange]);
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}
The issue is that you never do clearInterval. Whenever the component calls render, a new interval will issue.
Wrap setInterval in useEffect, which gets called when a component renders. The return of useEffectis a function that dictates what happens on component unmounting phase. See more here
useEffect(){
const tmp = setInterval(()=>{
setName('');
nameChange();
console.log(name);
}, 2000)
return () => { clearInterval(tmp); };
}
The issue is that every time your component is rendered, you are creating a new interval.
The solution is to wrap the setInterval call in useEffect, and then return a function to useEffect to clear the interval.
import React, { useState, useCallback, useEffect } from 'react';
import './styles.css';
const arrayName = ['Tom', 'Alice', 'Matt', 'Chris'];
export default function App() {
const [name, setName] = useState();
const nameChange = useCallback(() => {
const rand = Math.floor(Math.random() * arrayName.length);
setName(arrayName[rand]);
}, [setName]);
useEffect(() => {
const intervalId = setInterval(() => {
setName('');
nameChange();
}, 2000);
return () => clearInterval(intervalId);
}, [nameChange]);
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}

Create ref to external class component inside functional component in React

I would like to use react player library in my app
import React, { useEffect, useRef } from "react";
import ReactPlayer from "react-player";
import { useSelector } from "../../../redux/useSelector";
const VIDEO_URL =
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
export const Player: React.FC = React.memo(() => {
const player = React.useRef<any>(null);
const targetTimestamp = useSelector((state) => {
const {
timestamps: { selectedTimestamp },
} = state;
return (selectedTimestamp && selectedTimestamp.timestamp) || 0;
});
useEffect(() => {
console.log(player.current);
player.current && player.current.seekTo(targetTimestamp );
}, [targetTimestamp]);
return (
<ReactPlayer
ref={player}
url={VIDEO_URL}
controls
width={1280}
height={720}
/>
);
});
console.log(player.current); works, but on the next line I get the error
Uncaught TypeError: Cannot read property 'seekTo' of undefined
What's wrong? I can't use useRef here? Should I make Player class component? How to fix it and make it work?
////
let player;
const ref = (playerRef) => {
player = playerRef;
}
////
const seekHandler = (event) => {
....
player.seekTo(parseFloat(event.target.value), "fraction");
...
};
...
<ReactPlayer
ref={ref}
.....

Timer React + Redux. React don't dispatch action by timer (SetInterval) in ComponentDidMount

I'm trying to make a timer in my App with React + Redux.
So I have a component parent:
import React, { Component } from "react";
import { connect } from "react-redux";
import { compose } from "redux";
import QuestionCounter from "../question-counter";
import FinishButton from "../finish-button";
import TimeCounter from "../time-counter";
import PauseButton from "../pause-button";
import testFinished from "../../actions/test-finished";
import timerTick from "../../actions/timer-tick";
import setTimer from "../../actions/set-timer";
import totalWithEwStruct from "../hoc/total-with-ew-structure";
import withIndicators from "../hoc/with-indicators";
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
const mapStateToProps = ({ total, loading, error }) => {
return { total, loading, error };
};
const mapDispatchToProps = {
testFinished,
setTimer,
timerTick
}
export default compose(
totalWithEwStruct(),
connect(mapStateToProps, mapDispatchToProps),
withIndicators()
)(Total);
I try use timerTick by timer in componentDidMount
import React, { Component } from "react";
export default class TimeCounter extends Component {
componentDidMount() {
const { setTimer, timerTick } = this.props;
let timer = setInterval(() => {
timerTick();
console.log("tick");
}, 1000);
setTimer(timer);
}
componentDidUpdate() {
const { timeLeft, testFinished } = this.props;
if (timeLeft <= 0) {
testFinished();
}
}
render() {
const { timeLeft } = this.props;
return (
<div className="question-counter__timeleft">
Времени осталось
<span className="question-counter__timer">{timeLeft}</span>
</div>
);
}
}
So I see "tick" - "tick" - "tick" in console, but React doesn't dispatch my timerTick() function to reducer.
I have tried log to console action.type for debugging, and there is no action of timerTick.
const timerTick = () => {
return {
type: "TIMER_TICK"
};
};
export default timerTick;
Its code of action.
I don't understand why it doesn't work.
Your Total component needs to take timerTick function from props which is the one that is linked with redux store as you have added it to mapDispatchToProps.
If you do not destructure it from props, the ccomponent will use the imported function which isn't an action created unless its passed to dispatch function
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft, timerTick } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
You need to add dispatch of timer tick inside timer tick component. Because child component not aware about the actions.
Please refer below link for more details:
https://itnext.io/dispatching-actions-from-child-components-bd292a51f176
Response
if your component is not connected to redux you won’t be able to dispatch any action.
What do I mean?
Example
import React from “react”;
import { connect } from “react-redux”;
class MyCom extensa React.Component {
componentDidMount () {
const { action } = this.props;
action();
}
render () {
.....
}
}
const toState = state => ({....});
const toDispatch = {
action
};
export default connect(toState, toDispatch)(MyCom);
Explains
Basically connect from ”react-redux” is a HOC a high order component that on javascript world: is none but a high order function. a function that return another function.

Categories