useRef and useEffect not triggering on dependency change problem - javascript

Hi I have a useEffect question/bug in my code. I would like to have a custom input field to upload an image. So I use useRef and pass it to the ProfileImageOption component. After the upload component the image's get url (avatarUrlPut) is updated and useEffect should call the getPhoto() function and re-render the component. But it is not happenning.
I have found this blogpost related to my issue. But blogpost is not providing a solution idea around the bug. https://coder.earth/post/react-hooks-oops-part-3-an-effect-does-not-run-again-when-its-dependencies-change
I have read from the official documentation that when useRef is used, useEffect will not be able to re-render the component because useRef doesn't notify React when it's current property changes. I suspect a similar issue here but I didn't understand it completely. Would someone help? Thank you.
function EmployeeHeader(props) {
const {avatarUrlPut, avatarUrlPost} = props
const dispatch = useDispatch()
const userID = useSelector((state) => state.auth.fetchUserSuccess.id)
useEffect(() => {
// After uploading the image avatarUrlPut is changed and it is on the dependency array of useEffect
dispatch(getPhoto(avatarUrlPut))
console.log("Am I called after an upload?") // No!
}, [dispatch, avatarUrlPut])
const [uploadedImage, setUploadedImage] = useState({})
const uploadImage = useRef()
const openCameraModal = useRef()
return (
<div>
<ColorizedRect width={'100%'} height={'175px'}>
<Image
source={avatarUrlPut}
className={'profile_image'}
spinnerwidth={'140px'}
spinnerheight={'175px'}
/>
<ProfileImageOption
onClick={() => uploadImage.current.click()}
icon={'upload'}
top={'80px'}
></ProfileImageOption>
<input
id="uploadAvatar"
ref={uploadImage}
type="file"
accept=".jpg,.jpeg,.png,"
style={{display: 'none'}}
onChange={(e) =>
dispatch(uploadPhoto(e.target, avatarUrlPost, setUploadedImage, handleShow))
}
/>
<a href={avatarUrlPut} download="Avatar.jpg">
<ProfileImageOption icon={'download'} top={'125px'}></ProfileImageOption>
</a>
</ColorizedRect>
</div>
)
}
export default EmployeeHeader

Related

I stored api data to a state and tried to pass it to another component as prop but its behaving differently there

I stored api data to a state and tried to pass it to another component as prop but its behaving differently there.
I'm trying to pass the animeList data to the AnimeCard component but when i start typing in the input it show undefined or previous search result as many times as i press something in the console and on submit it how two array of the value.
const SearchBar = () => {
const [search, setSearch] = useState('')
const [animeList, setAnimeList] = useState()
const animeSearch = async (query) => {
const temp = await fetch(`https://api.jikan.moe/v3/search/anime? q=${query}&order_by=title&sort=asc&limit=10`)
.then(res => res.json())
//console.log(temp.results) it works here
setAnimeList(temp.results)
}
const handleSearch = (e) => {
e.preventDefault()
animeSearch(search)
}
return (
<div className='center'>
<form onSubmit={handleSearch}>
<input placeholder='search' type='search' value={search} onChange={(e) => setSearch(e.target.value)} />
</form>
<AnimeCard animeList={animeList} />
</div>
)
}
export default SearchBar
const AnimeCard = ({animeList}) => {
//trouble here
console.log(animeList)
}
export default AnimeCard
enter image description here
Created an Codesandbox for you.
Here is it
You have many problem with your code.
Why it show undefined many time:
When component(SearchBar) render, your AnimeCard component will render. The time it render, you did not set anything to animeList state.
Why I show many times? React component will re-render if any state change. search is one of your state. everytime you type something. It change which caused re-render.
How to fix? Add a condition to it like what I did in line 31.
Why did it log many times when you get the result:
you have react's StricMode component wrapped in index.js
fix:
Delete <StrictMode> in index.js which cause double render.
you should console.log inside useEffect hook.
ps: advice: dont forget to pass default value for state
It was causing trouble because i didn't set my useState to array,
const [animeList, setAnimeList] = useState([])
that was it

