I'm trying to wrap my head around this code. It essentially sends two params (page number + query) to a server, then gets the results and saves them in a state.
However, I don't understand the cancel, axios.CancelToken...etc part within useEffect. I would appreciate if it someone explains this thoroughly.
import axios from "axios";
import { useEffect, useState } from "react";
export default function useUserSearch(query, pageNumber) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [users, setUsers] = useState([]);
const [hasMore, setHasMore] = useState(false);
useEffect(() => {
setLoading(true);
setError(false);
let cancel;
axios
.get("https://someserver.com/users/search/", {
params: { q: query, page: pageNumber },
cancelToken: new axios.CancelToken((c) => (cancel = c)),
})
.then((res) => {
setUsers((prevState) => [...prevState, ...res.data.users]);
setHasMore(users.length >= res.data.total);
setLoading(false);
console.log(res.data);
})
.catch((e) => {
if (axios.isCancel(e)) return;
console.log(e);
setError(true);
});
return () => cancel();
}, [query, pageNumber, users]);
return { loading, error, users, hasMore };
}
The cancel token is used to cancel a request, but it is using deprecated API. Axios has newer API for that too.
As far as useEffect is concerned, the cancellation happens in the function which is returned by useEffect, that function runs when component unmounts and when any of its dependencies change.
Idea is for example if I have an ongoing query for keyword "Nick" and suddenly query was changed to "John", we are not interested in "Nick" query anymore so we can cancel it. Also if we don't cancel it and execute both queries "Nick" may arrive later, and overwrite result for "John".
Related
Pardon me if this is a silly question. Im a new react learner. Im trying using a create react app. I am using a custom hook for API handling only. Now I want the useEffect to run only when the data changes. Thats why I put it in dependency. But yet it keeps rendering for infinity. What is the problem? Or how should I handle this?
Thank you.
import { useCallback, useEffect, useState } from "react";
export const useAPI = (url, options) => {
const [data, setData] = useState([]);
const getDogCollection = useCallback(() => {
fetch(url, options)
.then((res) => res.json())
.then((result) => {
console.log(data, "----DI---", result);
setData(result);
});
}, []);
useEffect(() => {
getDogCollection();
}, [data]);
return data;
};
You'll just want url and options to be the dependencies, not data (because you set it in the effect!).
import { useEffect, useState } from "react";
export const useAPI = (url, options) => {
const [data, setData] = useState([]);
useEffect(() => {
fetch(url, options)
.then((res) => res.json())
.then(setData);
// TODO: add error handling...
}, [url, options]);
return data;
};
However, you'll probably just want to look at swr instead of writing this hook yourself.
It's because you've given data as one of the dependencies, but the function called in your useEffect updates data, so it then runs again.
You can change it to the length, like this, and it should work:
useEffect(() => {
getDogCollection();
}, [data.length]);
From what I understand useEffect hook runs last as a sideEffect. I am attempting to console log data.main.temp. I can understand that it doesn't know what that is yet, because it is fetching the data from the api in the useEffect hook which runs after.
How would I be able to access or console log data.main.temp AFTER the api call? (I feel like setTimout is the cheating way?)
import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";
export default function Weather() {
//State Management//
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
useEffect(() => {
const fetchData = async () => {
//get coordinates//
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
//fetch openWeather api//
await fetch(`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`)
.then((res) => res.json())
.then((result) => {
setData(result);
console.log(result);
});
};
fetchData();
}, [lat, long]);
//Examples of what I want, they run too early before api//
console.log(data.main.temp);
const Farenheit = data.main.temp * 1.8 + 32;
return (
<Card>
{typeof data.main != "undefined" ? (
<div className={`${styles.weatherContainer} ${styles.clouds}`}>
<h2>Weather</h2>
<p>{data.name}</p>
<p>{data.main.temp * 1.8 + 32} °F</p>
<p>{data.weather[0].description}</p>
<hr></hr>
<h2>Date</h2>
<p>{moment().format("dddd")}</p>
<p>{moment().format("LL")}</p>
</div>
) : (
<div></div>
)}
</Card>
);
}
You're right, the effect function is run after the first render which means you need to wait somehow until your api call is done. One common way to do so is to introduce another state flag which indicate whether the data is available or not.
Another thing which does not follow react good practices is the fact that you're effect function does more than one thing.
I also added trivial error handling and cleaned up mixed promises and async await
here is your refactored code
import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";
//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
export default function Weather() {
//State Management//
const [lat, setLat] = useState();
const [long, setLong] = useState();
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
}, []);
useEffect(() => {
const fetchData = async () => {
if (lat && long && key) {
try {
setLoading(true);
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`
);
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
}
};
fetchData();
}, [lat, long]);
if (error) {
return <div>some error occurred...</div>;
}
return (
<Card>
{loading || !data ? (
<div>loading...</div>
) : (
<div className={`${styles.weatherContainer} ${styles.clouds}`}>
<h2>Weather</h2>
<p>{data.name}</p>
<p>{data.main.temp * 1.8 + 32} °F</p>
<p>{data.weather[0].description}</p>
<hr></hr>
<h2>Date</h2>
<p>{moment().format("dddd")}</p>
<p>{moment().format("LL")}</p>
</div>
)}
</Card>
);
}
You can use another useEffect, which depends on changing the data state
useEfect(() => {
if (data) {
// do something with data
}
}, [data])
You can create a simple function and call it in your API call response and pass in the data directly from the api response, that way you will have access to the data immediately there's a response.
E.g
...
.then((result) => {
setData(result);
getDataValue(result) // this function will be called when the response comes in and you can use the value for anything
console.log(result);
});
METHOD 2:
You can use a useEffect hook to monitor changes in the data state, so that whenever there's an update on that state, you can use the value to do whatever you want. This is my less preferred option.
useEffect(() => {
//this hook will run whenever data changes, the initial value of data will however be what the initial value of the state is
console.log(data) //initial value = [] , next value => response from API
},[data])
I run into errors attempting to display data that has not been fetched yet, so.. I want to display loading until the data is fetched, but my 'setIsLoading' always returns false even when it's set to true.
Am I missing something really obvious? I'm fairly fresh to hooks.
const Pokedex = () => {
const [data, setData] = useState({});
const [isLoading, setIsLoading] = useState(false);
const pokemonId = 1;
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
const result = await axios(
`https://pokeapi.co/api/v2/pokemon/${pokemonId}`
);
setData(result.data);
};
fetchData();
setIsLoading(false);
}, [pokemonId]);
console.log("loading: ", isLoading);
You need to change isLoading state right after the fetch request completes.
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`https://pokeapi.co/api/v2/pokemon/${pokemonId}`
);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, []);
Don't rely on printing stuff outside the hooks because you won't have a real feedback. You can use render method (in the return, with JSX code) or inside the hooks.
Also, since state is reset when the component is refreshed, you can rely on initializating isLoading to true. That way you just need to change it once the request is fetched.
You can check a working demo here.
First of all, If you are calling async function, you are anonymously creating Promise<void>, so after that, good way to check if promise was successfull is to use then or catch methods from Promise API.
Example solution.
const fetchData = async () => {
setIsLoading(true);
const result = await axios(`https://pokeapi.co/api/v2/pokemon/${pokemonId}`);
setData(result.data);
};
fetchData().then(() => {
setIsLoading(false)
})
First, I think your loading state should be initialized to true.
const [isLoading, setIsLoading] = useState(true);
And then, set the loading state to false inside the async function
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`https://pokeapi.co/api/v2/pokemon/${pokemonId}`
);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [pokemonId]);
I am trying to use hooks and implement a custom hook for handling my data fetching after every update I send to the API.
My custom hook, however, doesn't fire on change like I want it too. Delete has to be clicked twice for it to rerender. Note: I removed some functions from this code as they don't pertain to the question.
import React, { useState, useEffect } from "react"
import {Trash} from 'react-bootstrap-icons'
import InlineEdit from 'react-ions/lib/components/InlineEdit'
function Board(){
const [render, setRender] = useState(false)
const [boards, setBoards] = useState([]);
const [isEditing, setEdit] = useState(false)
const [value, setValue] = useState("")
const[newValue, setNewValue] = useState("")
const [error, setError] = useState("")
function useAsyncHook(setState, trigger) {
const [result] = useState([]);
const [loading, setLoading] = useState("false");
useEffect(() => {
async function fetchList() {
try {
setLoading("true");
const response = await fetch(
`http://localhost:8080/api/boards`
);
const json = await response.json();
setState(json)
} catch (error) {
//console.log(error)
setLoading("null");
}
}
fetchList()
}, [trigger]);
return [result, loading];
}
useAsyncHook(setBoards, render)
const handleDelete = (id) => {
console.log("delete clicked")
setLoading(true);
fetch(`http://localhost:8080/api/boards/` + id, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
},
})
setRender (!render)
}
return(
<div>
<ul>
{boards.map(board => (
<li key={board.id}>
<InlineEdit value={board.size} isEditing={isEditing} changeCallback={(event)=>handleSave (event, board.id)} />
<Trash onClick={()=>handleDelete(board.id)}/>
</li>
</ul>
</div>
);
}
export default Board
OPTION 1:
Maybe you wanna have a hook that tells you when to fetch the board, right? For example:
const [auxToFetchBoard, setAuxToFetchBoard] = useState(false);
Then, in a useEffect you execute the function fetchBoard everytime that hook changes:
useEffect(fetchBoard, [auxToFetchBoard]);
Finally, in your handleDelete function, if your delete request returns correctly, you have to update auxToFetchBoard. Something like this:
const handleDelete = (id) => {
setIsLoading(true);
setError("");
fetch(yourURL, yourOptions)
.then(res => {
// check if response is correct and
setIsLoading(false);
setAuxToFetchBoard(!auxToFetchBoard);
})
.catch(() => {
setIsLoading(false);
setError("Error while deleting stuff");
});
};
Note: I changed the names of isLoading and setIsLoading because they are more explicit.
OPTION 2:
Instead of fetching the board again and again, you can update your board (in this case your code would be in 8th line inside the handleDeletefunction).
Hope it helps.
While I fetch data from API and set the response to a array using useEffect
it call the API repeat continuous.
let [product, setproduct] = useState([]);
async function fetchData() {
let response = await axios(
`api`
);
let user = await response.data;
setproduct(user);
console.log(product);
}
useEffect(() => {
fetchData();
});
From the docs,
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
You can provide the empty dependency array / [] as second argument to useEffect, it is same as componentDidMount which will executes only once in component life cycle.
useEffect(() => {
fetchData();
}, []); //This will run only once
Pass empty [] as an second argument to useEffect method. It will be called on initial render, like below.
useEffect(() => {
fetchData();
}, []);
i think the blow example will help you through fetch API .
import React , {useEffect} from "react";
import axios from "axios";
const Courses = ()=>{
useEffect(()=>{
getProducts()
})
const getProducts = async() => {
await axios.get('api/get_all_products')
.then(({data}) =>{
console.log("this is data from api ");
console.log('data' , data);
} )
console.log("data ehre ");
}
return(
<div>
<h2>Products data here</h2>
</div>
)
};
export default Courses;
let [product, setProduct] = useState([]);
// This function will be only called once
async function fetchData() {
let response = await axios("api");
let user = response.data;// Don't need await here
setProduct(user);
console.log(product);
}
useEffect(() => {
fetchData();
}, []);