Using React State Hook, call function after setting multiple states - reactjs

I'm trying to make a function that will reset multiple states and then make an API call, however I'm having trouble making the API call happen AFTER the three states have been set. My function looks like this:
const resetFilters = () => {
setYearFilter("");
setProgressFilter("");
setSearchFilter("");
callAPI();
};
I've tried using Promise.resolve().then(), and tried using async await, but it seems the useState setter function doesn't return a promise. Is there a way to make this all happen synchronously?

You could use useEffect to listen on changes and do each task sequentially
const resetFilters = () => {
setYearFilter("")
}
useEffect(() => {
setProgressFilter("")
}, [yearFilter])
useEffect(() => {
setSearchFilter("")
}, [progressFilter])
useEffect(() => {
callAPI()
}, [searchFilter])

Related

Using useEffect properly when making reqs to a server

I have a handleRating function which sets some state as so:
const handleRating = (value) => {
setCompanyClone({
...companyClone,
prevRating: [...companyClone.prevRating, { user, rating: value }]
});
setTimeout(() => {
handleClickOpen();
}, 600);
};
I think also have a function which patches a server with the new companyClone values as such:
const updateServer = async () => {
const res = await axios.put(
`http://localhost:3000/companies/${companyClone.id}`,
companyClone
);
console.log("RES", res.data);
};
my updateServer function gets called in a useEffect. But I only want the function to run after the state has been updated. I am seeing my res.data console.log when I load my page. Which i dont want to be making reqs to my server until the comapanyClone.prevRating array updates.
my useEffect :
useEffect(() => {
updateServer();
}, [companyClone.prevRating]);
how can I not run this function on pageload. but only when companyClone.prevRating updates?
For preventing function call on first render, you can use useRef hook, which persists data through rerender.
Note: useEffect does not provide the leverage to check the current updated data with the previous data like didComponentMount do, so used this way
Here is the code example.
https://codesandbox.io/s/strange-matan-k5i3c?file=/src/App.js

What's the best practice of calling API data from a function outside useEffect?

