How to use Context API with useParams - javascript

I have an api with details of a farm and I want to show them in different components using an id. Like the data is used in many components and I want to use Context API to display the data in the components.
So here is the code that fetches the data
let navigate = useNavigate();
const [farm, setFarm] = useState('');
const { username } = useParams();
const { farmId } = useParams();
const [isLoading, setIsLoading] = useState(true);
const user = React.useContext(UserContext);
useEffect(() => {
let isMounted = true;
axios.get(`/api/farm/${username}/${farmId}`).then(res => {
if (isMounted) {
if (res.data.status === 200) {
setFarm(res.data.farm);
setIsLoading(false);
console.warn(res.data.farm)
}
else if (res.data.status === 404) {
navigate('/');
toast.error(res.data.message, "error");
}
}
});
return () => {
isMounted = false
};
}, []);
The username is okay because I will use the user context to get the user details.
Now, how do Use this from a context into the components, because I have tried, and it is not working.

Well you can do it by adding another context for farm.
Your context file:
export const FarmContext = React.createContext();
export function FarmProvider({ children }) {
const [farm, setFarm] = useState();
return (
<FarmContext.Provider value={{farm, setFarm}}>
{ children }
</FarmContext.Provider>
)
}
And instead of this:
const [farm, setFarm] = useState();
Use this:
const { farm, setFarm } = useContext(FarmContext);

Include setIsLoading and setFarm into your dependency array. The dispatch function changes during a re-render. In your case, during development double renders components to detect issues with state management.
useEffect(() => {
....
}, [ setIsLoading, setFarm]);

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 call a React Hook from a child component to refresh results

I'm trying to refresh the results on the page, but the refresh button is in a child component to where my React Hook is originally called.
export const ParentComponent = ({
}) => {
const infoINeed = useSelector(getInfoINeed);
const { error, isLoading, data } = useMyAwesomeHook(infoINeed.name);
return (
<div>
<Header/>
<Body className={classes.body}>
<div>Hello Stack overflow</div>
<Body>
</div>
);
};
My Awesome hook looks like this
export const useDogCounts = (name: string | undefined) => {
const { data: token, error: authError } = useAuthHook();
const [error, setError] = useState<Error | null>(null);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
const fetchMyData = async () => {
const request = myRequest(name);
try {
setLoading(true);
const counts = callMyFunction()
setMyData(counts);
setLoading(false);
} catch (requestError) {
if (requestError === null) {
setError(requestError);
} else {
throw requestError;
}
setLoading(false);
}
};
fetchMyData();
}, [name, token]);
return {
data: dogCounts,
error,
isLoading,
};
};
then in my <Header/> component, I have a refresh button that I want to call the hook.
import React, { FC } from 'react';
import { Button } from '#material-ui/core';
export const Header: FC<HeaderProps> = ({}) => {
return (
<Page.Header className={classes.headerWrapper}>
<Page.Title>Dog Counts</Page.Title>
<Button
onClick={() => {}} // functionality to go here
>
Refresh
</Button>
</Header>
);
};
I've tried a couple approaches, including passing a variable into the useDogCount hook called refresh, which the Header component changes in the state in order to trigger the useEffect hook in my main hook. It seemed a bit messy to do it this way and introduce a new variable to keep track of.
I also have implemented something like this elsewhere a different time where I did not use a useEffect hook inside my custom hook, and instead passed the Promise back to the required place to refresh it. However, I need the useEffect hook here to check for updating name or token.
You can return the function used to fetch the data from your custom hook :
export const ParentComponent = () => {
const infoINeed = useSelector(getInfoINeed);
const { error, isLoading, data, fetchData } = useMyAwesomeHook(infoINeed.name);
return (
<div>
<Header onClickRefresh={fetchData}/>
<Body className={classes.body}>
<div>Hello Stack overflow</div>
<Body>
</div>
);
};
export const useDogCounts = (name: string | undefined) => {
const { data: token, error: authError } = useAuthHook();
const [error, setError] = useState<Error | null>(null);
const [isLoading, setLoading] = useState(true);
const fetchData = useCallback(async () => {
... // code to fetch the data
}, [name, token]);
useEffect(fetchData, [fetchData]);
return {
data: dogCounts,
fetchData,
error,
isLoading,
};
};
export const Header: FC<HeaderProps> = ({onClickRefresh}) => {
return (
<Page.Header className={classes.headerWrapper}>
<Page.Title>Dog Counts</Page.Title>
<Button onClick={onClickRefresh}>
Refresh
</Button>
</Header>
);
};
Right now there is no connection between your hook and either of components in terms of firing the request for the data. What I would suggest is to add a function to your hook that is going to call your api and return that function from the hook
export const useDogCounts = (name: string | undefined) => {
const { data: token, error: authError } = useAuthHook();
const [error, setError] = useState<Error | null>(null);
const [isLoading, setLoading] = useState(true);
const callAnApi = async () => {
// ... body of the useEffect
}
useEffect(() => {
const fetchMyData = async () => {
const request = myRequest(name);
try {
setLoading(true);
const counts = callMyFunction()
setMyData(counts);
setLoading(false);
} catch (requestError) {
if (requestError === null) {
setError(requestError);
} else {
throw requestError;
}
setLoading(false);
}
};
fetchMyData();
}, [name, token]);
return {
data: dogCounts,
error,
isLoading,
};
};
then in your ParentComponent you can destructure it as
const { error, isLoading, data, callAnApi } = useMyAwesomeHook(infoINeed.name);
and pass it to Header component as prop where you just use it as
<Button
onClick={callAnApiHandler}
>
Refresh
</Button>
Then you could call this new function inside your useEffect for further refactor

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 component not re-rendering on URL parameter change when using useEffect hook to fetch data