Unable to style component using forwardRef

I am fairly new to react, so still getting my head around the component's lifecycle.
But the problem has left me scratching my head.
For instance, I do not understand why adding "setState(10);" causes style of the "Test" component to revert to it's default value yet the <div ref={ref2}>Hi</div> maintains it's style. (see imagebelow)
I am aware that "setState(10);" will cause a re-render but why is the style of the "Test" component being reverted?
Also, please ignore the "practical use" of calling setState(10) - I am aware it is pointless as it is never used, and I am aware that using "state" as a UseEffect dependency can solve this issue. But the main issue I have is understanding why the component's style reverts to it's default value.
import React, { useEffect, useState, useRef } from "react";
export default function App() {
const [state, setState] = useState();
let ref1 = useRef();
let ref2 = useRef();
useEffect(() => {
console.log("useEffect called ", ref1.current);
ref1.current.style.backgroundColor = "red";
ref2.current.style.backgroundColor = "green";
setState(10);
// }, [state]);
}, []);
const Test = React.forwardRef((props, ref1) => {
console.log("test called - rendering webpage", ref1.current);
return (
<div ref={ref1} {...props}>
HI from Test{" "}
</div>
);
});
return (
<div className="App">
<Test ref={ref1} />
<div ref={ref2}>Hi</div>
</div>
);
}
Console output
test called - rendering webpage undefined
useEffect called <div style="background-color: red;">HI </div>
test called - rendering webpage <div style="background-color: red;">HI </div>
The reason the style is disappearing is that you've defined your Test component inside your App component. That means that every time App renders, you'll define a new component type named Test. The text of that component is identical to the previous one, but it's a new type as far as react can tell, so react is forced to unmount the old one, and mount the new one. This wipes out any changes you made to the old one.
So at the very least, you need to move Test outside of App. That way, the component is just defined once, and will not remount on every render
export default App() {
// ...
}
const Test = React.forwardRef((props, ref1) => {
// ...
})
The above should fix the reset and let you experiment with refs, but i strongly recommend that you do not use refs to style your elements. Refs are an escape hatch that's sometimes needed, but the standard way to style a component is through the style prop. If you need to change the style, then you can have a state variable and let that control the style prop.
If you manually use javascript to set ref1.current.style.backgroundColor, react has no way to know that you did this, and so can't take those changes into account. In some circumstances, react may end up overwriting your changes, or may skip making changes that it doesn't realize it needs to do.
export default function App () {
const [colored, setColored] = useState(false);
useEffect(() => {
setColored(true);
}, [])
return (
<div className="App">
<Test style={colored ? { backgroundColor: "green" } : undefined} />
<div style={colored ? { backgroundColor: "red" } : undefined}>Hi</div>
</div>
);
}
// Don't really need forwardRef anymore, but i left it in
const Test = React.forwardRef((props, ref) => {
return (
<div ref={ref} {...props}>
HI from Test
</div>
);
});
import React, { useEffect, useState, useRef } from "react";
export default function App() {
const [state, setState] = useState();
let ref1 = useRef();
let ref2 = useRef();
useEffect(() => {
console.log("useEffect called ", ref1.current);
ref1.current.style.backgroundColor = "red";
ref2.current.style.backgroundColor = "green";
setState(10);
// }, [ref.current]);
}, [state]);
const Test = React.forwardRef((props, ref1) => {
console.log("test called - rendering webpage", ref1.current);
return (
<div ref={ref1} {...props}>
HI from Test{" "}
</div>
);
});
return (
<div className="App">
<Test ref={ref1} />
<div ref={ref2}>Hi</div>
</div>
);
}
The reason this is happening is once you update the state entire component gets rerendered. Your useEffect will run only once on componentDidMount hence the new ref that you get is not updated. To get rid of this you should use state as a dependency of the useEffect.

