How to use jest.useFakeTimers(); with react - javascript

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

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

clearInterval not working with set interval stored in Context React

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

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

Debounce in React component JS

I'm writing a simple debounce function for an input component
export const debounce = (func, wait) => {
let timeout
return function (...args) {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => {
timeout = null
Reflect.apply(func, this, args)
}, wait)
}
}
it imported from an external file, and used as a wrap for input onKeyUp handler inside a React component (Hooks)
const handleChange = debounce(() => console.log("test"), 1000)
PROBLEM: I'm getting "test" log every time when text in input changes, not only one - as expected.
What am I doing wrong?
I'm not sure what is the problem with your code but here is a version with hooks working
import { useEffect, useState } from "react";
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
};
export default useDebounce;
and then you use it as
const debouncedValue = useDebounce(inputValue, delay);

Categories