useEffect called multiple times - reactjs

I have the following reactjs code:
import React, { useState, useEffect } from 'react'
const Test = () => {
const [counter, setCounter] = useState(0)
useEffect(() => {
const data = localStorage.getItem('counter')
setCounter(parseInt(data, 10))
console.log('init', data)
}, [])
useEffect(() => {
localStorage.setItem('counter', counter)
console.log('changed', counter)
}, [counter])
const addCounter = () => {
setCounter((c) => c + 1)
console.log('added', counter)
}
return (
<div>
{counter}
<button onClick={addCounter}>+</button>
</div>
)
}
function App() {
return (
<div className='App'>
<Test />
</div>
)
}
The useEffect() hooks are being called multiple times. The states are persisted in localStorage. But upon page refresh, the states reset to default values:
init 4 <--- counter was incremented to 4 at previous session
changed 0 <-- counter is reset to 0??
init 0 <-- this should not appear
changed 0
What am I doing wrong?

You can use this boilerplate to avoid repeated renders while in <StrictMode>
function App() {
const [success, setSuccess] = useState(false);
const isMounted = useRef(false);
useEffect(() => {
console.log('started');
if (isMounted.current) {
console.log('mounted');
} else {
console.log('mounting');
isMounted.current = true;
}
}, [success]);
const handleClick = (e) => {
setSuccess(!success);
}
return (
<div className="App">
<header className="App-header">
<button className="button" onClick={handleClick}>Action</button>
</header>
</div>
);
}

Related

React setState returns function dispatch()

