I'm trying to build an app which pulls data from backend with RTK Query and display that data as posts. I have successfully created RTKQ endpoint that gets data from backend with useEffect. However I want to put data in another array so that I can add more data with infinite scroll to that array but I am not able to do that.
My code is as follow. All help will be highly appreciated.
import { Box, Stack, Skeleton } from "#mui/material";
import React, { useEffect, useState } from "react";
import Post from "../components/postComponent";
import { useGetSimplesMutation } from '../services/authApi'
const Feed = () => {
const [getSimples, { isLoading, isSuccess, data }] = useGetSimplesMutation()
const [loadNow, setLoadNow] = useState(false)
let simplesData = []
useEffect(() => {
const handleGetSimples = async () => {
try {
await getSimples(0).unwrap()
if (isSuccess & !loadNow) {
console.log("Simples loaded")
simplesData = [...simplesData, ...data]
setLoadNow(true)
console.log(simplesData)
}
} catch (error) {
console.error(error.message)
}
}
handleGetSimples()
}, [])
return (
<Box flex={6} p={{ xs: 0, md: 2 }}>
{isLoading && (
<Box marginLeft={3}>
<Stack spacing={1} >
<Skeleton variant="text" height={100} />
<Skeleton variant="text" height={20} />
<Skeleton variant="text" height={20} />
<Skeleton variant="rectangular" height={300} />
</Stack>
</Box>
)}
{(isSuccess && loadNow) && simplesData.map(simples => {
return (
<div key={simples._id}>
<Post
userName={simples.userName}
dateSubmitted={simples.dateSubmitted}
beforePic={simples.beforePic}
afterPic={simples.afterPic}
tag={simples.tag}
/>
</div>
)
})}
</Box>
);
};
export default Feed;
This Code works when I directly map data obtained from RTKQ. However when I pass data to another array in an If statement the code inside if statement does not trigger when isSuccess gets true alongwith loadNow which has default state of false. I want to add data to simplesData array when this condition is true and then setLoadNow to true so that I can render my posts.
The line let simplesData = [] will be re-executed every time that the component re-renders, which will wipe out whatever you have stored.
If you want to keep data across multiple renders then you need to store it in a useState hook.
const [simplesData, setSimplesData] = useState([])
Inside your useEffect, you can call setSimplesData, using a functional update to minimize the dependencies of your effect.
setSimplesData(prevData => [...prevData, ...data])
There are some other issues here, mainly your lack of useEffect dependendencies. I think you want something like this?
const Feed = () => {
const [getSimples, { isLoading, isSuccess }] = useGetSimplesMutation()
const [loadPage, setLoadPage] = useState(0)
const [simplesData, setSimplesData] = useState([])
const handleEndReached = () => {
setLoadPage(prevPage => prevPage + 1);
}
const handleReset = () => {
setSimplesData([]);
setLoadPage(0);
}
useEffect(() => {
const handleGetSimples = async () => {
try {
const pageData = await getSimples(loadPage).unwrap()
console.log(`Simples page ${loadPage} loaded`)
setSimplesData(prevData => [...prevData, ...pageData])
} catch (error) {
console.error(error.message)
}
}
handleGetSimples()
}, [loadPage]);
According to the docs:
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
We can use the new useEffect() hook to simulate componentDidUpdate(), but it seems like useEffect() is being ran after every render, even the first time. How do I get it to not run on initial render?
As you can see in the example below, componentDidUpdateFunction is printed during the initial render but componentDidUpdateClass was not printed during the initial render.
function ComponentDidUpdateFunction() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
class ComponentDidUpdateClass extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidUpdate() {
console.log("componentDidUpdateClass");
}
render() {
return (
<div>
<p>componentDidUpdateClass: {this.state.count} times</p>
<button
onClick={() => {
this.setState({ count: this.state.count + 1 });
}}
>
Click Me
</button>
</div>
);
}
}
ReactDOM.render(
<div>
<ComponentDidUpdateFunction />
<ComponentDidUpdateClass />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
We can use the useRef hook to store any mutable value we like, so we could use that to keep track of if it's the first time the useEffect function is being run.
If we want the effect to run in the same phase that componentDidUpdate does, we can use useLayoutEffect instead.
Example
const { useState, useRef, useLayoutEffect } = React;
function ComponentDidUpdateFunction() {
const [count, setCount] = useState(0);
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<ComponentDidUpdateFunction />,
document.getElementById("app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
You can turn it into custom hooks, like so:
import React, { useEffect, useRef } from 'react';
const useDidMountEffect = (func, deps) => {
const didMount = useRef(false);
useEffect(() => {
if (didMount.current) func();
else didMount.current = true;
}, deps);
}
export default useDidMountEffect;
Usage example:
import React, { useState, useEffect } from 'react';
import useDidMountEffect from '../path/to/useDidMountEffect';
const MyComponent = (props) => {
const [state, setState] = useState({
key: false
});
useEffect(() => {
// you know what is this, don't you?
}, []);
useDidMountEffect(() => {
// react please run me if 'key' changes, but not on initial render
}, [state.key]);
return (
<div>
...
</div>
);
}
// ...
I made a simple useFirstRender hook to handle cases like focussing a form input:
import { useRef, useEffect } from 'react';
export function useFirstRender() {
const firstRender = useRef(true);
useEffect(() => {
firstRender.current = false;
}, []);
return firstRender.current;
}
It starts out as true, then switches to false in the useEffect, which only runs once, and never again.
In your component, use it:
const firstRender = useFirstRender();
const phoneNumberRef = useRef(null);
useEffect(() => {
if (firstRender || errors.phoneNumber) {
phoneNumberRef.current.focus();
}
}, [firstRender, errors.phoneNumber]);
For your case, you would just use if (!firstRender) { ....
Same approach as Tholle's answer, but using useState instead of useRef.
const [skipCount, setSkipCount] = useState(true);
...
useEffect(() => {
if (skipCount) setSkipCount(false);
if (!skipCount) runYourFunction();
}, [dependencies])
EDIT
While this also works, it involves updating state which will cause your component to re-render. If all your component's useEffect calls (and also all of its children's) have a dependency array, this doesn't matter. But keep in mind that any useEffect without a dependency array (useEffect(() => {...}) will be run again.
Using and updating useRef will not cause any re-renders.
#ravi, yours doesn't call the passed-in unmount function. Here's a version that's a little more complete:
/**
* Identical to React.useEffect, except that it never runs on mount. This is
* the equivalent of the componentDidUpdate lifecycle function.
*
* #param {function:function} effect - A useEffect effect.
* #param {array} [dependencies] - useEffect dependency list.
*/
export const useEffectExceptOnMount = (effect, dependencies) => {
const mounted = React.useRef(false);
React.useEffect(() => {
if (mounted.current) {
const unmount = effect();
return () => unmount && unmount();
} else {
mounted.current = true;
}
}, dependencies);
// Reset on unmount for the next mount.
React.useEffect(() => {
return () => mounted.current = false;
}, []);
};
a simple way is to create a let, out of your component and set in to true.
then say if its true set it to false then return (stop) the useEffect function
like that:
import { useEffect} from 'react';
//your let must be out of component to avoid re-evaluation
let isFirst = true
function App() {
useEffect(() => {
if(isFirst){
isFirst = false
return
}
//your code that don't want to execute at first time
},[])
return (
<div>
<p>its simple huh...</p>
</div>
);
}
its Similar to #Carmine Tambasciabs solution but without using state :)
function useEffectAfterMount(effect, deps) {
const isMounted = useRef(false);
useEffect(() => {
if (isMounted.current) return effect();
else isMounted.current = true;
}, deps);
// reset on unmount; in React 18, components can mount again
useEffect(() => {
isMounted.current = false;
});
}
We need to return what comes back from effect(), because it might be a cleanup function. But we don't need to determine if it is or not. Just pass it on and let useEffect figure it out.
In an earlier version of this post I said resetting the ref (isMounted.current = false) wasn't necessary. But in React 18 it is, because components can remount with their previous state (thanks #Whatabrain).
I thought creating a custom hook would be overkill and I didn't want to muddle my component's readability by using the useLayoutEffect hook for something unrelated to layouts, so, in my case, I simply checked to see if the value of my stateful variable selectedItem that triggers the useEffect callback is its original value in order to determine if it's the initial render:
export default function MyComponent(props) {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {
if(!selectedItem) return; // If selected item is its initial value (null), don't continue
//... This will not happen on initial render
}, [selectedItem]);
// ...
}
This is the best implementation I've created so far using typescript. Basically, the idea is the same, using the Ref but I'm also considering the callback returned by useEffect to perform cleanup on component unmount.
import {
useRef,
EffectCallback,
DependencyList,
useEffect
} from 'react';
/**
* #param effect
* #param dependencies
*
*/
export default function useNoInitialEffect(
effect: EffectCallback,
dependencies?: DependencyList
) {
//Preserving the true by default as initial render cycle
const initialRender = useRef(true);
useEffect(() => {
let effectReturns: void | (() => void) = () => {};
// Updating the ref to false on the first render, causing
// subsequent render to execute the effect
if (initialRender.current) {
initialRender.current = false;
} else {
effectReturns = effect();
}
// Preserving and allowing the Destructor returned by the effect
// to execute on component unmount and perform cleanup if
// required.
if (effectReturns && typeof effectReturns === 'function') {
return effectReturns;
}
return undefined;
}, dependencies);
}
You can simply use it, as usual as you use the useEffect hook but this time, it won't run on the initial render. Here is how you can use this hook.
useNoInitialEffect(() => {
// perform something, returning callback is supported
}, [a, b]);
If you use ESLint and want to use the react-hooks/exhaustive-deps rule for this custom hook:
{
"rules": {
// ...
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "useNoInitialEffect"
}]
}
}
#MehdiDehghani, your solution work perfectly fine, one addition you have to do is on unmount, reset the didMount.current value to false. When to try to use this custom hook somewhere else, you don't get cache value.
import React, { useEffect, useRef } from 'react';
const useDidMountEffect = (func, deps) => {
const didMount = useRef(false);
useEffect(() => {
let unmount;
if (didMount.current) unmount = func();
else didMount.current = true;
return () => {
didMount.current = false;
unmount && unmount();
}
}, deps);
}
export default useDidMountEffect;
Simplified implementation
import { useRef, useEffect } from 'react';
function MyComp(props) {
const firstRender = useRef(true);
useEffect(() => {
if (firstRender.current) {
firstRender.current = false;
} else {
myProp = 'some val';
};
}, [props.myProp])
return (
<div>
...
</div>
)
}
You can use custom hook to run use effect after mount.
const useEffectAfterMount = (cb, dependencies) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Here is the typescript version:
const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
For people who are having trouble with React 18 strict mode calling the useeffect on the initial render twice, try this:
// The init variable is necessary if your state is an object/array, because the == operator compares the references, not the actual values.
const init = [];
const [state, setState] = useState(init);
const dummyState = useRef(init);
useEffect(() => {
// Compare the old state with the new state
if (dummyState.current == state) {
// This means that the component is mounting
} else {
// This means that the component updated.
dummyState.current = state;
}
}, [state]);
Works in development mode...
function App() {
const init = [];
const [state, setState] = React.useState(init);
const dummyState = React.useRef(init);
React.useEffect(() => {
if (dummyState.current == state) {
console.log('mount');
} else {
console.log('update');
dummyState.current = state;
}
}, [state]);
return (
<button onClick={() => setState([...state, Math.random()])}>Update state </button>
);
}
ReactDOM.createRoot(document.getElementById("app")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
And in production.
function App() {
const init = [];
const [state, setState] = React.useState(init);
const dummyState = React.useRef(init);
React.useEffect(() => {
if (dummyState.current == state) {
console.log('mount');
} else {
console.log('update');
dummyState.current = state;
}
}, [state]);
return (
<button onClick={() => setState([...state, Math.random()])}>Update state </button>
);
}
ReactDOM.createRoot(document.getElementById("app")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="app"></div>
If you want to skip the first render, you can create a state "firstRenderDone" and set it to true in the useEffect with empty dependecy list (that works like a didMount). Then, in your other useEffect, you can check if the first render was already done before doing something.
const [firstRenderDone, setFirstRenderDone] = useState(false);
//useEffect with empty dependecy list (that works like a componentDidMount)
useEffect(() => {
setFirstRenderDone(true);
}, []);
// your other useEffect (that works as componetDidUpdate)
useEffect(() => {
if(firstRenderDone){
console.log("componentDidUpdateFunction");
}
}, [firstRenderDone]);
All previous are good, but this can be achieved in a simplier way considering that the action in useEffect can be "skipped" placing an if condition(or any other ) that is basically not run first time, and still with the dependency.
For example I had the case of :
Load data from an API but my title has to be "Loading" till the date were not there, so I have an array, tours that is empty at beginning and show the text "Showing"
Have a component rendered with different information from those API.
The user can delete one by one those info, even all making the tour array empty again as the beginning but this time the API fetch is been already done
Once the tour list is empty by deleting then show another title.
so my "solution" was to create another useState to create a boolean value that change only after the data fetch making another condition in useEffect true in order to run another function that also depend on the tour length.
useEffect(() => {
if (isTitle) {
changeTitle(newTitle)
}else{
isSetTitle(true)
}
}, [tours])
here my App.js
import React, { useState, useEffect } from 'react'
import Loading from './Loading'
import Tours from './Tours'
const url = 'API url'
let newTours
function App() {
const [loading, setLoading ] = useState(true)
const [tours, setTours] = useState([])
const [isTitle, isSetTitle] = useState(false)
const [title, setTitle] = useState("Our Tours")
const newTitle = "Tours are empty"
const removeTours = (id) => {
newTours = tours.filter(tour => ( tour.id !== id))
return setTours(newTours)
}
const changeTitle = (title) =>{
if(tours.length === 0 && loading === false){
setTitle(title)
}
}
const fetchTours = async () => {
setLoading(true)
try {
const response = await fetch(url)
const tours = await response.json()
setLoading(false)
setTours(tours)
}catch(error) {
setLoading(false)
console.log(error)
}
}
useEffect(()=>{
fetchTours()
},[])
useEffect(() => {
if (isTitle) {
changeTitle(newTitle)
}else{
isSetTitle(true)
}
}, [tours])
if(loading){
return (
<main>
<Loading />
</main>
)
}else{
return (
<main>
<Tours tours={tours} title={title} changeTitle={changeTitle}
removeTours={removeTours} />
</main>
)
}
}
export default App
const [dojob, setDojob] = useState(false);
yourfunction(){
setDojob(true);
}
useEffect(()=>{
if(dojob){
yourfunction();
setDojob(false);
}
},[dojob]);
I am trying to turnoff camera and flashlight when the component gets unmount , I am using react hook I have two function startCamera() and stopCamera() , I am calling startcamera when the component gets mount and stop camera when component gets unmount.
But its showing me error when stopCamera is called while unmounting
i have created an another button to test if StopCamera() working and i found its working , but i want to call the function when component is getting unmounted
my code:
CameraScreen.js
import "./styles.css";
import { useState, useEffect, useRef } from "react";
export default function CameraScreen() {
const videoElement = useRef(null);
const [facingMode, setFacingMode] = useState("environment");
const handleFacingModeToggle = () => {
stopCamera();
facingMode === "environment"
? setFacingMode("user")
: setFacingMode("environment");
};
useEffect(() => {
// const getUserMedia = async () => {
// try {
// const stream = await navigator.mediaDevices.getUserMedia({
// video: true
// });
// videoElement.current.srcObject = stream;
// } catch (err) {
// console.log(err);
// }
// };
// getUserMedia();
startCamera();
return function cleanup() {
stopCamera();
};
}, []);
const stopCamera = () =>
videoElement.current.srcObject &&
videoElement.current.srcObject.getTracks().forEach((t) => t.stop());
function startCamera() {
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices
.getUserMedia({
video: { facingMode: facingMode },
width: { ideal: 1280 },
height: { ideal: 720 }
})
.then(function (stream) {
if (videoElement.current) videoElement.current.srcObject = stream;
const track = stream.getVideoTracks()[0];
//Create image capture object and get camera capabilities
const imageCapture = new ImageCapture(track);
const photoCapabilities = imageCapture
.getPhotoCapabilities()
.then(() => {
//todo: check if camera has a torch
//let there be light!
track.applyConstraints({
advanced: [{ torch: true }]
});
});
})
.catch(function (error) {
alert("Please check your device permissions");
console.log("Something went wrong!");
console.log(error);
});
if (videoElement.current)
videoElement.current.onloadeddata = function () {
if (window.NativeDevice)
window.NativeDevice.htmlCameraReadyToRecord(true);
};
}
}
return (
<>
<video
autoPlay={true}
ref={videoElement}
style={{
minHeight: "67.82vh",
maxHeight: "67.82vh",
maxWidth: "100%",
minWidth: "100%"
}}
className="border-3rem bg-[#666]"
></video>
<button onClick={stopCamera}> stopCamera</button>
</>
);
}
App.js
import "./styles.css";
import { useState } from "react";
import CameraScreen from "./cameraScreen";
export default function App() {
const [switchS, setSwitchS] = useState(false);
return (
<div>
<button className="" onClick={() => setSwitchS(!switchS)} value="switch">
switch
</button>
{switchS && <CameraScreen />}
{!switchS && "Blank Screen"}
</div>
);
}
PS: the above code working at :https://5t2to.csb.app/
codesandbox link : https://codesandbox.io/s/practical-fast-5t2to?file=/src/cameraScreen.js
You can use useLayoutEffect hook. It works just before unmounting, like componentWillUnmount.
Here is an example to that
https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/random.js
You can go to https://ikqdn.csb.app/rand in sandbox browser and check the console on clicking to home button.
You can see the difference in working while unmounting of both useEffect and useLayoutEffect
It preserves ref.current, so what you can do is, you can pass ref.current in the function that you are calling just before unmounting, to prevent ref to the dom elment.
It took a bit of debugging/sleuthing to find the issue. So even though you have a ref attached to the video element, when the component is unmounted the ref is still mutated and becomes undefined. The solution is to save a reference to the current videoElement ref value and use this in a cleanup function.
useEffect(() => {
startCamera();
const ref = videoElement.current;
return () => {
ref.srcObject.getTracks().forEach((t) => t.stop());
};
}, []);
Simply add useLayoutEffect to stop camera
useEffect(() => {
// const getUserMedia = async () => {
// try {
// const stream = await navigator.mediaDevices.getUserMedia({
// video: true
// });
// videoElement.current.srcObject = stream;
// } catch (err) {
// console.log(err);
// }
// };
// getUserMedia();
startCamera();
}, []);
useLayoutEffect(()=>()=>{
stopCamera();
},[]);
Just need to change useEffect() to useLayoutEffect()and it will works like a charm.
useLayoutEffect(() => {
const ref = videoElement;
console.log(ref);
startCamera();
return function cleanup() {
stopCamera();
};
}, []);
sanbox link :- https://codesandbox.io/s/naughty-murdock-by5tc?file=/src/cameraScreen.js:415-731
I'm trying to create a functional component that fetches data from an API and renders it to a list. After the data is fetched and rendered I want to check if the URL id and list item is equal, if they are then the list item should be scrolled into view.
Below is my code:
import React, { Fragment, useState, useEffect, useRef } from "react";
export default function ListComponent(props) {
const scrollTarget = useRef();
const [items, setItems] = useState([]);
const [scrollTargetItemId, setScrollTargetItemId] = useState("");
useEffect(() => {
const fetchData = async () => {
let response = await fetch("someurl").then((res) => res.json());
setItems(response);
};
fetchData();
if (props.targetId) {
setScrollTargetItemId(props.targetId)
}
if (scrollTarget.current) {
window.scrollTo(0, scrollTarget.current.offsetTop)
}
}, [props]);
let itemsToRender = [];
itemsToRender = reports.map((report) => {
return (
<li
key={report._id}
ref={item._id === scrollTargetItemId ? scrollTarget : null}
>
{item.payload}
</li>
);
});
return (
<Fragment>
<ul>{itemsToRender}</ul>
</Fragment>
);
}
My problem here is that scrollTarget.current is always undefined. Please advice what I'm doing wrong. Thanks in advance.
Using useCallback, as #yagiro suggested, did the trick!
My code ended up like this:
const scroll = useCallback(node => {
if (node !== null) {
window.scrollTo({
top: node.getBoundingClientRect().top,
behavior: "smooth"
})
}
}, []);
And then I just conditionally set the ref={scroll} on the node that you want to scroll to.
That is because when a reference is changed, it does not cause a re-render.
From React's docs: https://reactjs.org/docs/hooks-reference.html#useref
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
constructor(props) {
thi.modal = React.createRef();
}
handleSwitch() {
// debugger
this.setState({ errors: [] }, function () {
this.modal.current.openModal('signup') // it will call function of child component of Modal
});
// debugger
}
return(
<>
<button className="login-button" onClick={this.handleSwitch}>Log in with email</button>
<Modal ref={this.modal} />
</>
)
React Hooks will delay the scrolling until the page is ready:
useEffect(() => {
const element = document.getElementById('id')
if (element)
element.scrollIntoView({ behavior: 'smooth' })
}, [])
If the element is dynamic and based on a variable, add them to the Effect hook:
const [variable, setVariable] = useState()
const id = 'id'
useEffect(() => {
const element = document.getElementById(id)
if (element)
element.scrollIntoView({ behavior: 'smooth' })
}, [variable])
I'm playing with hooks now. My code looks like that:
import React, { Fragment, useState, useEffect, useRef } from 'react';
import ReactPlayer from 'react-player';
import storage from '../../utils/localStorage';
const STORAGE_VIDEOS_DATA_KEY = 'VIDEOS_DATA';
const VideoItem = () => {
const [ playingStatus, setPlayingStatus ] = useState(false);
const [ videoId ] = useState(318298217);
const [ videoProgress, setVideoProgress ] = useState(0);
const player = useRef();
useEffect(() => {
window.addEventListener(
'beforeunload',
saveStateToLocalStorage
);
return () => {
window.removeEventListener(
'beforeunload',
saveStateToLocalStorage
);
saveStateToLocalStorage(); // this needs to be fired only once when component onmounts but clearing effect is done every time videoProgress changes
};
}, [videoProgress]);
const onVideoEnd = () => {
console.log('backend call - video end status');
};
const seekToPoint = () => {
const videosData = storage.hasKey(STORAGE_VIDEOS_DATA_KEY) &&
JSON.parse(storage.getItem(STORAGE_VIDEOS_DATA_KEY));
const toReturnVideoPoint = videosData[videoId] || 0;
player.current.seekTo(Number(toReturnVideoPoint));
};
const saveStateToLocalStorage = () => {
const videosPlayedDuration = {
[videoId]: videoProgress,
};
storage.setItem(STORAGE_VIDEOS_DATA_KEY, JSON.stringify(videosPlayedDuration));
};
return (
<Fragment>
<ReactPlayer
ref={player}
playing={playingStatus}
url={`https://player.vimeo.com/video/${videoId}`}
onPause={() => {
saveStateToLocalStorage();
setPlayingStatus(false);
}}
onEnded={onVideoEnd}
onProgress={progress => setVideoProgress(progress.playedSeconds)}
/>
<button onClick={() => {
seekToPoint();
setPlayingStatus(true);
}}
>
GO BACK TO THE PREVIOUS POINT
</button>
</Fragment>
);
};
export default VideoItem;
I want to clear my effect only when component unmounts, not everytime component gets rerendered when videoProgress piece of state changes. I could just use empty array instead of [ videoProgress ] but then videoProgress in method saveStateToLocalStorage won't get updated and onPause method won't cause a proper saving to localStorage (it will save 0). Is there a way to do that with hooks and not using class component with componentWillUnmount lifecycle?
You have to understand how useEffect works.
useEffect(() => {
// your code
return () => {
// your clearup code
};
}, [videoProgress]); // => This is the key point. It's called dependency
Whenever the dependency gets changed, react re-run the useEffect. Your videoProgress value gets changes after time interval (may be after 1 sec, I don't know). That's why react re-run the useEffect.
You can do this.
useEffect(() => {
// your code
return () => {
// your clearup code. =>> This block will work as componentWillUnmount
};
}, []); // => No Dependency
Or you can add dependency of your video id (as per your requirements).
One Handy example
useEffect(() => {
// This block (before the return) will act as componentDidMount
return () => {
// This block will work as componentWillUnmount
};
}, []); // => beacase of No Dependency
One Side note: You can have multiple useEffect.