state neither updated from useState nor useEffect inside useState [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I was facing almost the same problem as in this question
The code is a bit too much so I have made a stripped down version of the problem. (please forgive me if I made a mistake in doing so)
Basically, I have a main component and a sub component
main component
import React {useState} from 'react'
import SubComponent from './subcomponent';
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
const newState = data
console.log(state)
setState(newState)
console.log(state)
}
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
export default Main;
sub component
import React {useState} from 'react'
const SubComponent = ({updateStateFunction}) => {
return (
<div>
<button
onClick={() => updateStateFunction("Something new")}
>
</button>
</div>
)
}
export default SubComponent;
both the console logs give null.
My attempts at a solution:
Since most stack overflow answers suggested that stateupdates using hooks is asynchronous I tried using setTimeout
I thought we could then use async-await but that was a wrong approach
I tried updating the state inside useEffect but the point is that nothing is being re redered. This is because the variable that is being updated is never a part of an output but rather sort a helper varibale for further operations.
The way I did this was using the solution in the above refereced question:
const Main = (props) => {
/*Debugging*/
let newState = null
useEffect(()=>{
console.log("useEffect called")
setState(newState)
}, [newState])
/*Debugging*/
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
newState = data
console.log(state)
setState(newState)
console.log(state)
}
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
I though since the useEffect hook is not even being executed hence I did not try the other two methods in the solution
Am I referencing the wrong type of problem or is this a common behaviour in react?
Happy to provide any more information if needed
Edit:
I have added console.log() because I have operations followed by the state change that uses the value of the state variable.
Using React dev tools I see that the state is updating and that too almost instantly. The problem is that the button press leads to a dialogue pop-up in the real code whose component uses the state for other logic, hence I get an error that that state is still null
I am not sure how let newState = null has anything to do with any of the answers in the quoted question, so to be clear, this is how one would directly apply the accepted answer:
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => { setState(data) }
useEffect(() => { console.log(state) }, [state])
return <SubComponent updateStateFunction = {updateStateFunction} />
}
However, there is no point of changing a state if it's not used for anything rendered on the screen - if Reacts does not detect any change in the return value, it might not commit any change to the DOM as a part of the optimizations, so it would probably not run any useEffects in that case.
I would recommend using React Dev Tools for checking the current state of a Component.
Also, console.log is basically the only side effect which does not need to be inside useEffect. If directly in the render function, it would be executed whenever the render function is called and would not depend on React committing changes to DOM...
Note that the first advice in my answer to the quoted question does not wrap console.log into useEffect - and to be clear again, this is how one would directly apply that advice:
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => { setState(data) }
console.log(state)
return <SubComponent updateStateFunction = {updateStateFunction} />
}
The setting of the state is asynchronous in react.
An asynchronous function will be executed in parallel (well, kind of) as other instructions. Rather than console.logging after setting state, you could console.log state before the return function to know the new output.
There is nothing wrong with your first implementation. No need to use useEffect here
import React {useState} from 'react'
import SubComponent from './subcomponent';
const Main = (props) => {
const [state, setState] = useState(null);
const updateStateFunction = (data) => {
setState(data)
}
// try console logging here
console.log(state)
return (
<div>
<SubComponent
updateStateFunction = {updateStateFunction}
/>
</div>
)
}
export default Main;
Think of it like this, whenever the state is set, your function which contains the state gets refreshed and re-run. You could console.log anywhere in the function to get the new state.
Use the react devtools to debug, even if the console.log() display null you see the state change in the devtools. The reason is that the state update is asynchronous.
If you still want to debug your react app using console.log() then call it just before the return statement or even in the jsx directly using curly braces.

How to prevent useCallback from triggering when using with useEffect (and comply with eslint-plugin-react-hooks)?

