clearInterval not working with set interval stored in Context React - javascript

I have a simple context provider with timer state and functions that set interval to increase state and stopinterval that should clearInterval. clearInterval is not working.
Here is my code:
import React, { useState, useContext } from "react";
const Timer = React.createContext();
const StartTimer = React.createContext();
const StopTimer = React.createContext();
export function getTimer()
{
return useContext(Timer);
}
export function useStartTimer()
{
return useContext(StartTimer);
}
export function useStopTimer()
{
return useContext(StopTimer);
}
export function TimerProvider({ children })
{
const [timer, setTimer] = useState(0);
const startTimer = () => setInterval(() => setTimer((prevState) => prevState + 0.5), 500);
const stopTimer = () => clearInterval(startTimer);
return (
<Timer.Provider value={timer}>
<StartTimer.Provider value={startTimer}>
<StopTimer.Provider value={stopTimer}>
{children}
</StopTimer.Provider>
</StartTimer.Provider>
</Timer.Provider>
);
}

Related

useContext is in a render loop once a context function is called by one of its children

Everytime i call the function on one of the useContext children to update the state of a variable it endlessly loops crashing react.
**
Context**
import { createContext, useState, useMemo } from "react";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "../../pages/firebaseConfig";
export const ScoreContext = createContext();
function ScoreProvider({ children }) {
const [score, setScore] = useState(0);
const [user, setUser] = useState(null);
onAuthStateChanged(auth, (user) => {
retrieveUserData(user);
setUser(user);
});
const retrieveUserData = async () => {
let email = await user.email;
const response = await fetch(
`http://localhost:3001/api/users/email/${email}`
);
const data = await response.json();
// console.log(data.payload.total_score);
setScore(data.payload.total_score);
return data.payload;
};
const [level, setLevel] = useState(null);
function updateLevel(i) {
setLevel(i);
console.log("hello world");
}
console.log(level);
return (
<ScoreContext.Provider
value={{
score: score,
update: retrieveUserData,
user: user,
// level: level,
updateLevel: updateLevel,
}}
>
{children}
</ScoreContext.Provider>
);
}
export default ScoreProvider;
Child
const onClick = () => { context.updateLevel(2); };
Tried adding a useMemo but didnt work. Tried wrapping the state inside the function but made code unreachable.

Why is my function not dispatching in react component?

why is fetchReviews not fetching?
Originally didn't use fetchData in use effect.
Ive tried using useDispatch.
BusinessId is being passed into the star component.
no errors in console.
please let me know if theres other files you need to see.
thank you!
star component:
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import {AiFillStar } from "react-icons/ai";
import { fetchReviews } from '../../actions/review_actions';
function Star(props) {
const [rating, setRating] = useState(null);
// const [reviews, setReview] = useState(props.reviews)
// const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
await fetchReviews(props.businessId)
};
fetchData();
console.log(props);
// getAverageRating();
});
const getAverageRating = () => {
let totalStars = 0;
props.reviews.forEach(review => {totalStars += review.rating});
let averageStars = Math.ceil(totalStars / props.reviews.length);
setRating(averageStars);
}
return (
<div className='star-rating-container'>
{Array(5).fill().map((_, i) => {
const ratingValue = i + 1;
return (
<div className='each-star' key={ratingValue}>
<AiFillStar
className='star'
color={ratingValue <= rating ? '#D32322' : '#E4E5E9'}
size={24} />
</div>
)
})}
</div>
);
};
export default Star;
star_container:
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import Star from "./star";
import { fetchReviews } from "../../actions/review_actions";
const mSTP = state => {
return {
reviews: Object.values(state.entities.reviews)
};
}
const mDTP = dispatch => {
return {
fetchReviews: businessId => dispatch(fetchReviews(businessId))
};
};
export default connect(mSTP, mDTP)(Star);
console image
why is fetchReviews not fetching? Originally didn't use fetchData in use effect. Ive tried using useDispatch. BusinessId is being passed into the star component. no errors in console.
edit!***
made some changes and added useDispatch. now it wont stop running. its constantly fetching.
function Star(props) {
const [rating, setRating] = useState(null);
const [reviews, setReview] = useState(null)
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
}), [];
ended up just calling using the ajax call in the useEffect.
useEffect(() => {
const fetchReviews = (businessId) =>
$.ajax({
method: "GET",
url: `/api/businesses/${businessId}/reviews`,
});
fetchReviews(props.businessId).then((reviews) => getAverageRating(reviews));
}), [];
if anyone knows how i can clean up and use the dispatch lmk.
ty all.
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
}), [];
dependency array is outside the useEffect. Since useEffect has no dependency option passed, function inside useEffect will run in every render and in each render you keep dispatching action which changes the store which rerenders the component since it rerenders code inside useEffect runs
// pass the dependency array in correct place
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
},[]), ;
Passing empty array [] means, code inside useEffect will run only once before your component mounted

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>
);
}

How to use jest.useFakeTimers(); with react

I have a simple component, I would like ot test setInterval(), basically after forwarding the timer the snapshots should show 3 but instead is always on 0. Any idea what is wrong in my code? Thanks
component:
import React from 'react';
export function Test(): JSX.Element {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return clearInterval(id);
});
return <div>{counter}</div>;
}
test:
import React from 'react';
import renderer, { act } from 'react-test-renderer';
import { Test } from './Test';
describe('test', () => {
jest.useFakeTimers();
it('should xx', () => {
const tree = renderer.create(<Test />);
act(() => {
jest.advanceTimersByTime(3000);
expect(tree).toMatchSnapshot();
});
});
});
result:
// Jest Snapshot v1
exports[`test should xx 1`] = `
<div>
0
</div>
`;
I think you are clearing the interval just after creating it. I mean you are returning clearInterval(id). Instead just return the id.
import React from 'react'; export function Test(): JSX.Element { const [counter, setCounter] = React.useState(0); React.useEffect(() => { const id = setInterval(() => { setCounter(counter + 1); }, 1000); return id; }); return <div>{counter}</div>; }
You need to change useEffect so it may return a clean-up function, like:
import React from 'react';
export function Test(): JSX.Element {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => clearInterval(interval);
});
return <div>{counter}</div>;
}

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

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

Categories