useEffect not re-rendering on route change - javascript

States
const [searchTerm, setSearchTerm] = useState("");
const [URL, SetURL] = useState<URL | String>();
const [searchResults, setSearchResults] = useState([]);
resetSearch Function
const resetSearch = () => {
setSearchTerm("");
setSearchResults([]);
inputRef.current.value = ""
}
ResetQuery Function
const resetSearchQueryOnRouteChange = () => {
SetURL(window.location.href)
if (window.location.href != URL) {
resetSearch();
}
}
useEffect
useEffect(() => {
resetSearchQueryOnRouteChange()
}, [window.location.href, URL])
The issue is that i need the the resetSearchQueryOnRouteChange() function to be run on every route render, but when i change routes the function is not called, but the function itself is fully functional.
How can i make the function run when i change route/url?

If you are not using 'react-router' each location change will trigger whole re-render of the page. So, this block of code wouldn't execute.
You need to start to use 'react-router' and you'll get access to location

Related

I tried this code i already passed dependency in useEffect but the api call again and again after 3/4 second

Below code i try to make get response from api and put in Movie Component. but the problem is that api hit again and again i don't why this happened.here screen shot of api call
const [loading, setloading] = useState(false);
const [movielist, setmovielist] = useState([]);
const [err, seterr] = useState("");
const fetchMoviesHandler = useCallback(async () => {
setloading(true);
try {
const reponse = await fetch("https://swapi.dev/api/films");
if (reponse.status != 200) {
seterr("something is wrong");
}
const data = await reponse.json();
const result = data.results;
setmovielist(result);
} catch (errr) {
}
});
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
return (
<div>
{movielist.map((movie) => {
return <Movie key={movie.episode_id} title={movie.title} />;
})}
</div>
);
};
This is returning a new instance of the function on every render:
const fetchMoviesHandler = useCallback(async () => {
// your function
});
Which will trigger the useEffect on every render, since this function is in its dependency array.
To tell useCallback to memoize the function and keep the same instance across multiple renders, it also needs a dependency array. For example:
const fetchMoviesHandler = useCallback(async () => {
// your function
}, [setloading, setmovielist, seterr]);
Or, at the very least, an empty dependency array:
const fetchMoviesHandler = useCallback(async () => {
// your function
}, []);
Which would essentially create one instance of the function and always use it for the life of the component.

How to use a custom-hook that returns a value, inside another custom Hook?

