React Firebase data is undefined - reactjs

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)
}

Related

How to automatically refresh getstream.io FlatFeed after a new post using reactjs?

I would like to understand how can I auto-update the feed after submitting the form through the StatusUpdateForm component. At the moment I have to refresh the page to see the changes.
In general, my task is to differentiate feeds based on the user's location, I requested extended permissions from support so that different users can post to one feed, and therefore I use the modified doFeedRequest parameters of the FlatFeed component to show the feed without being tied to the current user and it works.
I do not use notification, I want the posted messages to appear immediately in the feed.
If I wrote my own custom feed (FeedCustom) component to display data, it would work fine, but how do I make it work with FlatFeed of getstream.io? Any help would be greatly appreciated.
import React, { useEffect, useState } from 'react';
import { StreamApp, FlatFeed, StatusUpdateForm } from 'react-activity-feed';
import 'react-activity-feed/dist/index.css';
// import FeedCustom from './FeedCustom';
const STREAM_API_KEY = 'XXXXXXXXXXXXXXXX';
const STREAM_APP_ID = 'XXXXX';
const App = () => {
const [userToken, setUserToken] = useState(null);
const [loading, setLoading] = useState(true);
const [locationId, setLocationId] = useState(null);
const [data, setData] = useState([]);
const callApi = async () => {
const response = await fetch('https://localhost:8080/user-token')
const userResponse = await response.json();
return userResponse;
};
useEffect(() => {
callApi()
.then(response => {
const resp = JSON.parse(response.body);
setLoading(false);
setUserToken(resp.userToken);
setLocationId(resp.locationId);
})
.catch(e => alert(e));
}, []);
const customDoFeedRequest = (client, feedGroup = 'timeline', userId = locationId, options) => {
const feed = client.feed(feedGroup, userId);
const feedPromise = feed.get(options);
feedPromise.then((res) => {
setData((data) => res.results);
});
return feedPromise;
}
return loading ? (
<div>.... Loading ....</div>
) : (
<StreamApp
apiKey={STREAM_API_KEY}
appId={STREAM_APP_ID}
token={userToken}
>
{/* <FeedCustom dataFeed={ data } /> */}
<FlatFeed doFeedRequest={customDoFeedRequest} />
<StatusUpdateForm
userId={locationId}
feedGroup={'timeline'}
onSuccess={(post) => setData((data) => [...data, post])}
/>
</StreamApp>
)
};
export default App;
My backend https://localhost:8080/user-token returns an object kind of:
{
userToken: 'XXXXXXX'
locationId: 'XXXXXXX'
}

How to use useNavigate outside react hook?

