React - How to properly load data with pagination - javascript

On start, I'm loading the data in useEffect like this:
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(12);
useEffect(() => {
const loadVehicles = async (dealershipId, id) => {
try {
const data = await getUserRequests(dealershipId, id, {
offset: (page - 1) * perPage,
limit: perPage,
});
setRequests(data.results);
} catch (err) {}
};
const loadUser = async (userId) => {
try {
const user = await getUserById(userId);
setSelectedUser(user);
} catch (err) {}
};
const loadData = async () => {
setIsLoading(true);
await loadVehicles(dealershipId, userId);
await loadUser(userId);
setIsLoading(false);
};
loadData();
}, [dealershipId, page, perPage, userId]);
Here, in the dependecy array I have "page" and "perPage", which are for pagination for vehicles. When I go to, for example, second page:
const changePageHandler = (pageNumber) => {
setPage(pageNumber);
};
page is updating, and then useEffect is calling. But the problem is that I just want to call loadVehicles() and not the loadUser() function.
And when I put loadVehicles(dealershipId, userId) function outside of the useEffect, and then call it after setPage(pageNumber), it's not getting the right page, because page is not updated yet.
How can I call only loadVehicles() with the correct page?

You should use another "useEffect" hook for handling page changes. Also it is better to define your functions outside your hooks so you can use them in other places as well.
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(12);
const loadVehicles = async (dealershipId, id) => {
try {
const data = await getUserRequests(dealershipId, id, {
offset: (page - 1) * perPage,
limit: perPage,
});
setRequests(data.results);
} catch (err) {}
};
const loadUser = async (userId) => {
try {
const user = await getUserById(userId);
setSelectedUser(user);
} catch (err) {}
};
const loadData = async () => {
setIsLoading(true);
await loadVehicles(dealershipId, userId);
await loadUser(userId);
setIsLoading(false);
};
useEffect(async() => {
await loadData();
}, [dealershipId, userId]);
useEffect(async() => {
setIsLoading(true);
await loadVehicles(dealershipId, userId);
setIsLoading(false);
}, [page, perPage]);
I am pretty sure you will understand the concept. The code above is just a hint.

Related

get data from async function to another function React JS

I have problem with async function. I need track.user in another function but my func getTracks() async. I don't have clue how can i get this.
const Player = ({trackUrl, index, cover, id}) => {
const [track, setTrack] = useState({})
const [user, setUser] = useState({})
useEffect(() => {
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
getTracks();
getUser() // track.user undefined
}, [])
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
}
I would declare both functions at the beginning of the component (you can later optimise them with useCallback but it's not that important in this phase).
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
I would then call an async function inside the useEffect hook. There are a couple of ways of doing it: you can either declare an async function in the useEffect hook and call it immediately, or you can call an anonymous async function. I prefer the latter for brevity, so here it is:
useEffect(() => {
(async () => {
await getTracks();
getUser();
})();
}, []);
Now when you call getUser you should be sure that getTracks has already set the track variable.
Here is the complete component:
const Player = ({trackUrl, index, cover, id}) => {
const [track, setTrack] = useState({})
const [user, setUser] = useState({})
const getTracks = async () => {
await httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
})
}
const getUser = async() => {
await httpClient.get(`/profile/${track.user}/`)
.then((response) => {
setUser(response.data);
})
}
useEffect(() => {
(async () => {
await getTracks();
getUser();
})();
}, []);
}
EDIT 07/18/22
Following Noel's comments and linked sandbox, I figured out that my answer wasn't working. The reason why it wasn't working is that the track variable was't available right after the getTrack() hook execution: it would have been available on the subsequent render.
My solution is to add a second useEffect hook that's executed every time the track variable changes. I have created two solutions with jsonplaceholder endpoints, one (see here) which preserves the most of the original solution but adds complexity, and another one (here) which simplifies a lot the code by decoupling the two methods from the setTrack and setUser hooks.
I'll paste here the simpler one, adapted to the OP requests.
export default function Player({ trackUrl, index, cover, id }) {
const [track, setTrack] = useState({});
const [user, setUser] = useState({});
const getTracks = async () => {
// only return the value of the call
return await httpClient.get(`/track/${id}`);
};
const getUser = async (track) => {
// take track as a parameter and call the endpoint
console.log(track, track.id, 'test');
return await httpClient.get(`profile/${track.user}`);
};
useEffect(() => {
(async () => {
const trackResult = await getTracks();
// we call setTrack outside of `getTracks`
setTrack(trackResult);
})();
}, []);
useEffect(() => {
(async () => {
if (track && Object.entries(track).length > 0) {
// we only call `getUser` if we are sure that track has at least one entry
const userResult = await getUser(track);
console.log(userResult);
setUser(userResult);
}
})();
}, [track]);
return (
<div className="App">{user && user.id ? user.id : "Not computed"}</div>
);
}
You can move the second request to the then block of the dependent first request,i.e., getTracks.
Also, you shouldn't mix then and await.
useEffect(() => {
const getTracks = () => {
httpClient.get(`/track/${id}`)
.then((response) => {
setTrack(response.data);
httpClient.get(`/profile/${response.data.user}/`)
.then((response) => {
setUser(response.data);
})
})
}
getTracks();
}, [])
You shouldn't be mixing thens with async/await. You should be using another useEffect that watches out for changes in the track state and then calls getUser with that new data.
function Player(props) {
const { trackUrl, index, cover, id } = props;
const [ track, setTrack ] = useState({});
const [ user, setUser ] = useState({});
async function getTracks(endpoint) {
const response = await httpClient.get(endpoint);
const data = await response.json();
setTrack(data);
}
async function getUser(endpoint) {
const response = await httpClient.get(endpoint);
const data = await response.json();
setUser(data);
}
useEffect(() => {
if (id) getTracks(`/track/${id}`);
}, []);
useEffect(() => {
if (track.user) getUser(`/profile/${track.user}`);
}, [track]);
}