I am using React-native and in it, I have a custom Hook called useUser that gets the user's information from AWS Amplify using the Auth.getUserInfro method, and then gets part of the returned object and sets a state variable with it. I also have another Hook called useData hook that fetches some data based on the userId and sets it to a state variable.
useUser custom-Hook:
import React, { useState, useEffect } from "react";
import { Auth } from "aws-amplify";
const getUserInfo = async () => {
try {
const userInfo = await Auth.currentUserInfo();
const userId = userInfo?.attributes?.sub;
return userId;
} catch (e) {
console.log("Failed to get the AuthUserId", e);
}
};
const useUserId = () => {
const [id, setId] = useState("");
useEffect(() => {
getUserInfo().then((userId) => {
setId(userId);
});
}, []);
return id;
};
export default useUserId;
import useUserId from "./UseUserId";
// ...rest of the necessary imports
const fetchData = async (userId) = > { // code to fetch data from GraphQl}
const useData = () => {
const [data, setData] = useState();
useEffect(() => {
const userId = useUser();
fetchData(userId).then( // the rest of the code to set the state variable data.)
},[])
return data
}
When I try to do this I get an error telling me
*Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.*
I think the problem is that I am calling the Hook useUser inside of the use effect, but using it inside the function will cause the problem described here, and I can't use it outside the body of the fetchData since the useData itself is a hook, and it can be only used inside a functional component's or Hook's body. So I don't know how to find a way around this problem.
Correct, React hooks can only be called from React function components and other React hooks. The useEffect hook's callback isn't a React hook, it's a callback. According to the Rules of Hooks, don't call hooks inside loops, conditions, or nested functions.
I suggest refactoring the useData hook to consume the userId as an argument, to be used in the dependency array of the useEffect.
const fetchData = async (userId) => {
// code to fetch data from GraphQl
};
const useData = (userId) => {
const [data, setData] = useState();
useEffect(() => {
fetchData(userId)
.then((....) => {
// the rest of the code to set the state variable data.
});
}, [userId]);
return data;
};
Usage in Function component:
const userId = useUser();
const data = useData(userId);
If this is something that is commonly paired, abstract into a single hook:
const useGetUserData = () => {
const userId = useUser();
const data = useData(userId);
return data;
};
...
const data = useGetUserData();
Though you should probably just implement as a single hook as follows:
const useGetUserData = () => {
const [data, setData] = useState();
useEffect(() => {
getUserInfo()
.then(fetchData) // shortened (userId) => fetchData(userId)
.then((....) => {
// the rest of the code to set the state variable data.
setData(....);
});
}, []);
return data;
};
You can't call hook inside useEffect, Hook should be always inside componet body not inside inner function/hook body.
import useUserId from "./UseUserId";
// ...rest of the necessary imports
const fetchData = async (userId) => {
// code to fetch data from GraphQl}
};
const useData = () => {
const [data, setData] = useState();
const userId = useUser();
useEffect(() => {
if (userId) {
fetchData(userId).then(setData);
}
}, [userId]);
return data;
};

Maximum depth exceeded while using useEffect

I am trying to implement a simple search algorithm for my products CRUD.
The way I thought to do it was entering the input in a search bar, and the products that matched the search would appear instantly every time the user changes the input, without needing to hit a search button.
However, the way I tried to do it was like this:
function filterProducts (productName, productList) {
const queryProducts = productList.filter((prod)=> {
return prod.title === productName;
});
return queryProducts;
}
function HomePage () {
const [productList, setProductList] = useState([]);
const [popupTrigger, setPopupTrigger] = useState('');
const [productDeleteId, setProductDeleteId] = useState('');
const [queryString, setQueryString] = useState('');
let history = useHistory();
useEffect(() => {
if (queryString.trim() === "") {
Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
setProductList(data.data);
});
return;
}
const queryProducts = filterProducts(queryString, productList);
setProductList(queryProducts);
}, [queryString, productList]);
I know that productList changes every render, and that's probably why it isn't working. But I didn't figure out how can I solve the problem. I've seen other problems here and solutions with useReducer, but I none of them seemed to help me.
The error is this one below:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
what you are doing here is fetching a product list and filtering it based on the query string and using that filtered list to render the UI. So ideally your filteredList is just a derived state based on your queryString and productList. So you can remove the filterProducts from your useEffect and move it outside. So that it runs when ever there is a change in the state.
function filterProducts (productName = '', productList = []) {
return productName.trim().length > 0 ? productList.filter((prod)=> {
return prod.title === productName;
}); : productList
}
function HomePage () {
const [productList, setProductList] = useState([]);
const [queryString, setQueryString] = useState('');
useEffect(() => {
if (queryString.trim() === "") {
Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
setProductList(data.data);
});
}
}, [queryString]);
// query products is the derived state
const queryProducts = filterProducts(queryString, productList);
// Now instead of using productList to render something use the queryProducts
return (
{queryProducts.map(() => {
.....
})}
)
If you want the filterProducts to run only on change in queryString or productList then you can wrap it in useMemo
const queryProducts = React.useMemo(() => filterProducts(queryString, productList), [queryString, productList]);
When you use a setState function in a useEffect hook while having the state for that setState function as one of the useEffect hook's dependencies, you'll get this recursive effect where you end up infinitely re-rendering your component.
So, first of all we have to remove productList from the useEffect. Then, we can use a function to update your state instead of a stale update (like what you're doing in your example).
function filterProducts (productName, productList) {
const queryProducts = productList.filter((prod)=> {
return prod.title === productName;
});
return queryProducts;
}
function HomePage () {
const [productList, setProductList] = useState([]);
const [popupTrigger, setPopupTrigger] = useState('');
const [productDeleteId, setProductDeleteId] = useState('');
const [queryString, setQueryString] = useState('');
let history = useHistory();
useEffect(() => {
if (queryString.trim() === "") {
Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
setProductList(data.data);
});
return;
}
setProductList(prevProductList => {
return filterProducts(queryString, prevProductList)
});
}, [queryString]);
Now, you still get access to productList for your filter, but you won't have to include it in your dependencies, which should take care of the infinite re-rendering.
I recommend several code changes.
I would separate the state that immediately reflects the user input at all times from the state that represents the query that is send to the backend. And I would add a debounce between the two states. Something like this:
const [query, setQuery] = useState('');
const [userInput, setUserInput] = useState('');
useDebounce(userInput, setQuery, 750);
I would split up the raw data that was returned from the backend and the filtered data which is just derived from it
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
I would split up the useEffect and not mix different concerns all into one (there is no rule that you cannot have multiple useEffect)
useEffect(() => {
if (query.trim() === '') {
Axios
.get("http://localhost:3001/api/product/get-all")
.then((data) => { setProducts(data.data) });
}
}, [query]);
useEffect(
() => setFilteredProducts(filterProducts(userInput, products)),
[userInput, products]
);