Gets list of emails from firestore and checks if current user is registered and then redirects them to sign up if they are new user.
The code is functional(it redirects succesfully) but get the following error:
arning: Cannot update a component (BrowserRouter) while rendering a different component You should call navigate() in a React.useEffect(), not when your component is first rendered.
const navigate = useNavigate();
let hasEmail = false;
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
const emailCheck = (emails) => { //checks if email exists
hasEmail = emails.some((e) => e.email === auth.currentUser.email);
};
const direct = () => { // redirects to required page
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
emailCheck(emailList);
direct();
Move the email checking logic into a useEffect hook with a dependency on the emailList state.
const navigate = useNavigate();
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
useEffect(() => {
if (emailList.length) {
const hasEmail = emailList.some((e) => e.email === auth.currentUser.email);
navigate(hasEmail ? "/index" : "/enterdetails");
}
}, [auth, emailList, navigate]);
This might not run without the proper firebase config but check it out
https://codesandbox.io/s/elated-bell-kopbmp?file=/src/App.js
Things to note:
Use useMemo for hasEmail instead of emailCheck. This will re-run only when emailList changes
const hasEmail = useMemo(() => {
//checks if email exists
return emailList.some((e) => e.email === auth.currentUser.email);
}, [emailList]);
There isn't really a point in having this in a react component if you are just redirecting away. Consider having the content of 'index' at the return (</>) part of this component. Only redirect if they aren't authorized
useEffect(() => {
if (!hasEmail) {
navigate("/enterdetails");
}
//else {
// navigate("/index");
//}
}, [hasEmail, navigate]);

Getting a undefined value when trying to match fetch results to people objects

Im working on a star wars api app. I am getting an array of people objects, 10 characters. Who all are their own object with different values. However homeworld, and species are urls. So I have to fetch them and store that data to the correct place. I figured out a way to get the homeworld values to each character. However when I try to do it with species I receive undefined. Would appreciate any help this has been kind of a pain thanks ahead of time !
const [people, setPeople] = useState([]);
const [homeWorld, setHomeWorld] = useState([]);
const [species, setSpecies] = useState([]);
const [nextPageUrl, setNextPageUrl] = useState("https://swapi.dev/api/people/");
const [backPageUrl, setBackPageUrl] = useState('');
const [test, setTest] = useState([]);
const fetchPeople = async () => {
const { data } = await axios.get(nextPageUrl);
setNextPageUrl(data.next);
setBackPageUrl(data.previous);
return data.results;
}
const backPage = async () => {
const { data } = await axios.get(backPageUrl);
setCharacters(data.results);
setNextPageUrl(data.next);
setBackPageUrl(data.previous);
}
// Get People
async function getPeople() {
const persons = await fetchPeople();
const homeWorldUrl= await Promise.all(
persons.map((thing) => axios.get(thing.homeworld)),
);
const newPersons = persons.map((person) => {
return {
...person,
homeworld: homeWorldUrl.find((url) => url.config.url === person.homeworld)
};
});
const newPersons2 = newPersons.map((person) => {
return {
...person,
homeWorld: person.homeworld.data.name
};
});
setPeople(newPersons2);
}
// Get Species
async function getSpecies() {
const persons = await fetchPeople();
const speciesUrl = await Promise.all(
persons.map((thing) => axios.get(thing.species)),
);
const newSwapi = persons.map((person) => {
return {
...person,
species: speciesUrl.find((info) => info.data.url === person.species)
};
});
setTest(newSwapi);
// const newPersons2 = newPersons.map((person) => {
// return {
// ...person,
// homeWorld: person.homeworld.data.name
// };
// });
}
useEffect(() => {
getPeople();
getSpecies();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); ```
Species property of person is a array, so your getSpecies() should be like
async function getSpecies() {
const persons = await fetchPeople();
const speciesUrl = await Promise.all(
persons
.filter((thing) => thing.species.length)
.map((thing) => axios.get(thing.species[0]))
);
const newSwapi = persons.map((person) => {
return {
...person,
species: speciesUrl.find((info) => info.data.url === person.species[0])
};
});
setTest(newSwapi);
}

How can I get the data back with useState?

const noticeList = useSelector(state => state.noticeReducer.list) //ν˜„μž¬ νŽ˜μ΄μ§€μ— λ„μ›Œμ§ˆ 곡지 리슀트
//page
const [current, setCurrent] = useState(0); //ν˜„μž¬ νŽ˜μ΄μ§€
const pageInfo = useSelector(state => state.noticeReducer.pageInfo); //전체 νŽ˜μ΄μ§€ 정보
const [keyword, setkeyword] = useState(null); //ν‚€μ›Œλ“œ state
const [searchedList, setsearchedList] = useState(noticeList); // 검색 ν• λ•Œλ§Œ μ‚¬μš©ν•˜λ―€λ‘œ 여기에 μ‚¬μš©
const [active, setactive] = useState("");
console.log(searchedList)
const Search = () => {
const data = axios.post('/noticeList',{
keyword : keyword,
})
.then(res => res.data)
.catch(err => console.log(err));
setsearchedList(data)
}
useEffect(() => {
return (
dispatch(getNoticeList(current+1,keyword)) //곡지사항 λͺ©λ‘ λ°›μ•„μ˜€κΈ°
)
}, [dispatch, current])
//화면에 좜λ ₯ν•˜κΈ° μœ„ν•΄ map ν•¨μˆ˜λ₯Ό ν™œμš©
let homeNotice = searchedList.map(
item =>
{
return(
<NoticeDetail key = {item.noticeId} title = {item.title} active = {active} setactive = {setactive} content = {item.content}/>
)
}
)
I saved the data in Redux in the state with useEffect.
I want to overwrite the data in the same state when searching in the search function. What should I do?
Uncaught TypeError: searchedList.map is not a function
You are not using promises correctly you should set the data with the result of the promise, not with the promise:
const Search = () => {
axios
.post('/noticeList', {
keyword: keyword,
})
.then(({ data }) => setsearchedList(data))
.catch((err) => console.log(err));
};
I am assuming the api call resolves with data being an array.

set Function not working in custom hook with useEffect

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])

Resources