Length is showing 0 even after using async and await keyword

I Dont know what is going on but even after using async and await keyword still the length is showing zero. Thanks in advance.
const commercial_shoots = [];
let test;
React.useEffect(() => {
async function fetchData() {
const app_ref = ref(storage, "Home/");
await listAll(app_ref)
.then((res) => {
res.items.forEach((itemRef) => {
getDownloadURL(itemRef).then((url) => {
commercial_shoots.push({ img: url });
});
});
})
.catch((error) => {
console.log(error);
});
}
fetchData();
}, []);
return <div>{commercial_shoots.length}</div>;
};
React only re-renders the component when state or props updates. Here, you are only updating a local variable. So, even when it updates, the UI does not reflect the change.
The solution would be to use commercialShoots as a state in the component.
const CommercialShoots = () => {
const [commercialShoots, setCommercialShoots] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const app_ref = ref(storage, "Home/");
const res = await listAll(app_ref);
const downloadUrls = await Promise.all(res.items.map(itemRef) => getDownloadURL(itemRef));
const mappedUrlsToImg = downloadUrls.map((url) => ({ img: url });
setCommercialShoots(mappedUrlsToImg);
} catch (error) {
console.error(error);
}
}
fetchData();
}, []);
return <div>{commercial_shoots.length}</div>;
};
NOTE - Since we are using async / await extensively, I took the liberty of updating the .then() to async / await syntax.

how to call to api once a hour with react

i would like to know how can i get the api on first time reload the page and then call to api again once a hour to update my UI because that api update once a hour by default
here is my code
const [news, setNews] = useState([])
useEffect(() => {
setInterval(() => {
(async () => {
tryƏ {
const res = await fetch(`https://newsapi.org/v2/top-headlines?country=""""&apiKey=""""""""`)
const data = await res.json()
setNews(data)
console.log("yes")
} catch (error) {
console.log(error)
}
})()
}, 36000000);
}, [])
with that code i can't get result on first time page reload, only after a hour...
Move your API call to separate function. Call it on page load and on timeout:
let callApi = async () => {
try {
const res = await fetch(url)
const data = await res.json()
setNews(data)
} catch (error) {
console.log(error)
}
};
useEffect(() => {
callApi();
setInterval(callApi, 1000 * 60 * 60)
});
You can create another function and call from interval and outside both.
const [news, setNews] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(
`https://newsapi.org/v2/top-headlines?country=""""&apiKey=""""""""`
);
const data = await res.json();
setNews(data);
console.log("yes");
} catch (error) {
console.log(error);
}
};
fetchData();
const interval = setInterval(() => {
fetchData();
}, 36000000);
return () => clearInterval(interval);
}, []);
You can try this code:
const [news, setNews] = useState([]);
const [timer, setTimer] = useState(null);
const APIResponse = async (url) => {
try {
const response = await fetch(url);
const data = await response.json();
setNews(data);
} catch (e) {
console.error(e);
}
};
useEffect(() => {
APIResponse("https://newsapi.org/v2/top-headlines?country=""""&apiKey=""""""""");
setTimer(
setInterval(async () => {
APIResponse("https://newsapi.org/v2/top-headlines?country=""""&apiKey=""""""""");
}, 5000)
);
return () => clearInterval(timer);
}, []);