React higher order function to return hook

Currently, I have a custom fetching data hook written in javascript and it works
import {useState, useEffect} from 'react';
const useApi = apiName => id => {
const [response, setResponse] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const fetching = async () => {
setLoading(true);
const data = await fetch(`/api/${apiName}${id ? `/${id}` : ""}`)
.then((x) => x.json())
.catch((error) => setError(error));
setResponse(data);
setLoading(false);
};
useEffect(() => {
fetching();
}, [id]);
return { response, loading, error };
};
Then I can use pass in what api I want to call to get the hook. For examples:
const useCustomer = useApi("customer")
const useHello = useApi("hello")
.....
const {response, loading, error} = useCustomer("id_1")
It works fine.
Then, I try to convert to typescript
const useApi = (apiName:string) => (id?:string) => {
const [response, setResponse] = useState({})
.......
}
and eslint complains that
React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function
I would like to know whats wrong with this approach, I know I can have something like:
const useApi = (apiName:string, id?:string) => {}
or disable the eslint(react-hooks/rules-of-hooks)
But just curious about whats the potential problems having higher order function of hook since it actually return the response .
Thanks
When you name you function with prefix hooks, eslint thinks of it as a custom hook according to the general convention. Now that implements useState in a nested function which is why it gives you an error
The best way to write the above code is to not use currying function but pass in the apiName as a param directly
const useApi = (apiName, id) => {
const [response, setResponse] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const fetching = async () => {
setLoading(true);
const data = await fetch(`/api/${apiName}${id ? `/${id}` : ""}`)
.then((x) => x.json())
.catch((error) => setError(error));
setResponse(data);
setLoading(false);
};
useEffect(() => {
fetching();
}, [id]);
return { response, loading, error };
};
and use it like
.....
const {response, loading, error} = useApi("customer","id_1");
P.S. Hooks are meant to be an alternative to HOC's and there is no point writing a hook if you use it as an HOC itself
There's a much easier way if you don't need the id variable to be in the hook. The reason why you get the warning is because your hooks are in your CB instead of your root function.
Correct Example:
const useApi = (apiName:string) => {
const [response, setResponse] = useState({});
return (id?: string) => {
.......
};
}

How use Local Storage in Functional Component React

How can I use LocalStorage in a functional component like this
I know how do this in a class component but can I solve this problem in this case?
ERROR: TypeError: repositories is not a function
export default function Main() {
const [newRepo, setNewRepo] = useState('');
const [repositories, setRepositories] = useState([]);
const [clearInput] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
repositories(localStorage.getItem('repositories'));
if (repositories) {
setRepositories(JSON.parse(repositories));
}
}, [repositories]);
useEffect((_, prevState) => {
if (prevState.repositories !== repositories) {
localStorage.setItem('repositories', JSON.stringify(repositories));
}
});
In your first useEffect, the repositories is your state which an array. Not a function.
Also, in your second useEffect you need to make correction to the way you access the prevState in hooks.
Fix for 1st useEffect
export default function Main() {
const [newRepo, setNewRepo] = useState('');
const [repositories, setRepositories] = useState([]);
const [clearInput] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const localRepoItems = localStorage.getItem('repositories');
if (localRepoItems) {
setRepositories(JSON.parse(localRepoItems));
}
}, []); // do not give the dependency as repositories as it will go to infinite loop
});
To obtain previous state in hooks, you can write a little custom hook:
Like this:
export const usePrevious = value => {
const ref = React.useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Usage in your component:
const prevRepositories = usePrevious(repositories);
useEffect(() => {
if (prevRepositories.length !== repositories.length) {
localStorage.setItem('repositories', JSON.stringify(repositories));
}
}, [repositories]);

Categories

Resources