Here I have a useState for posts of my blog webapp. I am getting the posts from the mongoose back end which works fine. But the second I set the posts variable I get an infinite loop of rerendering.
Here is the app.jsx:
import React, {useState} from 'react';
import './App.css';
import Nav from './Navbar/NavBar.jsx';
import Content from "./content/Content"
import { getPosts } from "./api/post";
function App() {
const idList = ["631a58c165b3a10ac71497e1", "631a58fa65b3a10ac71497e3"];
const [posts, setPosts] = useState([]);
setPosts(async() => await getPosts(idList));
return (
<div>
hello
</div>
);
}
export default App;
Here is the axios part:
import axios from "axios";
const getPost = (id) => {
new Promise((resolve, reject) => {
const url = "/posts/" + id
console.log(url)
axios.get(url)
.then(response => {
if (response.status === 200){
console.log("Succesfull call");
console.log(response.data);
resolve(response.data);
}
else if(response.status === 404){
reject(response);
}
})
.catch(err => {
console.log("Failed call");
reject(err);
})
});
};
const getPosts = async (idList) => {
var result = []
for(const id in idList){
try{
let post = await getPost(idList[id]);
result.push(post);
}catch(err){
console.log(err.message);
}
}
if (result.length === 0) throw {message: "No posts"};
else return result;
};
export {getPosts};
How can I run setPosts async so that the site wont refresh infinitely?
You cannot pass a function returning a promise into the state setter. It must return the new state value directly.
You'll want to use an effect hook instead
useEffect(() => {
getPosts(idList).then(setPosts);
}, []); // empty dependency array means run once on mount
Given idList appears constant, you should define it outside your component. Otherwise you'll need to work around its use as a dependency in the effect hook.
Your getPosts function falls prey to the explicit promise construction anti-pattern. Since Axios already returns a promise, you don't need to make a new one.
You can also use Promise.allSettled() to make your requests in parallel.
const getPost = async (id) => (await axios.get(`/posts/${id}`)).data;
const getPosts = async (idList) => {
const results = (await Promise.allSettled(idList.map(getPost)))
.filter(({ status }) => status === "fulfilled")
.map(({ value }) => value);
if (results.length === 0) {
throw new Error("No posts");
}
return results;
};
Related
I have used useEffects in my components to load data, moment the component mounts. But i am trying to optimize my code by avoiding any memmory leaks. To achieve this i am trying to use AbortController to cancel any request in any case if the component unmounts. Something like this
useEffect(() => {
let abortController;
(async () {
abortController = new AbortController();
let signal = abortController.signal;
// the signal is passed into the request(s) we want to abort using this controller
const { data } = await axios.get(
'https://random-data-api.com/api/company/random_company',
{ signal: signal }
);
setCompany(data);
})();
return () => abortController.abort();
}, []);
But i am finding it difficult to implement this because my axios request is in a service file which is called by a reducer in slice file.
Below is my useEffect of my Component.
// Component.js
import { bookDetails } from '../../features/user/userSlice'
//import reducer from my slice file
.
.
// code
useEffect(() => {
let mounted = true
if (mounted) {
dispatch(bookDetails(bookId))
}
return () => mounted = false
}, [])
Below is my reducer from my slice file which imports function from my service file.
// userSlice.js
import userService from "./userService";
export const bookDetails = createAsyncThunk(
"user/book",
async (id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await userService.bookDetails({ id, token });
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
Below is my function from my service file
// userService.js
const bookDetails = async ({ id, token }) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
const response = await axios.get(API_URL + `/book/${id}`, config);
return response.data;
};
I want to cancel this request in case my component unmounts from useEffect. Please Help. Thanks in advance.
Since you're using Redux Toolkit's createAsyncThunk, it sounds like you're looking for the "canceling while running" feature of createAsyncThunk. Perhaps something like the following:
export const bookDetails = createAsyncThunk(
"user/book",
async (id, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await userService.bookDetails({ id, token, signal: thunkAPI.signal });
} catch (error) {
return thunkAPI.rejectWithValue(getErrorMessage(error));
}
}
);
useEffect(() => {
const promise = dispatch(bookDetails(bookId));
return () => promise.abort();
}, [])
However, if all you're worried about is fetching data, I strongly suggest taking a look at RTK Query, React Query, or SWR. These take care of the common pattern of asynchronously fetching data, without making you write the various slices and reducers yourself, and add useful features such as caching, retrying, etc.
Make your bookDetails method accept an extra property named signal. Then pass it to the axios method.
const bookDetails = async ({ id, token, signal }) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
signal,
};
const response = await axios.get(API_URL + `/book/${id}`, config);
return response.data;
};
For this purpose, change your reducer method like this:
async ({ id, signal}, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
return await userService.bookDetails({ id, token, signal });
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
Finally dispatch actions like this:
abortController = new AbortController();
let signal = abortController.signal;
dispatch(bookDetails({ id: bookId, signal: signal ))
it's a good idea to have an AbortController to avoid memory leaks when using useEffect to load data this will be useful when its possible to get errors during dispatching requests to avoid requests still running when an error occurs, here's an example that you can use to cancel requests if the state doesn't load for any reason.
import { useEffect, useState } from 'react';
const MyComponent = () => {
const [data, setData] = useState(null); // set state to null
useEffect(() => {
const controller = new AbortController(); // AbortController instance
// Dispatch the request
fetch('/data', { signal: controller.signal })
.then(response => response.json()) // get response
.then(data => setData(data)) // Updating useState
.catch(e => { // Catch errors
if (e.name === 'AbortError') {
console.log("Failed to fetching data");
}
throw e;
});
// cleanup controller
return () => {
controller.abort();
};
}, []);
// Render the component
return <div>{data && data.message}</div>;
};
I'm currently working on a React project. Using the Axios library, I need to get data from the API and write it to a variable.
Searching the web for this, I found the only option is via Promise.then(), but that's not what I want, as the result is simply returned to the next Promise.then().
I need Axios to return the result and after that my function returns this result instead of with the tag so to speak. Sample code below. Thanks in advance.
function getData() {
let packsId = '11111';
if (packsId) {
return axios.get('http://localhost:8000/api/words?id=' + packsId)
.then((response) => {
return response.data.title;
})
.catch((error) => {
return error.code;
});
}
else {
return "Doesn't exist";
}
}
export default function WordsPack() {
let result = getData(); // here returned Promise, not String
return <div>{ result }</div>
}
If you wanna call let result = getData() at the top level of your component as you showed in your example, you would wanna transform getData to a custom hook, as an example like so:
import { useState, useEffect } from "react";
// ⚠️ Notice it start with "use":
export default function useGetData() {
const [result, setResult] = useState("Loading..."); // the data to show while fetching
useEffect(() => {
let packsId = "11111";
if (packsId) {
axios
.get("http://localhost:8000/api/words?id=" + packsId)
.then((response) => setResult(response.data.title))
.catch((error) => setResult(error.code));
} else {
setResult("Doesn't exist");
}
}, []);
return result;
}
export default function WordsPack() {
let result = useGetData();
return <div>{result}</div>
}
you need to use Async/Await
async function getData() {
let packsId = '11111';
if (packsId) {
try{
return await axios.get('http://localhost:8000/api/words?id=' + packsId);
}
catch(ex){
//error handle
}
}
else {
return <div>Doesn't exist</div>;
}
}
now to read the value you have to call with await in the same way
export default async function WordsPack() {
let result = await getData(); // here returned Promise, not Object with data
return <div>{ result }</div>
}
if it's inside component where you are calling getData()
use useEffect and implement with state
export default async function WordsPack() {
const [result, setResult] = useState();
useEffect(() => {
(async() => {
const dataResult = await getData();
setResult(dataResult)
})();
})
return <div>{ result }</div>
}
for more references you can read
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
i have a onSnapshot query in a function:
//firebaseutil.js
export async function getShorts(uid) {
const q = query(collection(db, 'shorted'), where('owner', '==', uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const urls = [];
querySnapshot.forEach((doc) => {
urls.push({
url: doc.data().url,
shorturl: doc.data().shorturl,
hits: doc.data().hits,
});
});
console.log(urls);
return urls;
});
}
Which correctly logs the data, and relog it if i change it on the firestore collection (as expected)
i am trying to access these data from a user dashboard this way:
//dashboard.js
import { getShorts } from '../lib/fbutils';
import { useEffect, useState } from 'react';
export default function Dashboard() {
const [myurls, setUrls] = useState([]);
useEffect(() => {
const fetchShorts = async () => {
if (user) {
const urls = await getShorts(user.uid);
setUrls(urls);
console.log(myurls);
console.log(urls);
}
};
fetchShorts();
}, []);
user.id is correctly set, but both urls and myurls are logged as undefined (i was thinking at least for a Promise pending)
what am i doing wrong? i usually use this pattern to retrieve data, but it's my first time i get data from a firestore subscription
The onSnapshot() does not return a promise and your getShorts() function returns before the data is received. You can return a promise from that function as shown below:
let fetched = false;
export function getShorts(uid) {
return new Promise((resolve, reject) => {
const q = query(collection(db, 'shorted'), where('owner', '==', uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const urls = querySnapstho.docs.map((d) => ({ id: d.id, ...d.data() }))
if (!fetched) {
// URLs fetched for first time, return value
fetched = true;
resolve(urls);
} else {
// URLs fetched already, an update received.
// TODO: Update in state directly
}
})
})
}
This should return all the URLs when you call the function await getShorts(user.uid); but for the updates received later, you'll have to update them in the state directly because the promise has resolved now.
New to React Hooks and unsure how to solve. I have the following snippet of code within my App.js file below.
What I am basically trying to achieve is to get the user logged in by calling the getUser() function and once I have the user id, then check if they are an authorised user by calling the function checkUserAccess() for user id.
Based on results within the the validIds array, I check to see if it's true or false and set authorised state to true or false via the setAuthorised() call.
My problem is, I need this to process first prior to performing my first render within my App.js file.
At the moment, it's saying that I'm not authroised even though I am.
Can anyone pls assist with what I am doing wrong as I need to ensure that authorised useState is set correctly prior to first component render of application, i.e. path="/"
const [theId, setTheId] = useState('');
const [authorised, setAuthorised] = useState(false);
const checkUserAccess = async (empid) => {
try {
const response = await fetch("http://localhost:4200/get-valid-users");
const allUsers = await response.json();
const validIds = allUsers.map(({ id }) => id);
const isAuthorised = validIds.includes(empid);
if (isAuthorised) {
setAuthorised(true)
} else {
setAuthorised(false)
}
} catch (err) {
console.error(err.message);
}
}
const getUser = async () => {
try {
const response = await fetch("http://localhost:4200/get-user");
const theId= await response.json();
setTheId(theId);
checkUserAccess(theId);
} catch (err) {
console.error(err.message);
}
}
useEffect(() => {
getUser();
}, []);
Unless you are wanting to partially render when you get the user ID, and then get the access level. There is no reason to have multiple useState's / useEffect's.
Just get your user and then get your access level and use that.
Below is an example.
const [user, setUser] = useState(null);
const checkUserAccess = async (empid) => {
const response = await fetch("http://localhost:4200/get-valid-users");
const allUsers = await response.json();
const validIds = allUsers.map(({ id }) => id);
const isAuthorised = validIds.includes(empid);
return isAuthorised;
}
const getUser = async () => {
try {
const response = await fetch("http://localhost:4200/get-user");
const theId= await response.json();
const access = await checkUserAccess(theId);
setUser({
theId,
access
});
} catch (err) {
console.error(err.message);
}
}
useEffect(() => {
getUser();
}, []);
if (!user) return <div>Loading</div>;
return <>{user.theId}</>
This way it should work
but keep in mind that you must render your app only if theId in the state is present, which will mean your user is properly fetched.
const [state, setState] = useState({ theId: '', isAutorized: false })
const getUser = async () => {
try {
const idResp = await fetch("http://localhost:4200/get-user");
const theId = await idResp.json();
const authResp = await fetch("http://localhost:4200/get-valid-users");
const allUsers = await authResp.response.json();
const validIds = allUsers.map(({ id }) => id);
const isAuthorised = validIds.includes(theId);
setState({ theId, isAuthorised })
} catch (err) {
console.error(err.message);
}
}
useEffect(() => {
getUser();
}, []);
if (!state.theId) return <div>Loading</div>;
if (state.theId && !isAuthorized) return <AccessNotAllowed />
return <Home />
I am trying to return the response from an axios API call. I don't quite get what a promise is and all the tutorials/information I find they only log the response, I want to return it.
Here is what I have, but when I call getPokemon it's undefined.
const axios = require('axios');
const getPokemon = () => {
axios.get('https://pokeapi.co/api/v2/pokemon/')
.then(function (response) {
console.log("Response:", response.data.results);
return response.data.results;
})
.catch(function (error) {
return null;
});
}
export {getPokemon};
If this is a React app then you want to do your Axios call in componentDidMount. Axios automatically returns a promise.
class Example extends Component {
constructor(props) {
super(props);
this.state = {
data: ""
};
}
componentDidMount() {
axios
.get("https://pokeapi.co/api/v2/pokemon/")
.then(res => {
console.log(res);
this.setState({
data: res.data.results
});
})
.catch(err => {
console.log(err);
});
}
render() {
let pokemon = this.state.data;
let display = Object.values(pokemon).map((item, key) => {
return (
<div>
<p>{item.name}</p>
<p>{item.url}</p>
</div>
);
});
return (
<div>{display}</div>
);
}
}
export default Example;
Doing it like this will send the Axios request after the React app has loaded and set the JSON data in the component state. You should be able to access the JSON data via this.state.data.
Check out this Codepen example with working API call.
Well, first of all, I suggest you read about promises.
a good method for achieving what you need is by using async/await syntax check out the following code:
const axios = require('axios');
const getPokemon = async () => {
try{
let res = await axios.get('https://pokeapi.co/api/v2/pokemon/');
return res.data.results;
}
catch(error){
return null //that's what you did in your code.
}
}
export {getPokemon};
Remove ".result"
const axios = require("axios");
const getPokemon = async () => {
try {
let res = await axios.get("https://jsonplaceholder.typicode.com/users");
return res.data; **here remove his .result**
} catch (error) {
return null; //that's what you did in your code.
}
};
export default getPokemon;
In index.js or any page call it:
import getPokemon from "./GetPokimon";
const xyz = async () => {
const data = await getPokemon();
alert(JSON.stringify(data));//u will see the data here
}
xyz(); //calling getPokemon()