While working with react useEffect hook, most of the example I came across in case of calling api data in useEffect hook for initiate the component is, calling api directly inside useEffce hook.
For instance,
useEffect(() => {
async function(){
const res = await axios.get(`https://jsonplaceholder.typicode.com/${query}`);
setData(res.data)
}
}, []);
But what about fetch data outside the hook with a method ? For instance,
const getData = () => {
async function(){
const res = await axios.get(`https://jsonplaceholder.typicode.com/${query}`);
setData(res.data)
}
useEffect(() => {
getData(); // here eslint shows an warning "Promise returned from setData is ignored"
}, [])
is there any specific reason for avoiding second example. If not what's the proper way to call api call function in useEffect hook with proper cleanup ?
In React component file
useEffect(() => {
loadData(query).then(setData)
}, [query])
crate another service file to serve data from API
in service file
export const loadData = async query => {
const res = axios.get(`https://jsonplaceholder.typicode.com/${query}`);
return res.data;
// You can put this API call in try catch to handle API errors
};
Creating a separate function for calling an api is a perfect example of loading data in useEffect. You can give it parameters if you would have a paginated endpoint and call it multiple times or add polling to the page to load the data by interval. I can only see benefits by creating a function for this.
useEffect(() => { fetch("./product.JSON") .then(res => res.json()) .then(data => setProducts(data)) }, [])

Invalid hook call when trying to fetch data using useCallback

I'm trying to call useState inside an async function like:
const [searchParams, setSearchParams] = useState({});
const fetchData = () => useCallback(
() => {
if (!isEmpty(searchParams)) {
setIsLoading(true); // this is a state hook
fetchData(searchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
},
[],
);
There are two states I am trying to set inside this fetchData function (setIsLoading and setIds), but whenever this function is executed am getting the error:
Uncaught 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:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
What is this Rule of hooks I am breaking here?
Is there any way around to set these states from the function?
PS: I only used the useCallback hook here for calling this function with lodash/debounce
Edit: The function is called inside useEffect like:
const debouncedSearch = debounce(fetchSearchData, 1000); // Is this the right way to use debounce? I think this is created every render.
const handleFilter = (filterParams) => {
setSearchParams(filterParams);
};
useEffect(() => {
console.log('effect', searchParams); // {name: 'asd'}
debouncedSearch(searchParams); // Tried without passing arguments here as it is available in state.
// But new searchParams are not showing in the `fetchData`. so had to pass from here.
}, [searchParams]);
The hook rule you are breaking concerns useCallback because you are returning it as the result of your fetchData;
useCallback should be called on top level; not in a callback, like this:
const fetchData = useCallback(
() => {
if (!isEmpty(searchParams)) {
setIsLoading(true); // this is a state hook
fetchData(searchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
},
[],
);
The code you wrote is equivalent to
const fetchData = () => { return React.useCallback(...
or even
function fetchData() { return React.useCallback(...
To read more about why you can't do this, I highly recommend this blog post.
edit:
To use the debounced searchParams, you don't need to debounce the function that does the call, but rather debounce the searched value. (and you don't actually the fetchData function that calls React.useCallback at all, just use it directly in your useEffect)
I recommend using this useDebounce hook to debounce your search query
const [searchParams, setSearchParams] = React.useState('');
const debouncedSearchParams = useDebounce(searchParams, 300);// let's say you debounce using a delay of 300ms
React.useEffect(() => {
if (!isEmpty(debouncedSearchQuery)) {
setIsLoading(true); // this is a state hook
fetchData(debouncedSearchParams)
.then((ids) => {
setIds(ids); // Setting the id state here
}).catch(() => setIsLoading(false));
}
}, [debouncedSearchParams]); // only call this effect again if the debounced value changes

Running into an infinite loop when using useEffect() in react-native

I have recently begun using react native and I'm building this mobile application using Expo. I am using useEffect() to be able to call a function inside it. All I want is to wait for the reponse of the API to finish completely before I display the result (which in this case is the connectionName variable). I gave the useEffect() function walletsas a second parameter because I want the API fetched again whenever wallets changes, but I seem to be running in an infinite loop even though wallets hasn't changed. Any help is much appreciated.
export default function LinksScreen() {
const [wallets, setWallets] = React.useState([]);
const [count, setCount] = React.useState(0);
const [connectionName, setConnectionName] = React.useState("loading...");
React.useEffect(() => {
(async () => {
fetchConnections();
})();
}, [wallets]);
async function fetchConnections() {
const res = await fetch('https://api.streetcred.id/custodian/v1/api/' + walletID + '/connections', {
method: 'GET',
headers: {
Accept: 'application/json',
},
});
res.json().then(res => setWallets(res)).then(setConnectionName(wallets[0].name))
}
return (
<ScrollView style={styles.container} contentContainerStyle={styles.contentContainer}>
<OptionButton
icon="md-school"
label={connectionName}
onPress={() => WebBrowser.openBrowserAsync('https://docs.expo.io')}
/>
</ScrollView>
);
}
Your useEffect hook runs every time wallets changes, and it calls fetchConnections, which calls setWallets, which then triggers your useEffect hook because it changed wallets, and so on...
Pass an empty dependencies array to useEffect:
React.useEffect(() => {...}, [])
That will make it run only on mount.
If you still want to call fetchConnections whenever wallets changes, you shouldn't do it through an effect hook because you'll get the infinite loop you described. Instead, call fetchConnections manually whenever you call setWallets. You could make a function that does this for you:
const [wallets, setWallets] = useState([]);
const setWalletsAndFetch = (wallets) => {
setWallets(wallets);
fetchConnections(wallets);
}
You listen to a change and then as it changes you change it over and over without stopping
React.useEffect(() => {
(async () => {
fetchConnections();
})();
}, []);//remove wallets

How to cleanup an async funtion on componentDidUpdate using hooks

I've a component upon whose mounting, I'm subscribing to a socket(async task) and I've to unsubscribe(async) in upon unmounting. When the component updates, I've to unsubscribe to the old socket and subscribe to the new one. I'm not sure how to do this using react hooks. Attaching a sample codesandbox for reference.
https://codesandbox.io/s/clever-robinson-h0gq5
I'm not sure how you would like to implement the code within your sandbox but I think to call and async task within an effect you can do it like this:
useEffect(() => {
const timeout = async (msg, time) => {
await setTimeout(() => {
console.log(msg);
}, time);
}
timeout("subscribed to websocket", 2000);
return () => {
timeout("unsubscribed to websocket", 3000);
};
}, [match]);
To use an async await within useEffect you need to declare it within the function and call it from there.

Resources