Here's the issue:
I have a component that is meant to be the same structure for ≈ 25 different items/pages. So, I do what anyone would when trying to use React, and I am passing dynamic URL parameters into my API request (pictured below).
const [{ items, isLoading, isError }] = useDataApi(
`http://localhost:5000/api/teams/topspending/${params.team}`,
[],
params.team);
This is simply using a useEffect component that has been separated for simplicity (pictured below).
const useDataApi = (initialUrl, initialData, effect) => {
console.log("start/top Data API");
const [items, setItems] = useState(initialData);
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
setUrl(initialUrl);
const abortCtrl = new AbortController();
const opts = { signal: abortCtrl.signal };
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
console.log("data loading");
try {
console.log(url, "this should be the new Url");
const result = await axios(url, opts);
setItems(result.data.data);
} catch (error) {
setIsError(true);
}
console.log("data loaded...");
setIsLoading(false);
};
fetchData();
return () => abortCtrl.abort();
}, [effect]);
return [{ items, isLoading, isError }];
};
export default useDataApi;
The task is pretty simple. Upon clicking on a simple navigation link in the navbar to change the URL from http://localhost:5000/api/teams/topspending/Team1 to http://localhost:5000/api/teams/topspending/Team2 I am wishing that the SAME component will re-render after fetching NEW data with the UPDATED URL.
I have tried many things... and I can get the component to update after every URL change, BUT the data fetched is still the OLD data!
(I am using React-Router to route the single component to this link)
Ok, I think there are 2 little issues in your code.
Inside the parent function
This is my main function that is going to use your custom hook. If you see, I don't use interpolation because it is not going to be detected by your custom hook. That is why your initialUrl variable (Your URL) in your custom hook never change.
const App = () => {
const [id, setId] = React.useState(1);
const response = useDataApi(
'https://jsonplaceholder.typicode.com/posts/' + id,
[],
id,
);
return (
<>
<div>My id {id}</div>
<button onClick={() => setId(id + 1)}>Click Me!</button>
</>
);
};
Inside the custom hook
It seems to me that you are misunderstanding the setState function provided by react.
Remember that every time you call the setState function is not synchronous. I mean, if you use setUrl(initialUrl), then in the next line of code your state variable url will not necessarily have the values already updated. To know more about it, you can read this: https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous
I would suggest using another variable to call the correct URL and change the variable names of your custom hook. I added some comments to your code //Note:
export const useDataApi = (initialUrl, initialData, effect) => {
console.log("start/top Data API", effect, initialUrl);
const [items, setItems] = useState(initialData);
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
// Note: This not sync function
setUrl(initialUrl);
const abortCtrl = new AbortController();
const opts = { signal: abortCtrl.signal };
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
console.log("data loading");
try {
console.log(url, "this should be the new Url");
// Note: I changed this url variable of your state, to use your initialUrl variable. (this initialUrl parameter should have your UPDATED URL)
const result = await axios(initialUrl, opts);
setItems(result.data);
} catch (error) {
setIsError(true);
}
console.log("data loaded...");
setIsLoading(false);
};
fetchData();
return () => abortCtrl.abort();
}, [effect]);
return [{ items, isLoading, isError }];
};
I Hope, this can help you!.
setState is asynchronous, so there's no guarantee as to when it will be affected before the next render. There's multiple ways to rewrite your code to work more predictably, but with the examples you've provided the easiest solution is to remove the url state altogether and just use initalUrl in your call to axios.
This isn't great.
So another option would be to keep your url state and add a second useEffect that watches url.
eg.
const useDataApi = (initialUrl, initialData, effect) => {
console.log("start/top Data API");
const [items, setItems] = useState(initialData);
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
setUrl(initialUrl);
}, [effect]);
useEffect(() => {
const abortCtrl = new AbortController();
const opts = { signal: abortCtrl.signal };
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
console.log("data loading");
try {
console.log(url, "this should be the new Url");
const result = await axios(url, opts);
setItems(result.data.data);
} catch (error) {
setIsError(true);
}
console.log("data loaded...");
setIsLoading(false);
};
fetchData();
return () => abortCtrl.abort();
}, [url])
return [{ items, isLoading, isError }];
};
export default useDataApi;
Still not great, but does what you're trying to do?

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