How to pass state from one useEffect to another useEffect on intial page load?

I have a component which displays products for a category. CategoryId is taken from subscribe method which is formed by pubsub pattern so I am waiting sub function to finish and passing to my API but it is not working on intial load of the page?
import { subscribe } from "./pubsub";
const Test = () => {
const [productId, setProductId] = useState({});
const [response, setResponse] = useState([]);
React.useEffect(() => {
function sub() {
return new Promise((resolve, reject) => {
subscribe("product-message", (data) => {
// console.log("Got some message", data);
// setProductId(data.productId);
resolve(data.productId);
});
});
}
async function fetchData() {
let message = await sub();
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${message.productId}` // Here I couldn't get the async data from above useEffect
);
console.log(response);
setResponse(response);
}
fetchData();
}, []);
return <div>{response.title}</div>; //It is not printing in intial load
};
export default Test;
So here is my sandbox link: https://codesandbox.io/s/happy-forest-to9pz?file=/src/test.jsx
If you only need the response, you do not need to store productId in state and then use it in another useEffeect to fetch data. You can simply implement the logic in one useEffec. Also note that you need to use the json response from fetch call so you need to use it like
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
).then(res => res.json());
or
let res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
)
let response = await res.json();
Complete function will look like
const Test = () => {
const [response, setResponse] = useState([]);
React.useEffect(() => {
async function fetchData(productId) {
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
).then(res => res.json());
console.log(response);
setResponse(response);
}
console.log("Api calls");
subscribe("product-message", (data) => {
// console.log("Got some message", data);
fetchData(data.productId);
});
}, []);
return <div>{response.title}</div>;
};
export default Test;
However if you need productId in your application, you can go via a multiple useEffect approach like you have tried in your sandbox. Also make sure that you are using thee fetch call correctly and also make sure to not make the API call wheen productId is not available
const Test = () => {
const [productId, setProductId] = useState({});
const [response, setResponse] = useState([]);
React.useEffect(() => {
console.log("Api calls");
subscribe("product-message", (data) => {
// console.log("Got some message", data);
setProductId(data.productId);
});
}, []);
React.useEffect(() => {
async function fetchData() {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}` // Here I couldn't get the async data from above useEffect
);
const response = await res.json();
console.log(response);
setResponse(response);
}
if(productId) {
fetchData();
}
}, [productId]);
return <div>{response.title}</div>;
};
export default Test;
Working Sandbox

Converting useEffect function to componentDidMount

I have a problem in which some files are using new useEffect and some are using the older componentDidMount, I am more familiar with useEffect and I have the following async function below:
const [user, setUser] = useContext(UserContext);
useEffect(() => {
const fetchUser = async () => {
const user = await Auth.currentAuthenticatedUser();
getUserData(user.username).then((user) => {
setUser(user);
});
};
fetchUser();
}, []);
The above function is what I am trying to replicate below:
this.state = {
user: null,
};
async fetchUser() {
try {
const user = await Auth.currentAuthenticatedUser();
const res = await getUserData(user.username);
this.setState({ user: res });
console.log(user);
} catch (error) {
console.error(error, 'Error: cant retrieve user data');
}
}
async componentDidMount() {
await this.fetchUser();
}
However, I cannot retrieve the same outcome with the componentDidMount function, how could I do this?

Categories