I observed a strange behavior in React while unmounting a component. Typically, when we unmount a component, we do this.
const [isVisible, setVisibility] = useState(false)
const onKeyDown = (event) => { console.log(event) }
useEffect(() => {
window.addEventListener('keydown', onKeyDown)
return () => { window.removeEventListener('keydown', onKeyDown) }
}, [isVisible])
return (
<button onClick={() => setVisibility(!isVisible)}>Click me!</button>
)
It works perfectly fine, but when we try to achieve this same thing using an if-else statement instead of a return, unmounting doesn't work. I searched for the reason, and I found out that when the state changes, React re-renders the handler function, which results in mismatching of both the handler (old and new one), and it won't remove.
Code using if-else statement.
const [isVisible, setVisibility] = useState(false)
const onKeyDown = (event) => { console.log(event) }
useEffect(() => {
if(isVisible) window.addEventListener('keydown', onKeyDown)
else window.removeEventListener('keydown', onKeyDown)
}, [isVisible])
return (
<button onClick={() => setVisibility(!isVisible)}>Click me!</button>
)
But now my question is how it works when we write return()? Does React save the same handler function when we write return()?
I don't know if this is the answer you're looking for, but I had issues where a stateful variable was needed on my unmount function (saving a form value to localstorage), and the issue seemed to lie in the fact that the original value on initial render was being used, and would not update: (I assume because my useEffect had been set to only run at the start via ,[])
If the if/else statement you mentioned is based on a stateful variable, you likely ran into the same situation.
Here's a small code snippet of what solved things for me, the same idea likely applies for you. Just use a ref and keep the ref updated as you state value changes:
const formValueRef = useRef<any>();
useEffect(() => {
return () => {
saveEntryFormToLocalStorage(formValueRef.current); // save form to storage when the component is destroyed
}
}, []) // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
formValueRef.current = form.values;
}, [form.values]); // eslint-disable-line react-hooks/exhaustive-deps
Related
Are there ways to simulate componentDidMount in React functional components via hooks?
For the stable version of hooks (React Version 16.8.0+)
For componentDidMount
useEffect(() => {
// Your code here
}, []);
For componentDidUpdate
useEffect(() => {
// Your code here
}, [yourDependency]);
For componentWillUnmount
useEffect(() => {
// componentWillUnmount
return () => {
// Your code here
}
}, [yourDependency]);
So in this situation, you need to pass your dependency into this array. Let's assume you have a state like this
const [count, setCount] = useState(0);
And whenever count increases you want to re-render your function component. Then your useEffect should look like this
useEffect(() => {
// <div>{count}</div>
}, [count]);
This way whenever your count updates your component will re-render. Hopefully this will help a bit.
There is no exact equivalent for componentDidMount in react hooks.
In my experience, react hooks requires a different mindset when developing it and generally speaking you should not compare it to the class methods like componentDidMount.
With that said, there are ways in which you can use hooks to produce a similar effect to componentDidMount.
Solution 1:
useEffect(() => {
console.log("I have been mounted")
}, [])
Solution 2:
const num = 5
useEffect(() => {
console.log("I will only run if my deps change: ", num)
}, [num])
Solution 3 (With function):
useEffect(() => {
const someFunc = () => {
console.log("Function being run after/on mount")
}
someFunc()
}, [])
Solution 4 (useCallback):
const msg = "some message"
const myFunc = useCallback(() => {
console.log(msg)
}, [msg])
useEffect(() => {
myFunc()
}, [myFunc])
Solution 5 (Getting creative):
export default function useDidMountHook(callback) {
const didMount = useRef(null)
useEffect(() => {
if (callback && !didMount.current) {
didMount.current = true
callback()
}
})
}
It is worth noting that solution 5 should only really be used if none of the other solutions work for your use case. If you do decide you need solution 5 then I recommend using this pre-made hook use-did-mount.
Source (With more detail): Using componentDidMount in react hooks
There's no componentDidMount on functional components, but React Hooks provide a way you can emulate the behavior by using the useEffect hook.
Pass an empty array as the second argument to useEffect() to run only the callback on mount only.
Please read the documentation on useEffect.
function ComponentDidMount() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log('componentDidMount');
}, []);
return (
<div>
<p>componentDidMount: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<div>
<ComponentDidMount />
</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>
useEffect() hook allows us to achieve the functionality of componentDidMount, componentDidUpdate componentWillUnMount functionalities.
Different syntaxes of useEffect() allows to achieve each of the above methods.
i) componentDidMount
useEffect(() => {
//code here
}, []);
ii) componentDidUpdate
useEffect(() => {
//code here
}, [x,y,z]);
//where x,y,z are state variables on whose update, this method should get triggered
iii) componentDidUnmount
useEffect(() => {
//code here
return function() {
//code to be run during unmount phase
}
}, []);
You can check the official react site for more info. Official React Page on Hooks
Although accepted answer works, it is not recommended. When you have more than one state and you use it with useEffect, it will give you warning about adding it to dependency array or not using it at all.
It sometimes causes the problem which might give you unpredictable output. So I suggest that you take a little effort to rewrite your function as class. There are very little changes, and you can have some components as class and some as function. You're not obligated to use only one convention.
Take this for example
function App() {
const [appointments, setAppointments] = useState([]);
const [aptId, setAptId] = useState(1);
useEffect(() => {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = aptId;
console.log(aptId);
setAptId(aptId + 1);
return item;
})
setAppointments(apts);
});
}, []);
return(...);
}
and
class App extends Component {
constructor() {
super();
this.state = {
appointments: [],
aptId: 1,
}
}
componentDidMount() {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = this.state.aptId;
this.setState({aptId: this.state.aptId + 1});
console.log(this.state.aptId);
return item;
});
this.setState({appointments: apts});
});
}
render(...);
}
This is only for example. so lets not talk about best practices or potential issues with the code. Both of this has same logic but the later only works as expected. You might get componentDidMount functionality with useEffect running for this time, but as your app grows, there are chances that you MAY face some issues. So, rather than rewriting at that phase, it's better to do this at early stage.
Besides, OOP is not that bad, if Procedure-Oriented Programming was enough, we would never have had Object-Oriented Programming. It's painful sometimes, but better (technically. personal issues aside).
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Please visit this official docs. Very easy to understand the latest way.
https://reactjs.org/docs/hooks-effect.html
Info about async functions inside the hook:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
useLayoutEffect hook is the best alternative to ComponentDidMount in React Hooks.
useLayoutEffect hook executes before Rendering UI and useEffect hook executes after rendering UI. Use it depend on your needs.
Sample Code:
import { useLayoutEffect, useEffect } from "react";
export default function App() {
useEffect(() => {
console.log("useEffect Statements");
}, []);
useLayoutEffect(() => {
console.log("useLayoutEffect Statements");
}, []);
return (
<div>
<h1>Hello Guys</h1>
</div>
);
}
Yes, there is a way to SIMULATE a componentDidMount in a React functional component
DISCLAIMER: The real problem here is that you need to change from "component life cycle mindset" to a "mindset of useEffect"
A React component is still a javascript function, so, if you want something to be executed BEFORE some other thing you must simply need to execute it first from top to bottom, if you think about it a function it's still a funtion like for example:
const myFunction = () => console.log('a')
const mySecondFunction = () => console.log('b)
mySecondFunction()
myFunction()
/* Result:
'b'
'a'
*/
That is really simple isn't it?
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be mapped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
And in this specific case it's true. But what happens if I do something like that:
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be maped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
This "cleverFunction" we are defining it's not the same in every re-render of the component.
This lead to some nasty bugs and, in some cases to unnecessary re-renders of components or infinite re-render loops.
The real problem with that is that a React functional component is a function that "executes itself" several times depending on your state thanks to the useEffect hook (among others).
In short useEffect it's a hook designed specifically to synchronize your data with whatever you are seeing on the screen. If your data changes, your useEffect hook needs to be aware of that, always. That includes your methods, for that it's the array dependencies.
Leaving that undefined leaves you open to hard-to-find bugs.
Because of that it's important to know how this work, and what you can do to get what you want in the "react" way.
const initialState = {
count: 0,
step: 1,
done: false
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'doSomething') {
if(state.done === true) return state;
return { ...state, count: state.count + state.step, state.done:true };
} else if (action.type === 'step') {
return { ...state, step: action.step };
} else {
throw new Error();
}
}
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
dispatch({ type: 'doSomething' });
}, [dispatch]);
return (
<div>
<h1>Hi!</h1>
</div>
)}
useReducer's dispatch method it's static so it means it will be the same method no matter the amount of times your component is re-rendered. So if you want to execute something just once and you want it rigth after the component is mounted, you can do something like the above example. This is a declarative way of do it right.
Source: The Complete Guide to useEffect - By Dan Abramov
That being said if you like to experiment with things and want to know how to do it "the imperative wat" you can use a useRef() with a counter or a boolean to check if that ref stores a defined reference or not, this is an imperative approach and it's recommended to avoid it if you're not familiar with what happen with react behind curtains.
That is because useRef() is a hook that saves the argument passed to it regardless of the amount of renders (I am keeping it simple because it's not the focus of the problem here, you can read this amazing article about useRef ). So it's the best approach to known when the first render of the component happened.
I leave an example showing 3 different ways of synchronise an "outside" effect (like an external function) with the "inner" component state.
You can run this snippet right here to see the logs and understand when these 3 functions are executed.
const { useRef, useState, useEffect, useCallback } = React
// External functions outside react component (like a data fetch)
function renderOnce(count) {
console.log(`renderOnce: I executed ${count} times because my default state is: undefined by default!`);
}
function renderOnFirstReRender(count) {
console.log(`renderOnUpdate: I executed just ${count} times!`);
}
function renderOnEveryUpdate(count) {
console.log(`renderOnEveryUpdate: I executed ${count ? count + 1 : 1} times!`);
}
const MyComponent = () => {
const [count, setCount] = useState(undefined);
const mounted = useRef(0);
// useCallback is used just to avoid warnings in console.log
const renderOnEveryUpdateCallBack = useCallback(count => {
renderOnEveryUpdate(count);
}, []);
if (mounted.current === 0) {
renderOnce(count);
}
if (mounted.current === 1) renderOnFirstReRender(count);
useEffect(() => {
mounted.current = mounted.current + 1;
renderOnEveryUpdateCallBack(count);
}, [count, renderOnEveryUpdateCallBack]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(prevState => (prevState ? prevState + 1 : 1))}>TouchMe</button>
</div>
);
};
class App extends React.Component {
render() {
return (
<div>
<h1>hI!</h1>
</div>
);
}
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<MyComponent/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If you execute it you will see something like this:
You want to use useEffect(), which, depending on how you use the function, can act just like componentDidMount().
Eg. you could use a custom loaded state property which is initially set to false, and switch it to true on render, and only fire the effect when this value changes.
Documentation
the exact equivalent hook for componentDidMount() is
useEffect(()=>{},[]);
hope this helpful :)
Across my app, there are some UX logic that needs to be shared. They are triggered by events, so I wrote 2 custom hooks. Let's call one useRefWithCalc. The other hook is a more standard useEventListener, similar to this one.
useRefWithCalc calls the native useRef, has some internal handlers for UX, then calls useEventListener to attach those handlers. useRefWithCalc returns the ref created within, so another component can use this hook, and get the returned ref to attach to elements.
This has worked for me when the ref isn't attached to conditionally rendered elements.
The component looks something like this. Please take note on the 2 test logs.
const useEventListener = (event, listener, ref) => {\
...
useEffect(() => {
...
console.log("1. ref is: ", ref.current); // test logging 1.
ref.current.addEventListener(event, listener);
return () => {
ref.current.removeEventListener(event, listener);
}
}, [event, listener, ref]);
}
const useRefWithCalc = (value) => {
const ref = useRef(null);
...
const calc = () => {
// some calculations
}
...
useEventListener(event, calc, ref)
return [ref, result]
}
// works perfectly
const WorkingElement = (props) => {
const [ref, result] = useRefWithCalc(props.value);
...
return <B ref={ref} />
}
// doesn't work consistently
const ConditionalElement = (props) => {
const [state, setState] = useState(false);
const [ref, result] = useRefWithCalc(props.value)
useEffect(()=>{
if (ref && ref.current) {
ref.current.focus();
console.log("2. ref is: ", ref.current); // test logging 2
}
}, [ref])
...
return state ? <A> : <B ref={ref} />
}
The <WorkingElement /> works just as expected. The ref gets attached, and handles events with no problem.
However, in the <ConditionalElement />, when B is mounted, sometimes times test logging 1 won't fire. Test logging 2 always fires, and the ref gets the focus correctly. But this update is not passed into useEventListener
Once <B /> gets 1 subsequent update (e.g. when user inputs something), both logs will fire correctly, and the event listner gets attached correctly, and it work just as <WorkingElement />
Sorry for not posting the exact code. I feel like my approach is convoluted and might be wrong.
In React when a ref changes, it doesn't trigger a component update, and useEffects are not triggered.
I suggest to put your ref inside a state so that effects are triggered when the ref changes :
const [ref, setRef] = useState(undefined)
return (
<B ref={setRef}/>
)
Everytime I work with addEventListener(), and also want to access some state inside useEffect, I get the same issue. I can't add the state as dependency, because then I would create multiple event listeners each time the state changes.
I almost everytime find myself stuck with the "React Hook useEffect has a missing dependency" warning.
Let's say I have a component that needs to change it state on window.onClick() and on window.onDoubleClick(). If the state is true, click should change it to false, and if the state is false, double click should change it to true.
So here's what I whould write:
import React, { useState, useEffect } from 'react';
export default function someComponent() {
const [toggle, setToggle] = useState(false);
useEffect(() => {
window.addEventListener('click', (event) => {
if (toggle) setToggle(false)
})
window.addEventListener('dblclick', (event) => {
if (!toggle) setToggle(true)
})
}, [])
return (
<p>The toggle state is {toggle.toString()}</p>
);
}
This code works, but I get the missing dependency warning. I can't add toggle to the dependency array, because then it will add another event listener each time the toggle state changes.
What am I doing wrong here? how should I fix this?
Edit: Maybe this example wasn't too good, but it's the simplest I could think of. But, this issue is also for when I create other event listeners, that have to be on the windows object, like scroll. I know I can use return to remove the event listener everytime, but for events like scroll it makes it much slower. It doesn't make sense to me that I have to remove and add it everytime, when I just don't need it to fire again.
With react you don't have to use the window element in this case. Not even a useEffect.
By using the useEffect hook you are telling react to do something after render (depending on the dependency array). In this case changing state is not necessary immediately after rendering the page, only when the user interacts with the element.
Adding click events through the useEffect is probably not needed most of the time and and doing it like the example below will probably save you time and a headache and maybe even performance (correct me if i'm wrong).
I would personally do it like this.
import React, { useState } from 'react';
export default function someComponent() {
const [toggle, setToggle] = useState(false);
return (
<p
onClick={() => setToggle(false)}
onDoubleClick={() => setToggle(true)}
>
The toggle state is {toggle.toString()}
</p>
);
}
You could also call functions from the element like so
const [toggle, setToggle] = useState(false);
const handleClick = () => {
if (toggle) {
setToggle(false);
}
};
const handleDoubleClick = () => {
if (!toggle) {
setToggle(true);
}
};
return (
<p
onClick={() => handleClick()}
onDoubleClick={() => handleDoubleClick()}
>
The toggle state is {toggle.toString()}
</p>
);
CodeSandbox example
You can add a clean-up function to the useEffect hook to remove old listeners. This way you can pass toggle into the dependency array and you won't have stacking event listeners.
https://reactjs.org/docs/hooks-effect.html
useEffect(() => {
const handleClick = () => toggle ? setToggle(false) : setToggle(true);
window.addEventListener('click', handleClick);
window.addEventListener('dblclick', handleClick);
return () => {
window.removeEventListener('click', handleClick);
window.removeEventListener('dblclick', handleClick);
}
}, [toggle]);
I can't add the state as dependency, because then I would create multiple event listeners each time the state changes.
There is a way around this, and that is to return a cleanup function from the useEffect callback. I would encourage you to read the linked section of the docs, then the below solution would become much clearer:
useEffect(() => {
const handleClick = () => {
setToggle(!toggle)
}
window.addEventListener('click', handleClick)
return () => {
window.removeEventListener('click', handleClick)
}
}, [toggle])
with the above solution, each time toggle is updated, the cleanup function is called, which removes the current event listener before running the effect again.
Also note that you can provide a callback function to setToggle, which receives the current value of toggle and returns the new value. With this approach you wouldn't need to pass toggle as a dependency to useEffect:
useEffect(() => {
const handleClick = () => {
setToggle(currentValue => !currentValue)
}
window.addEventListener("click", handleClick)
return () => {
window.removeEventListener("click", handleClick)
}
}, [])
What follows is my first attempt at the component, which is supposed to re-query data when the filters prop changes. Additionaly, whenever such a change is detected it should wrap back to the first page (first occurence of useEffect). Thirdly, the user should be able to go to the next page manually (nextPage callback).
function Fetcher(filters) {
const [page, setPage] = React.useState(0);
const nextPage = React.useCallback(() => {
setPage((p) => p + 1);
}, []);
React.useEffect(() => {
setPage(0);
}, [filters]);
React.useEffect(() => {
externalRequest(filters, page);
}, [filters, page]);
return <button onClick={nextPage}></button>;
}
Unfortunately this will naturally won't work properly, as resetting the page in the first effect hook will be performed asynchronously and picked up belatedly: if the user sits on page 1 and filters change, externalRequest will be fired twice in the following manner:
externalRequest(1, newFilters)
externalRequest(0, newFilters)
What I'd consider to be a workaround is either to store the page as a mutable reference as follows:
function Fetcher(filters) {
const page = React.useRef(0);
const nextPage = React.useCallback(() => {
page.current = page.current + 1;
}, []);
React.useEffect(() => {
page.current = 0;
}, [filters]);
React.useEffect(() => {
externalRequest(filters, page);
}, [filters, page.current]);
return <button onClick={nextPage}></button>;
}
or lift it up and pass from the outside as the prop (which is not ideal, as I want to avoid leaking this dependency outside). The issue with the former solution (using useRef) is that the component won't re-render when nextPage is called and the re-cycle would need to be forced. The problem with the latter is that it puts the 'burden' of managing the page dependency outside. It solves it though.
My question is whether there exists a way to structure the Fetcher so that we get the best of both worlds i.e. page state internally in the component and it remaining part of the state, rather than a reference? I'm sorry if this example seems a bit contrived, but I feel this pattern occurs quite often and I'd be really grateful for some input on the matter!
You can resolve this issue by removing the filters from the second effect dependency
Why? since you are making sure the page will trigger re-render for effect, so, by setting the filter in both effects, that means trigger twice, one for filter and then page also updates so this second render will be trigger.
You can check the demo here.
function Fetcher(props) {
const [page, setPage] = useState(0);
const nextPage = useCallback(() => {
setPage((p) => p + 1);
}, []);
useEffect(() => {
setPage(0);
}, [props.filters]);
useEffect(() => {
console.log(props.filters, page);
}, [page]);
return <button onClick={nextPage}>Next Page</button>;
}
There's an important note: each dependency will trigger re-render when its update, so that we put it inside the dependency array, but you need to make sure about the rendering tree too.
Also, you have another solution, by saving the old filter and checking the new value if it's equal old one or not.
Also, you can use the re-structure component and build function and call what you needed based on conditions...
And I don't recommend to useRef in this case... it's not a solution for a normal state, and I think the first solution is fair.
UPDATE 1: (for taking care about filters change):
function Fetcher(props) {
const [page, setPage] = useState(0);
const [oldFilters, setOldFilters] = useState(props.filters);
const nextPage = useCallback(() => {
setPage((p) => p + 1);
}, []);
useEffect(() => {
setPage(0);
if(JSON.stringify(props.filters) !== JSON.stringify(oldFilters)){
setOldFilters(props.filters);
}
}, [props.filters]);
useEffect(() => {
console.log(props.filters, page);
}, [page, oldFilters]);
return <button onClick={nextPage}>Next Page</button>;
}
Also, the setPage(() => () => 0); its a good option (like Pasato answer bellow) for handling this cae by update referance each time to trigger re-render.
Thank you Anees, that was so simple..
There's one edge caveat that's not addressed by your solution and it's the case in which filters change, while still on page 0 - in this case page dependent effect won't fire. I've ultimately opted in for the following:
function Fetcher(props) {
const [page, setPage] = useState(() => () => 0);
const nextPage = useCallback(() => {
setPage((p) => () => p() + 1);
}, []);
// do not run on the mount, but only when props change
// as otherwise the initial request is fired twice
useUpdate(() => {
setPage(() => () => 0);
}, [props.filters]);
useEffect(() => {
const actualPage = page();
}, [page]);
return <button onClick={nextPage}>Next Page</button>;
}
Laziness of the state is irrelevant in this case - I just "wrap it" in the function so that two page instances are referentially different, even if they return the same number. Also, page resetter doesn't run upon mount so that we don't query twice.
I am trying to apply a parallax effect to an .svg image by using useRef() to grab bubblesRef and translateY() onScroll.
The parallax works but when I navigate to the next page I receive error "TypeError: Cannot read property 'style' of null". I think it is because the addEventListener is still listening and trying to useRef() on bubblesRef while navigating to the next page. So I added the cleanup function in useEffect() but that doesn't seem to fix it.
Any help is appreciated. Thanks!
p.s. If anyone can share their approach to a simple parallax effect like this that would be great too. This is the only approach I've figured that won't rerender everything else on the page onScroll.
const HomePage = () => {
const [loadedPosts, setLoadedPosts] = useState([]);
const { sendRequest } = useHttpClient();
console.log("loadedPosts homePage", loadedPosts);
const bubblesRef = useRef();
useEffect(() => {
if (loadedPosts.length === 0) {
//api call
}
}, [sendRequest, loadedPosts]);
useEffect(() => {
const parallax = () => {
let scrolledValue = window.scrollY / 3.5;
bubblesRef.current.style.transform = `translateY(
-${scrolledValue + "px"}
)`;
console.log("scrolling...", scrolledValue);
};
window.addEventListener("scroll", parallax);
return () => window.removeEventListener("scroll", parallax);
}, []);
return (
<HomePageContainer>
<Header />
<SectionOne posts={loadedPosts} />
<SectionTwo />
<BubbleBlobs className="bubbleBlobs" ref={bubblesRef} />
<BlobTop className="backBlobBottom" preserveAspectRatio="none" />
</HomePageContainer>
);
};
export default HomePage;
You definitely need the cleanup function any time you add a listener to the window, or the handler (and thus the component instance itself) will live on forever. However, since React runs those cleanup hooks asynchronously, it might not happen until after other window events. The value of the ref is set to null when the component unmounts, so you need to check that it is still defined before using the value.
useEffect(() => {
const handler = () => {
if (ref.current) {
// perform update
}
}
window.addEventListener('scroll', handler)
return () => window.removeEventListener('scroll', handler)
}, [])
When you call useEffect, your reference has not been instantiated, so the error message appears, in your useEffect dependency array, insert your ref and before running the code in useEffect, make sure your current reference is defined.