I have met issues setting the state of the const displayClipName in the following function which is blocking despite the fact that I passed the props from the parent element to the child.
const audioClips = [
{
keyCode: 67,
keyTrigger: "C",
id: "Closed-HH",
src: "https://s3.amazonaws.com/freecodecamp/drums/Cev_H2.mp3"
}
]
function App() {
const [displayClipName, setDisplayClipName] = React.useState('Click a key!')
return (
<div id="drum-machine" className="text-white text-center">
<div className="container bg-info">
<h1>FCC - Drum Machine</h1>
{audioClips.map((clip) => (
<Pad
key={clip.id}
clip={clip}
setDisplayClipName={setDisplayClipName}
/>
))}
<h2>{displayClipName}</h2>
</div>
</div>
)
}
const Pad = ({clip, setDisplayClipName}) => {
const [playing, setPlaying] = React.useState(false)
React.useEffect(() => {
document.addEventListener('keydown', handleKey);
return () => {
document.removeEventListener('keydown', handleKey)
}
}, [])
const handleKey = (e) => {
if(e.keyCode === clip.keyCode) {
playSound()
}
}
const playSound = () => {
const audioPlay = document.getElementById(clip.keyTrigger);
const clipName = document.getElementById(clip.id)
setPlaying(true);
setTimeout(() => setPlaying(false), 300);
audioPlay.currentTime = 0;
audioPlay.play();
setDisplayClipName(clipName);
console.log(setDisplayClipName)
}
return (
<div onClick={playSound} id={`drum-pad-${clip.keyTrigger}`}>
<audio src={clip.src} className="clip" id={clip.keyTrigger}/>
{clip.keyTrigger}
</div>
)
}
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
The console returns the following message:
function dispatchSetState()
​
length: 1
​
name: "bound dispatchSetState"
​
<prototype>: function ()
As some have pointed out in comments to your post it'd be better if you used refs. Also you were logging a function that's why the console displayed that. I have taken the liberty to do some modifications to your code so I could understand it better, I would suggest you keep the ones you find reasonable:
The displayName variable has an undefined state when no song is playing. This could be set from any other part of the application but you wouldn't depend on rerendering the component for it to return to a default message ("Press a key!")
The playSound function could be bound to the id of the song and you would avoid having to check the HTML element that received the input.
Here is a working snippet.
const { useState, useEffect } = React;
const audioClips = [
{
keyCode: 67,
keyTrigger: "C",
id: "Closed-HH",
src: "https://s3.amazonaws.com/freecodecamp/drums/Cev_H2.mp3"
}
]
const Pad = ({ clip, setDisplayClipName }) => {
const [playing, setPlaying] = useState(false);
useEffect(() => {
document.addEventListener("keydown", handleKey);
return () => {
document.removeEventListener("keydown", handleKey);
};
}, []);
const handleKey = (e) => {
if (e.keyCode === clip.keyCode) {
playSound(clip.id);
}
};
const playSound = (clipId) => {
const audioPlay = document.getElementById(clip.keyTrigger);
setPlaying(true);
setTimeout(() => setPlaying(false), 300);
audioPlay.currentTime = 0;
audioPlay.play();
setDisplayClipName(clipId);
};
return (
<div onClick={() => playSound(clip.id)} id={`drum-pad-${clip.keyTrigger}`}>
<audio src={clip.src} className="clip" id={clip.keyTrigger} />
{clip.keyTrigger}
</div>
);
};
function App() {
const [clipName, setClipName] = useState(undefined);
return (
<div id="drum-machine" className="text-white text-center">
<div className="container bg-info">
<h1>FCC - Drum Machine</h1>
{audioClips.map((clip) => (
<Pad key={clip.id} clip={clip} setDisplayClipName={setClipName} />
))}
<h2>{clipName ? clipName : "Press a key!"}</h2>
</div>
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
To further refine the Pad component and avoid the missing dependencies on the useEffect hook I would suggest you model it like this, using useMemo:
export const Pad = ({ clip, setDisplayClipName }) => {
const [playing, setPlaying] = useState(false);
const playSound = useMemo(
() => () => {
const audioPlay = document.getElementById(clip.keyTrigger);
setPlaying(true);
setTimeout(() => setPlaying(false), 300);
if (audioPlay) {
audioPlay.currentTime = 0;
audioPlay.play();
}
setDisplayClipName(clip.id);
},
[setDisplayClipName, clip]
);
useEffect(() => {
const handleKey = (e) => {
if (e.keyCode === clip.keyCode) {
playSound();
}
};
document.addEventListener("keydown", handleKey);
return () => {
document.removeEventListener("keydown", handleKey);
};
}, [playSound, clip.keyCode]);
return (
<div onClick={playSound} id={`drum-pad-${clip.keyTrigger}`}>
<audio src={clip.src} className="clip" id={clip.keyTrigger} />
{clip.keyTrigger}
</div>
);
};

how to pass the values between components without using props

I want to pass the values from the counter component to Multiplier Component.I am using local storage for that.
I can not use props for this task niether useReducer, context etc.
Task is to on clicking the counter button from counter component it changes the value of multiplier component.
In App.js, the counter component works fine and store the value on localstorage but the multiplier component does not change its value on rendering in App.js as well as in the Counter component. I want to have change the value of Multiplier Component when i click on counter button.
Counter.js
import { useState, useEffect } from 'react'
import Multiplier from './components/Multiplier'
const Counter = () => {
const [counter, setCounter] = useState(1)
useEffect(() => {
localStorage.setItem('count', counter)
}, [])
const funcIncr = () => {
setCounter(counter => counter + 1);
localStorage.setItem('count', counter + 1)
showmult()
}
const funcDecr = () => {
setCounter(counter => counter - 1);
localStorage.setItem('count', counter - 1)
}
const showmult = () => {
return <Multiplier />
}
return (
<div className=''>
<button onClick={funcIncr}>+</button>
<div id='count'>{counter}</div>
<button onClick={funcDecr}>-</button>
<Multiplier />
</div>
)
}
export default Counter
Multiplier.js
import { useState, useEffect } from 'react'
const Multiplier = () => {
const [multiple, setMultiple] = useState(-5)
useEffect(() => {
let value = localStorage.getItem('count')
setMultiple(multiple=>multiple*value)
// multiplier()
}, [multiple])
// const multiplier = ()=>{
// // this.forceUpdate();
// let value = localStorage.getItem('count')
// setMultiple(multiple=>multiple*value)
// }
return (
<>
<div>Multiple: {multiple}</div>
</>
)
}
export default Multiplier
App.js
import Counter from './Counter'
import './App.css';
import Multiplier from './components/Multiplier';
function App() {
return (
<div className="App">
<div className='flex'>
<div>Counter</div>
<Counter/>
<Multiplier/>
</div>
</div>
);
}
export default App;
Counter.js
import React from 'react';
import { useState, useEffect } from 'react';
const Counter = () => {
const [counter, setCounter] = useState(1);
const funcIncr = () => {
setCounter((counter) => counter + 1);
localStorage.setItem('count', counter + 1);
};
const funcDecr = () => {
setCounter((counter) => counter - 1);
localStorage.setItem('count', counter - 1);
};
useEffect(() => {
let value = localStorage.getItem('count');
const count = localStorage.getItem('count');
const multiple = value * count;
document.getElementById('multipler').innerHTML = 'Multiple: ' + multiple;
}, [counter]);
return (
<div className="">
<button onClick={funcIncr}>+</button>
<div id="count">{counter}</div>
<button onClick={funcDecr}>-</button>
</div>
);
};
export default Counter;
Multiplier.js
import React from 'react';
const Multiplier = () => {
return <div id="multipler" />;
};
export default Multiplier;
A solution would be to use a Custom Event
window.dispatchEvent(
new CustomEvent('updateTitle', {
detail: {
firstName: values.firstName,
lastName: values.lastName,
},
}
)
You can add an event listener for the pages in which you want to listen to this event
useEffect(() => {
window.addEventListener("updateTitle", updateTitleEventHandler, false);
return () => {
window.removeEventListener("updateTitle", updateTitleEventHandler);
};
}, []);
const updateTitleEventHandler = useCallback((e) => {
//your logic
});

Not Rendering Card - React

I'm new to React, and I would like to know if someone can help me?
I'm trying to use useEffect and State to manipulate the API.
But the cards are not rendering.
Sometimes all the cards are rendering, other times not.. and they always come on a different order even after sorting them :( Can you help me?
App.js
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect } from "react";
import PlayerList from "./PlayerList";
import axios from "axios";
function App() {
const Team = [
...
];
const Team2 = [
...
];
const Team3 = [
...
];
const teamForLoop = [Team, Team2, Team3];
const [allPlayers, setAllPlayers] = useState([]);
const [team, setTeam] = useState([]);
const [allTeams] = useState(teamForLoop);
const [loading, setLoading] = useState(true);
useEffect(() => {
const playerInfo = async () => {
setLoading(true);
allTeams.map(async (teamArray) => {
setTeam([]);
teamArray.map(async (player) => {
let playerName = player.split(" ");
const result = await axios.get(
`https://www.thesportsdb.com/api/v1/json/2/searchplayers.php?p=${playerName[0]}%20${playerName[1]}`
);
if (result.data.player === null) {
setTeam((state) => {
return [...state];
});
} else {
setTeam((state) => {
return [...state, result.data.player[0]];
});
}
});
setAllPlayers(team);
});
setLoading(false);
};
playerInfo();
}, [allTeams]);
if (loading) return "...Loading...";
return (
<>
<PlayerList allPlayers={allPlayers} />
</>
);
}
export default App;
PlayerList.js
import React from "react";
export default function PlayerList({ allPlayers }) {
const myData = []
.concat(allPlayers)
.sort((a, b) => (a.strNumber > b.strNumber ? 1 : -1))
.sort((a, b) => (a.idTeam !== b.idTeam ? 1 : -1));
return (
<div>
{myData.map((player, index) => (
<div key={index}>
<div className="playerCard">
<img
className="playerImage"
src={player.strCutout}
alt={`${player.strPlayer}`}
/>
<h1 className="playerName">{player.strPlayer}</h1>
<h2 className="playerNumber">{player.strNumber}</h2>
</div>
</div>
))}
</div>
);
}
Codesandbox link:
"https://codesandbox.io/s/busy-orla-v872kt?file=/src/App.js"

useEffect only runs on hot reload

I have a parent/child component where when there is a swipe event occurring in the child the parent component should fetch a new profile. The problem is the useEffect in the child component to set up the eventListeneners currently is not running, only occasionally on hot-reload which in reality should run basically every time.
Child component
function Profile(props: any) {
const [name] = useState(`${props.profile.name.title} ${props.profile.name.first} ${props.profile.name.last}`);
const [swiped, setSwiped] = useState(0)
const backgroundImage = {
backgroundImage: `url(${props.profile.picture.large})`
};
const cardRef = useRef<HTMLDivElement>(null);
const card = cardRef.current
let startX:any = null;
function unify (e:any) { return e.changedTouches ? e.changedTouches[0] : e };
function lock (e:any) { if (card) {startX = unify(e).clientX; console.log(startX)} }
function move (e: any) {
console.log('move')
if(startX) {
let differenceX = unify(e).clientX - startX, sign = Math.sign(differenceX);
if(sign < 0 || sign > 0) {
setSwiped((swiped) => swiped +1)
props.parentCallback(swiped);
startX = null
}
}
}
// Following code block does not work
useEffect(() => {
if (card) {
console.log(card)
card.addEventListener('mousedown', lock, false);
card.addEventListener('touchstart', lock, false);
card.addEventListener('mouseup', move, false);
card.addEventListener('touchend', move, false);
}
})
return (
<div>
<h1 className="heading-1">{name}</h1>
<div ref={cardRef} className="card" style={backgroundImage}>
</div>
</div>
);
}
Parent component
function Profiles() {
const [error, setError] = useState<any>(null);
const [isLoaded, setIsLoaded] = useState(false);
const [profiles, setProfiles] = useState<any[]>([]);
const [swiped, setSwiped] = useState(0)
useEffect(() => {
getProfiles()
}, [swiped])
const callback = useCallback((swiped) => {
setSwiped(swiped);
console.log(swiped);
}, []);
const getProfiles = () => {
fetch("https://randomuser.me/api/")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setProfiles(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}
if (error) {
return <h1 className="heading-1">Error: {error.message}</h1>;
} else if (!isLoaded) {
return <h1 className="heading-1">Loading...</h1>;
} else {
return (
<div id="board">
{profiles.map(profile => (
<Profile key={profile.id.value} profile={profile} parentCallback={callback}/>
))}
</div>
);
}
}
If you want the parent components swiped state to change, you need to pass "setSwiped" from the parent to the child compenent. You will also need to pass "swiped" to the child to use its current value to calculate the new value. I'm going to assume you declared the useState in the child component trying to set the parents state of the same name, so I'm going to remove that useState Declaration in the child altogether.
Here's an example of passing the setSwiped method and swiped value to the child:
PARENT
import React, {useState, useEffect, useCallback} from 'react';
import './Index.css';
import Profile from './Profile'
function Profiles() {
const [error, setError] = useState<any>(null);
const [isLoaded, setIsLoaded] = useState(false);
const [profiles, setProfiles] = useState<any[]>([]);
const [swiped, setSwiped] = useState(0)
useEffect(() => {
getProfiles()
}, [swiped])
const callback = useCallback((swiped) => {
setSwiped(swiped);
console.log(swiped);
}, []);
const getProfiles = () => {
fetch("https://randomuser.me/api/")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setProfiles(result.results);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}
if (error) {
return <h1 className="heading-1">Error: {error.message}</h1>;
} else if (!isLoaded) {
return <h1 className="heading-1">Loading...</h1>;
} else {
return (
<div id="board">
{profiles.map(profile => (
<Profile key={profile.id.value} profile={profile} parentCallback={callback} setSwiped={setSwiped} swiped={swiped}/>
))}
</div>
);
}
}
export default Profiles;
CHILD
import React, {useState, useRef, useEffect } from 'react';
import './Index.css';
function Profile(props: any) {
const [name] = useState(`${props.profile.name.title} ${props.profile.name.first} ${props.profile.name.last}`);
const backgroundImage = {
backgroundImage: `url(${props.profile.picture.large})`
};
const cardRef = useRef<HTMLDivElement>(null);
const card = cardRef.current
let startX:any = null;
function unify (e:any) { return e.changedTouches ? e.changedTouches[0] : e };
function lock (e:any) { if (card) {startX = unify(e).clientX; console.log(startX)} }
function move (e: any) {
console.log('move')
if(startX) {
let differenceX = unify(e).clientX - startX, sign = Math.sign(differenceX);
if(sign < 0 || sign > 0) {
props.setSwiped((props.swiped) => props.swiped +1)
props.parentCallback(props.swiped);
startX = null
}
}
}
useEffect(() => {
if (card) {
console.log(card)
card.addEventListener('mousedown', lock, false);
card.addEventListener('touchstart', lock, false);
card.addEventListener('mouseup', move, false);
card.addEventListener('touchend', move, false);
}
})
return (
<div>
<h1 className="heading-1">{name}</h1>
<div ref={cardRef} className="card" style={backgroundImage}>
</div>
</div>
);
}
export default Profile;
I'm hoping I didn't miss anything here.
Best of luck.

How to avoid extra renders in my component to use react hooks

I try to use react hooks instead of class-based components and have some problem with performance.
Code:
import React, { memo, useCallback, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
let counter = -1;
function useToggle(initialValue) {
const [toggleValue, setToggleValue] = useState(initialValue);
const toggler = useCallback(() => setToggleValue(!toggleValue), [
toggleValue,
setToggleValue
]);
return [toggleValue, toggler];
}
const Header = memo(({ onClick }) => {
counter = counter + 1;
return (
<div>
<h1>HEADER</h1>
<button onClick={onClick}>Toggle Menu</button>
<div>Extra Render: {counter}</div>
</div>
);
});
const Dashboard = memo(() => {
const [visible, toggle] = useToggle(false);
const handleMenu = useCallback(
() => {
toggle(!visible);
},
[toggle, visible]
);
return (
<>
<Header onClick={handleMenu} />
<div>Dashboard with hooks</div>
{visible && <div>Menu</div>}
</>
);
});
export default Dashboard;
Here is an example of what I wanna do: Example.
As you see, there are extra renders in my Header component.
My question: Is it possible to avoid extra renders to use react-hooks?
Change your custom hook useToggle to use functional state setter, like this
function useToggle(initialValue) {
const [toggleValue, setToggleValue] = useState(initialValue);
const toggler = useCallback(() => setToggleValue(toggleValue => !toggleValue));
return [toggleValue, toggler];
}
and use it like this :
const Dashboard = memo(() => {
const [visible, toggle] = useToggle(false);
const handleMenu = useCallback(
() => {
toggle();
}, []
);
return (
<>
<Header onClick={handleMenu} />
<div>Dashboard with hooks</div>
{visible && <div>Menu</div>}
</>
);
});
Complete example : https://codesandbox.io/s/z251qjvpw4
Edit
This can be simpler (thanks to #DoXicK)
function useToggle(initialValue) {
const [toggleValue, setToggleValue] = useState(initialValue);
const toggler = useCallback(() => setToggleValue(toggleValue => !toggleValue), [setToggleValue]);
return [toggleValue, toggler];
}
const Dashboard = memo(() => {
const [visible, toggle] = useToggle(false);
return (
<>
<Header onClick={toggle} />
<div>Dashboard with hooks</div>
{visible && <div>Menu</div>}
</>
);
});
This is an issue with useCallback get invalidate too often. (there is a conversation about this on React repo here: https://github.com/facebook/react/issues/14099)
since useCallback will be invalidated every time toggle value change and return a new function, then passing a new handleMenu function to <Header /> cause it re-render.
A workaround solution is to create a custom useCallback hook:
(Copied from https://github.com/facebook/react/issues/14099#issuecomment-457885333)
function useEventCallback(fn) {
let ref = useRef();
useLayoutEffect(() => {
ref.current = fn;
});
return useMemo(() => (...args) => (0, ref.current)(...args), []);
}
Example: https://codesandbox.io/s/1o87xrnj37
If you use the callback pattern to update state, you would be able to avoid extra re-renders since the function need not be created again and again and you use just create handleMenu on first render
const Dashboard = memo(() => {
const [visible, toggle] = useToggle(false);
const handleMenu = useCallback(() => {
toggle(visible => !visible);
}, []);
return (
<>
<Header onClick={handleMenu} />
<div>Dashboard with hooks</div>
{visible && <div>Menu</div>}
</>
);
});
Working Demo

Resources