I have a use-case where a page have to call the same fetch function on first render and on button click.
The code is similar to the below (ref: https://stackblitz.com/edit/stackoverflow-question-bink-62951987?file=index.tsx):
import React, { FunctionComponent, useCallback, useEffect, useState } from 'react';
import { fetchBackend } from './fetchBackend';
const App: FunctionComponent = () => {
const [selected, setSelected] = useState<string>('a');
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<boolean>(false);
const [data, setData] = useState<string | undefined>(undefined);
const query = useCallback(async () => {
setLoading(true)
try {
const res = await fetchBackend(selected);
setData(res);
setError(false);
} catch (e) {
setError(true);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
query();
}, [query])
return (
<div>
<select onChange={e => setSelected(e.target.value)} value={selected}>
<option value="a">a</option>
<option value="b">b</option>
</select>
<div>
<button onClick={query}>Query</button>
</div>
<br />
{loading ? <div>Loading</div> : <div>{data}</div>}
{error && <div>Error</div>}
</div>
)
}
export default App;
The problem for me is the fetch function always triggers on any input changed because eslint-plugin-react-hooks forces me to declare all dependencies (ex: selected state) in the useCallback hook. And I have to use useCallback in order to use it with useEffect.
I am aware that I can put the function outside of the component and passes all the arguments (props, setLoading, setError, ..etc.) in order for this to work but I wonder whether it is possible to archive the same effect while keeping the fetch function inside the component and comply to eslint-plugin-react-hooks?
[UPDATED]
For anyone who is interested in viewing the working example. Here is the updated code derived from the accepted answer.
https://stackblitz.com/edit/stackoverflow-question-bink-62951987-vxqtwm?file=index.tsx
Add all of your dependecies to useCallback as usual, but don't make another function in useEffect:
useEffect(query, [])
For async callbacks (like query in your case), you'll need to use the old-styled promise way with .then, .catch and .finally callbacks in order to have a void function passed to useCallback, which is required by useEffect.
Another approach can be found on React's docs, but it's not recommended according to the docs.
After all, inline functions passed to useEffect are re-declared on each re-render anyways. With the first approach, you'll be passing new function only when the deps of query change. The warnings should go away, too. ;)
There are a few models to achieve something where you need to call a fetch function when a component mounts and on a click on a button/other. Here I bring to you another model where you achieve both by using hooks only and without calling the fetch function directly based on a button click. It'll also help you to satisfy eslint rules for hook deps array and be safe about infinite loop easily. Actually, this will leverage the power of effect hook called useEffect and other being useState. But in case you have multiple functions to fetch different data, then you can consider many options, like useReducer approach. Well, look at this project where I tried to achieve something similar to what you wanted.
https://codesandbox.io/s/fetch-data-in-react-hooks-23q1k?file=/src/App.js
Let's talk about the model a bit
export default function App() {
const [data, setDate] = React.useState("");
const [id, setId] = React.useState(1);
const [url, setUrl] = React.useState(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
fetch(url)
.then(response => response.json())
.then(json => {
setDate(json);
setIsLoading(false);
});
}, [url]);
return (
<div className="App">
<h1>Fetch data from API in React Hooks</h1>
<input value={id} type="number" onChange={e => setId(e.target.value)} />
<button
onClick={() => {
setIsLoading(true);
setUrl(`https://jsonplaceholder.typicode.com/todos/${id}`);
}}
>
GO & FETCH
</button>
{isLoading ? (
<p>Loading</p>
) : (
<pre>
<code>{JSON.stringify(data, null, 2)}</code>
</pre>
)}
</div>
);
}
Here I fetched data in first rendering using the initial link, and on each button click instead of calling any method I updated a state that exists in the deps array of effect hook, useEffect, so that useEffect runs again.
I think you can achieve the desired behavior easily as
useEffect(() => {
query();
}, [data]) // Only re-run the effect if data changes
For details, navigate to the end of this official docs page.

When and why to useEffect

This may seem like a weird question, but I do not really see many use cases for useEffect in React (I am currently working on a several thousand-lines React codebase, and never used it once), and I think that there may be something I do not fully grasp.
If you are writing a functional component, what difference does it make to put your "effect" code in a useEffect hook vs. simply executing it in the body of the functional component (which is also executed on every render) ?
A typical use case would be fetching data when mounting a component : I see two approaches to this, one with useEffect and one without :
// without useEffect
const MyComponent = () => {
[data, setData] = useState();
if (!data) fetchDataFromAPI().then(res => setData(res));
return(
{data ? <div>{data}</div> : <div>Loading...</div>}
)
}
// with useEffect
const MyComponent = () => {
[data, setData] = useState();
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
return(
{data ? <div>{data}</div> : <div>Loading...</div>}
)
}
Is there an advantage (performance-wise or other) to useEffect in such usecases ?
I. Cleanup
What if your component gets destroyed before the fetch is completed? You get an error.
useEffect gives you an easy way to cleanup in handler's return value.
II. Reactions to prop change.
What if you have a userId passed in a props that you use to fetch data. Without useEffect you'll have to duplicate userId in the state to be able to tell if it changed so that you can fetch the new data.
The thing is, useEffect is not executed on every render.
To see this more clearly, let's suppose that your component MyComponent is being rendered by a parent component (let's call it ParentComponent) and it receives a prop from that parent component that can change from a user action.
ParentComponent
const ParentComponent = () => {
const [ counter, setCounter ] = useState(0);
const onButtonClicked = () => setCounter(counter + 1);
return (
<>
<button onClick={onButtonClicked}>Click me!</button>
<MyComponent counter={counter} />
</>
);
}
And your MyComponent (slightly modified to read and use counter prop):
const MyComponent = ({ counter }) => {
[data, setData] = useState();
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
return(
<div>
<div>{counter}</div>
{data ? <div>{data}</div> : <div>Loading...</div>}
</div>
)
}
Now, when the component MyComponent is mounted for the first time, the fetch operation will be performed. If later the user clicks on the button and the counter is increased, the useEffect will not be executed (but the MyComponent function will be called in order to update due to counter having changed)!
If you don't use useEffect, when the user clicks on the button, the fetch operation will be executed again, since the counter prop has changed and the render method of MyComponent is executed.
useEffect is handling the side effect of the problem. useEffect is the combination of componentDidMount and componentDidUpdate. every initial render and whenever props updated it will be executed.
For an exmaple:
useEffect(() => {
fetchDataFromAPI().then(res => setData(res))
}, []);
Another example:
let's assume you have multiple state variables, the component will re-render for every state values change. But We may need to run useEffect in a specific scenario, rather than executing it for each state change.
function SimpleUseEffect() {
let [userCount, setUserCount] = useState(0);
let [simpleCount, setSimpleCount] = useState(0);
useEffect(() => {
alert("Component User Count Updated...");
}, [userCount]);
useEffect(() => {
alert("Component Simple Count Updated");
}, [simpleCount]);
return (
<div>
<b>User Count: {userCount}</b>
<b>Simple Count: {simpleCount}</b>
<input type="button" onClick={() => setUserCount(userCount + 1}} value="Add Employee" />
<input type="button" onClick={() => setSimpleCount(simpleCount + 1}} value="Update Simple Count" />
</div>
)
}
In the above code whenever your props request changed, fetchDataFromAPI executes and updated the response data. If you don't use useEffect, You need to automatically handle all type of side effects.
Making asynchronous API calls for data
Setting a subscription to an observable
Manually updating the DOM element
Updating global variables from inside a function
for more details see this blog https://medium.com/better-programming/https-medium-com-mayank-gupta-6-88-react-useeffect-hooks-in-action-2da971cfe83f

Categories