I am trying to make a useEffect fire every time the query params of my URL changes, this is the format of the URL:
https://localhost:3000/main/persons?results=1
In my code I have the following:
const location = useLocation();
useEffect(() => {
console.log('Location update')
}, [location]);
However, my problem is that the useEffect is only run when the location.pathname changes, and not when the query parameters of the URL changes (?results=1). I have also tried the following logic: [location.pathname, location.search] but with no luck.
Do anyone know how I can solve this?
Try doing this:
const { pathName } = useLocation();
useEffect(() => {
console.log('Location update')
}, [pathName]);
Similarly for location.search
For URL params use useParams in your hook
let { results} = useParams();
useEffect(() => {
console.log('Params updated',results)
}, [results]);
Related
I expected to get the url with category=business,but the web automatically reset my state to the url that dosent have the category.I dont know the reason behind
let {id}=useParams()
const [newsurl,setNewsurl]=useState(()=>{
const initialstate="https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
return initialstate;})
//console.log(id);
const [articles, setActicles] = useState([]);
useEffect( ()=>{
if(id === 2)
console.log("condition")
setNewsurl("https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee")},[])
useEffect(() => {
const getArticles = async () => {
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state ha
s been updated.
}, [newsurl])
//return "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";}
return (<><Newsnavbar />{articles?.map(({title,description,url,urlToImage,publishedAt,source})=>(
<NewsItem
title={title}
desciption={description}
url={url}
urlToImage={urlToImage}
publishedAt={publishedAt}
source={source.name} />
)) } </>
)
one more things is that when i save the code the page will change to have category but when i refresh it ,it change back to the inital state.Same case when typing the url with no id.May i know how to fix this and the reason behind?
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
You can instead, for example, use a useEffect hook that is dependant on the relevant state in-order to see that the state value actually gets updates as anticipated.
Example:
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state has been updated.
}, [newsurl])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "newsurl" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.
setState is an async operation so in the first render both your useEffetcs run when your url is equal to the default value you pass to the useState hook. in the next render your url is changed but the second useEffect is not running anymore because you passed an empty array as it's dependency so it runs just once.
you can rewrite your code like the snippet below to solve the problem.
const [articles, setActicles] = useState([]);
const Id = props.id;
useEffect(() => {
const getArticles = async () => {
const newsurl =
Id === 2
? "https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
: "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);
I am using NextJs, and I have a component that uses useDebounceHook.
The component looks like this.
import { useRouter } from 'next/router';
function SearchComponent() {
const router = useRouter();
const [searchResults, setSearchResults] = useState([]);
const [searchTerm, setSearchTerm] = useState<string>('');
const debouncedSearchTerm: string = useDebounce<string>(searchTerm, 250);
const handleSearchChange = (event) => {
setSearchTerm(event.target.value);
};
useEffect(() => {
const newPath = `/search?q=${debouncedSearchTerm}`;
router.push(newPath, undefined, { shallow: true });
}, [debouncedSearchTerm]);
// eslint complains above that router is missing and if we add, the function runs infintely.
useEffect(() => {
fetchQueryResults()
.then((data) => {
setSearchResults(data)l
})
}, [router.query.q]);
return (
<InputField
placeholder="Search"
value={searchTerm}
onChange={handleSearchChange}
/>
{renderSearchResults()}
)
}
// useDebounceHook reference: https://usehooks.com/useDebounce/
The component listens to the search change event and immediately updates the value as it needs to be visible on the screen textbox. However, it debounces the value for fetching search results.
And we want to do the fetch from the URL route as we want it to be bookmarkable. Hence we push the query param to the URL once the debounce value changes rather than directly fetching.
Here the problem is Eslint complains that we are missing router from the dependency array. If we add it, it goes into an infinite loop.
How to solve this issue? Is it ok if we skip adding the router to the dependency array?
One option is to only do the router push if it changes q, i.e.
useEffect(() => {
const newPath = `/search?q=${debouncedSearchTerm}`;
if (debouncedSearchTerm !== router.query.q) {
router.push(newPath, undefined, { shallow: true });
}
}, [router, debouncedSearchTerm]);
However I think it's also fine to omit router as a dependency here (and suppress the lint), since you don't need to re-navigate if the router changes (generally when dependencies change you want to rerun the effect, but here router doesn't seem necessary).
One thing to consider is if the page changes from some other reason (ex. they click a link on the page which takes them to "search?q=blah"), what should happen? Should the effect run taking them back to search?q=searchTerm? This is essentially the case being handled here.
I did this temporary fix until the larger issue is resolved from nextjs:
useEffect(() => {
const newPath = `/search?q=${debouncedSearchTerm}`;
router.push(newPath, undefined, { shallow: true });
// This is a hack to prevent the redirect infinite loop problem
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [undefined]);
Here I have a state which contains of client info
const { info, setInfo, onPostInfoChanges } = useContext(ClientInfos);
Below it, is a function that post or put new infos to server
const onSubmitHandler = async (model) => {
const emptyInputs = invalidInputs(model);
if (emptyInputs.length) {
emptyInputs.forEach((key) =>
setError((prev) => ({ ...prev, [key]: true }))
);
} else {
const response = await Save(model);
setAllClient((currentArray) => [response, ...currentArray]);
closeModal();
setError({});
}
};
I wanna execute onSubmitHandler when info state changed. onPostInfoChanges is a function that handle input change event.
I think you can just use useEffect hook with info as dependency.
It should look something like this,
useEffect(() => {
// Your code that you want to run whenever the info stat is changed
...
onSubmitHandler(model)
...
},[info])
I think this is what you want.
You can use the useEffect hook with info as a dependency to execute the onSubmitHandler when the info state is changed.
useEffect(() => {
onSubmitHandler(model)
},[info])
Now, this useEffect hook identifies the change in the info state and execute the onSubmitHandler.
Refer to this doc for more information about React useEffect hook
I have a problem with my custom express route. Every time I change page via my custom route component I get a react no-op error :
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Here is my custom route component/handler :
import React from 'react';
import { Route } from 'react-router-dom';
import { AuthenticationContext } from '../../contexts/authentication/context';
import Unauthorized from '../Unauthorized';
import axios from 'axios';
const ProtectedRoute = ({ component: Component, redirect: Redirect, contextProvider: ContextProvider, path, ...routeProps }) => {
const { authenticationState: {isAuthenticated, isFetchingTheUser, currentUser} } = React.useContext(AuthenticationContext)
const [authorized, setAuthorized] = React.useState(['Admin']);
const [isFetchingAuthorizations, setIsFetchingAuthorizations] = React.useState(false);
React.useEffect(() => {
console.log("useEffect from protected route")
setIsFetchingAuthorizations(true);
axios.get(`${global.REST_API_ADDR}/api/pages/${encodeURIComponent(path)}`)
.then((response) => {
setAuthorized(response.data.authorized);
setIsFetchingAuthorizations(false);
})
.catch((error) => {
setIsFetchingAuthorizations(false);
console.log("Protected route use Effect error : ", error);
})
}, [path])
return (
<Route {...routeProps}
render={ props => {
if(isFetchingTheUser || isFetchingAuthorizations) return <div>Chargement...</div>
if(isAuthenticated && authorized.includes(currentUser.rank)){
return ContextProvider ? <ContextProvider><Component {...props} /></ContextProvider> : <Component {...props} />
}else if(isAuthenticated && !authorized.includes(currentUser.rank)) {
return <Unauthorized {...props} />;
}
else{
return <Redirect {...props}/>;
}
}}/>
);
};
export default ProtectedRoute;
If delete the part with useEffect() I no longer receive a warning in the console, nevertheless I really need this hook to check that the user has permission to access this page
Can someone please enlighten me ?
Thanks in advance
EDIT 1 :
I tried to create an empty component and accessed it via my same custom route (ProtectedRoute) and there is no warning, this "empty component" doesnt have any useEffect in it and it seems to be the problem in the others components... So I got this warning when I try to access a component with useEffect in it...
EDIT 2 :
With further testing I can affirm with certitude that the problem is coming from the useEffect in my ProtectedRoute component, if i manually set the "authorized" array and the "isFetchingAuthorizations" it works fine. The problem seems to come from the setter in the http request (if I comment only the setters it works fine too...)
EDIT 3 : I added an useEffect in my empty component to fetch all my users and display it, and it throw a warning like all others components. I guess the problem comes from having an useEffect in the component...
EDIT 4 : I added a console.log in my empty component's useEffect, it seems that the useEffect is triggered even tho the component is not returned ! It might be the problem here !
EDIT 5 : The problems seems to come from the fact 'authorized' state is keeped between each routing, so when the user ask for a new route the 'autorized' state is filled with the previous page authorizations array... don't know how to fix it, i'm trying to empty it after the route has been served... if aynone have some tips
I have one suggest for your case like this:
useEffect(() => {
let didCancel = false; // to trigger handle clean up
const fetchData = async () => {
try {
const result = await .get(`${global.REST_API_ADDR}/api/pages/${encodeURIComponent(path)}`);
if (!didCancel) {
setAuthorized(result...);
setIsFetchingAuthorizations(result...);
}
} catch (error) {
if (!didCancel) {
setIsFetchingAuthorizations(result...);
}
}
};
fetchData()
return {
didCancel = true; // clean up useEffect
}
})
Hope help you !
So here is what I think, you clean up your http requests before you unmount. I wrote something that might work for you, try it out and let me know. Also here is a helpful article on this https://medium.com/#selvaganesh93/how-to-clean-up-subscriptions-in-react-components-using-abortcontroller-72335f19b6f7
useEffect(() => {
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
setIsFetchingAuthorizations(true);
const fetchData = axios.get(`${global.REST_API_ADDR}/api/pages/${encodeURIComponent(path)}`)
.then((response) => {
setAuthorized(response.data.authorized);
setIsFetchingAuthorizations(false);
})
.catch((error) => {
setIsFetchingAuthorizations(false);
console.log("Protected route use Effect error : ", error);
})
fetchData();
return () => {
source.cancel();
};
}, [path]);
I'm using a componentDidUpdate function
componentDidUpdate(prevProps){
if(prevProps.value !== this.props.users){
ipcRenderer.send('userList:store',this.props.users);
}
to this
const users = useSelector(state => state.reddit.users)
useEffect(() => {
console.log('users changed')
console.log({users})
}, [users]);
but it I get the message 'users changed' when I start the app. But the user state HAS NOT changed at all
Yep, that's how useEffect works. It runs after every render by default. If you supply an array as a second parameter, it will run on the first render, but then skip subsequent renders if the specified values have not changed. There is no built in way to skip the first render, since that's a pretty rare case.
If you need the code to have no effect on the very first render, you're going to need to do some extra work. You can use useRef to create a mutable variable, and change it to indicate once the first render is complete. For example:
const isFirstRender = useRef(true);
const users = useSelector(state => state.reddit.users);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
console.log('users changed')
console.log({users})
}
}, [users]);
If you find yourself doing this a lot, you could create a custom hook so you can reuse it easier. Something like this:
const useUpdateEffect = (callback, dependencies) => {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
return callback();
}
}, dependencies);
}
// to be used like:
const users = useSelector(state => state.reddit.users);
useUpdateEffect(() => {
console.log('users changed')
console.log({users})
}, [users]);
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate, and
componentWillUnmount combined.
As from: Using the Effect Hook
This, it will be invoked as the component is painted in your DOM, which is likely to be closer to componentDidMount.