I'm working on a custom hook that relies on an async operation in useEffect. I cannot get my set function to actually set the value of the result of the async operation. In this case, country is always null in my App component so nothing is ever rendered. foundCountry gets set correctly, but setCountry doesn't seem to work. Thanks for the help!
const useCountry = name => {
const [country, setCountry] = useState([null]);
useEffect(() => {
const findCountry = async () => {
const foundCountry = await axios.get(
`https://restcountries.eu/rest/v2/name/${name}?fullText=true`
);
setCountry(foundCountry);
};
if (name !== '') findCountry();
}, [name]);
};
And here is my App component where I am using the custom hook
const App = () => {
const nameInput = useField('text');
const [name, setName] = useState('');
const country = useCountry(name);
const fetch = e => {
e.preventDefault();
setName(nameInput.value);
};
return (
<div>
<form onSubmit={fetch}>
<input {...nameInput} />
<button>find</button>
</form>
<Country country={country} />
</div>
);
};
You defined the custom hook, but you forgot to return the country state as the result:
const useCountry = name => {
const [country, setCountry] = useState([null]);
useEffect(() => {
const findCountry = async () => {
const foundCountry = await axios.get(
`https://restcountries.eu/rest/v2/name/${name}?fullText=true`
);
setCountry(foundCountry);
};
if (name !== '') findCountry();
}, [name]);
// you forgot to return it
return country;
};
You can try this
const useCountry = name => {
const foundCountry = await axios.get(
`https://restcountries.eu/rest/v2/name/${name}?fullText=true`
);
if (name !== '') return findCountry();
return;
};
//App container
const [country, setCountry] = useState('');
useEffect(() => {
setCountry(useCountry(name))
}, [name])
Related
I have a React component "PostDetails" like this:
const PostDetails = () => {
const params = useParams();
const [post, setPost] = useState({});
const [fetchPostById, isLoading, error] = useFetching(async (id) => {
const response = await PostService.getById(id);
setPost(response.data);
})
useEffect(() => {
fetchPostById(params.id)
}, [])
return (
<div>
<h1>Post details page for ID = {params.id}</h1>
<div>{post.id}. {post.title}</div>
</div>
);
};
export default PostDetails;
Custom hook "useFetching" is implemented like this:
export const useFetching = (callback) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const fetching = async () => {
try {
setIsLoading(true);
await callback();
} catch (e) {
setError(e.message);
} finally {
setIsLoading(false);
}
}
return [fetching, isLoading, error];
}
Utility class "PostService" is implemented like this:
export default class PostService {
static async getById(id) {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts/" + id);
return response;
};
}
In browser console I get the error for "GET" request like this:
GET https://jsonplaceholder.typicode.com/posts/undefined 404
I tried to reformat my URL like this:
https://jsonplaceholder.typicode.com/posts/${id}
But still get the same error.
Why does "params.id" convert into undefined when I call my axios fetching request? What am I doing wrong here?
hope my code would be useful.
CodeSandBox
const [id, setId] = useState(1)
const [data, setData] = useState([]);
useEffect(() => {
const res = axios
.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((res) => setData(res.data));}, [id]);
return (
<>
<div>Fetched Title of data</div>
<div>{data.title}</div>
<button onClick={() => setId(id + 1)}>Click to increase id</button>
<button onClick={() => setId(id - 1)}>Click to decrease id</button>
</>);
can you try plz
useEffect(() => {
params?.id && fetchPostById(params.id)
}, [])
Try this. I have earned.
const PostDetails = () => {
const params = useParams();
const [post, setPost] = useState({});
const [fetchPostById, isLoading, error] = useFetching(async () => {
const response = await PostService.getById(params.id);
setPost(response.data);
})
useEffect(() => {
fetchPostById(params)
}, [])
return (
<div>
<h1>Post details page for ID = {params.id}</h1>
<div>{post.id}. {post.title}</div>
</div>
);
};
export default PostDetails;
I'm trying to build a fintech website, and I have a users collection and a transactions collection. One user can send money to another user by using their phone number.
The user schema contains these
uid - string
phone - string
.....//other data
I need to achieve the following functionality
Enter phone number of target receiver
Get details of user with the entered phone number
Add this data to another collection called transactions
I tried doing that, but I'm getting a bug that in the 3rd step, the data from the 2nd step is undefined. Here's my code
const SendMoney = () => {
const [receiverDetails, setRecieverDetails] = useState({})
const [allUsers, setAllUsers] = useState([])
const [receiverphone, setReceiverphone] = useState('')
const usersCollectionRef = collection(db, "users")
const getAllUsers = async () => {
const data = await getDocs(usersCollectionRef)
setAllUsers(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })))
}
useEffect(() => {
getAllUsers()
}, [])
const getRecieverDetails = (phone) => {
const receiver = allUsers.filter(u => u.phone === phone)[0]
setRecieverDetails(receiver)
}
const makeTransaction = async () => {
getRecieverDetails(receiverphone)
console.log(receiverDetails) --------> prints {}
const transactionsCollectionRef = collection(db, "transactions")
await addDoc(transactionsCollectionRef,
{
toUser: receiverDetails.uid,
//other data
}
)
}
return (
<div>
<h2>Send money</h2>
<input placeholder='phone number' onChange={e => setReceiverphone(e.target.value)} />
<input type="number" onChange={e => setAmount(e.target.value)} />
<button onClick={makeTransaction}>send money</button>
</div>
)
}
export default SendMoney
My guess is that the addDoc function is called before the receiverDetails gets populated with data. I am not sure how to fix this bug
Calls to setState or the setter of a useState hook are asynchronous.
Don't use state to pass data between your own pieces of code, but instead use normal variables, and promises or async/await to synchronize.
const SendMoney = () => {
const [receiverDetails, setRecieverDetails] = useState({})
const [allUsers, setAllUsers] = useState([])
const [receiverphone, setReceiverphone] = useState('')
let users; // 👈 new variable
const usersCollectionRef = collection(db, "users")
const getAllUsers = async () => {
const data = await getDocs(usersCollectionRef)
users = data.docs.map((doc) => ({ ...doc.data(), id: doc.id })); // 👈 This is synchronouse
setAllUsers(users); // 👈 This is asynchronous
}
useEffect(() => {
}, [])
const getRecieverDetails = (phone) => {
await getAllUsers()
const receiver = users.filter(u => u.phone === phone)[0]
setRecieverDetails(receiver)
}
Why does my article state doesnt have the same Parameter like my cart.filter element.
What am I doing wrong, using the useState Hook.
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
setArticle(cart.filter((e) => e.id === id));
fetchCartAndPrice();
}, []);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
}
In the moment that you are trying set the articles don't have carts yet. You need wait the cart update creating an exclusive useEffect to cart. Something like this:
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
fetchCartAndPrice();
}, []);
useEffect(() => {
setArticle(cart.filter((e) => e.id === id));
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
When you trigger your function setArticle() the async function which fetch the cart didn't finished yet ... So it can't "filter" the (still empty) cart ...
You need to execute that filter thing after the cart is set :
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
};
}, []);
useEffect(() => {
// --> ensure we are not in the "init step"
if (cart.length) {
setArticle(cart.filter((e) => e.id === id));
// Not sur where this one belongs ... :
fetchCartAndPrice();
}
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
Another maner to do so is to set the article at the same place of the cart :
useEffect(() => {
const fetchCartAndPrice = async () => {
const { sess } = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
setArticle(sess.cart.filter((e) => e.id === id));
};
}, []);
Gabriel Furlan gave a great solution.
I would use the async declaration at the top level of the useEffect hook.
Ex.
const [article, setArticle] = useState();
const [cart, setCart] = useState([]);
const { id } = useParams();
useEffect(async () => {
const sess = await ApiClientShoppingCart.getCart();
setCart(sess.cart);
}, []);
useEffect(() => {
setArticle(cart.filter((e) => e.id === id));
}, [cart]);
return (
<div>
{console.log(cart.filter((e) => e.id === id))}
{console.log(article)}
</div>
);
there is something strange happening with my code. My variable data (useState) is randomly empty when I call my callback when onpopstate event is fired.
I have 2 components and 1 hook used like that:
const Parent = props => {
const {downloadData} = useData();
const [data, setData] = useState([]);
const [filteredData, setFilteredData] = useState();
const loadData = async () => setData(await downloadData());
useEffect(() => {
loadData();
}, []);
return <FilterPage data={data} onDataChange={data => setFilteredData(data)} />
}
const FilterPage = ({data, onDataChange} => {
const {saveHistoryData} = useHistoryState('filter', null, () => {
updateFilters();
});
const filter = (filterData, saveHistory = true) => {
let r = data; // data is randomly empty here
...
if(saveHistory)saveHistoryData(filterData);
onDataChange(r);
}
});
// my hook
const useHistoryState = (name, _data, callback) => {
const getHistoryData = () => {
const params = new URLSearchParams(window.location.search);
try{
return JSON.parse(params.get(name));
}catch(err){
return null;
}
}
const saveHistoryData = (data) => {
const params = new URLSearchParams(window.location.search);
params.set(name, JSON.stringify(data || _data));
window.history.pushState(null, '', window.location.pathname + '?' + params.toString());
}
const removeHistoryData = () => {
const params = new URLSearchParams(window.location.search);
params.delete(name);
window.history.pushState(null, '', window.location.pathname + '?' + params.toString());
}
const watchCallback = () => {
callback(getHistoryData());
};
useEffect(() => {
let d = getHistoryData();
if(d)watchCallback();
window.addEventListener('popstate', watchCallback);
return () => window.removeEventListener('popstate', watchCallback);
}, []);
return {getHistoryData, saveHistoryData, removeHistoryData};
}
Any suggestions please
Edit
I'm sorry is not the entire code, just a draft. I download the data using async function. The data is loading fine but is empty only if we call the callback from the hook.
You need to use setData to populate data
First of all you are not calling setData() anywhere.
You are using data but not setData and you are using setFilteredData but not filteredData.
Furthermore it doesn't look like updateFilters() exist within FilterPage.
You are passing onDataChange to <Filterpage> but you are not using the property, only ({data}) which explains why it's empty. You might want to update the FilterPage signature: const FilterPage = ({data, onDataChange}) => {} and use the onDataChange
I need some help understanding why I'm getting the error from the title: 'TypeError: Cannot read property 'map' of undefined'. I need to render on the page (e.g state & country here) some data from the API, but for some reason is not working.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const APIFetch = () => {
const [user, setUser] = useState('');
const [info, setInfo] = useState([]);
const fetchData = async () => {
const data = await axios.get('https://randomuser.me/api');
return JSON.stringify(data);
}
useEffect(() => {
fetchData().then((res) => {
setUser(res)
setInfo(res.results);
})
}, [])
const getName = user => {
const { state, country } = user;
return `${state} ${country}`
}
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>
})}
</div>
)
}
Can you guys provide me some help? Thanks.
Try this approach,
const APIFetch = () => {
const [user, setUser] = useState("");
const [info, setInfo] = useState([]);
const fetchData = async () => {
const data = await axios.get("https://randomuser.me/api");
return data; <--- Heres is the first mistake
};
useEffect(() => {
fetchData().then((res) => {
setUser(res);
setInfo(res.data.results);
});
}, []);
const getName = (user) => {
const { state, country } = user.location; <--- Access location from the user
return `${state} ${country}`;
};
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>;
})}
</div>
);
};
Return data without stringify inside the fetchData.
Access user.location inside getName.
Code base - https://codesandbox.io/s/sharp-hawking-6v858?file=/src/App.js
You do not need to JSON.stringify(data);
const fetchData = async () => {
const data = await axios.get('https://randomuser.me/api');
return data.data
}
Do it like that
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const APIFetch = () => {
const [user, setUser] = useState('');
const [info, setInfo] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get('https://randomuser.me/api');
setUser(res.data);
setInfo(res.data.results);
}
featchData();
}, [])
const getName = user => {
const { state, country } = user;
return `${state} ${country}`
}
return (
<div>
{info.map((info, id) => {
return <div key={id}>{getName(info)}</div>
})}
</div>
)
}
Codesandbox: https://codesandbox.io/s/vigorous-lake-w52vj?file=/src/App.js