How to declare a variable from a firestore query? - javascript

this piece of code works when I press save in visual studio code. But If I refresh the preview page in the browser it shows me this error: Unhandled Rejection (FirebaseError): Function Query.where() called with invalid data. Unsupported field value: undefined
let { id } = useParams();
const [video, setVideo] = React.useState([]);
const [show, setShow] = React.useState([]);
const classes = useStyles();
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore();
const data = await db
.collection("videos")
.where('path', '==', id)
.get()
setVideo(data.docs.map(doc => doc.data()));
}
fetchData()
}, [])
let showUrl = video.map(video =>(video.uploadBy));
console.log(showUrl[0]);
let videoDate = video.map(video =>(video.date.toDate()));
console.log(videoDate[0]);
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore();
const data = await db
.collection("shows")
.where('urlPath', '==', showUrl[0])
.get()
setShow(data.docs.map(doc => doc.data()));
}
fetchData()
}, [])
I think that the problem is that I'm trying to declare the variable "showUrl" in the wrong way. The console.log(showUrl[0]) works perfectly. It prints exactly the value that I need.

Both these useEffect calls fire as soon as the component mounts. If you are getting the id for your first useEffect from url parameters or such, it's probably there immediately and the call Firestore query should work.
However, when your second useEffect fires, the state 'video' is still set to an empty array. Therefore the showUrl variable is also an empty array, and showUrl[0] is undefined.
What you could do for your second useEffect is this:
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore();
const data = await db
.collection("shows")
.where('urlPath', '==', showUrl[0])
.get()
setShow(data.docs.map(doc => doc.data()));
}
video.length && fetchData()
}, [video])
So you are only calling the fetchData() function if the array in 'video' state has more than 0 items, and you add it to the useEffect dependency array, so the useEffect gets ran every time 'video' changes.

Related

React: How to save value from async function and use later

I have an async function that calls an api getting me the current role of a user. Later on I want to attach that role to a variable. Here's my code
const getRole = async () => {
const response = await roleService.getRole();
const roles = await response.role
return roles
}
...........
const currentRole = getRole() //I want this to be the value from return roles
I'm new to react and having trouble with this. How can I set currentRole to the value in return roles?
I would opt to save the information that you got from the API on a state
const [roles, setRoles] = useState();
const getRole = async () => {
const response = await roleService.getRole();
const roles = await response.role
setRoles(roles);
}
you can call the gerRole function on a useEffect like this
useEffect(() => {
getRole();
}, []);
or you can call the getRole function on a button click
<button onClick={getRole}>Click me to get roles</button>

How can I set state in useMemo with an async function?

I am trying to fetch data during ComponentWillMount lifecycle in server side using useMemo()
const [text, setText] = useState('hello')
const fakeApiCall = new Promise(resolve => 'world')
useMemo(async () => {
const value = await fakeApiCall
setText(value)
}, [])
Doing this the value for text will still remain hello instead of world. Is it possible to achieve this? Why is this not working?
as you know, the await will be return a 'promise object', and after the response came out it will be replaced with 'promise value', all you need is a falge to detect whenever you got a success response .
your code will seems like
const [text, setText] = useState('hello')
const fakeApiCall = new Promise(resolve => 'world')
const [flage,setFlag] = useState(false) //adding whatever your flag name
useMemo(async () => {
const response = await fakeApiCall
if(response.Success) // you will check on response result
{
setText(value)
}
}, [])
or you can check on this Promise.resolve(valueOrPromiseItDoesntMatter).then
Practically, you can achieve all you need here with useState and useEffect:
const [text, setText] = useState('hello')
const fakeApiCall = new Promise(resolve => 'world')
useEffect(() => {
fakeApiCall()
.then(response => { setText(response) })
},[])
Note that you do not need to make the function within the useEffect hook async as it will resolve normally through typical promise flow.
As we have passed nothing into the dependency array, this hook will execute once on component mount and effectively memoise the response in the text variable.

stale state inside socket callback

I was trying to make socket connection using React hooks. I am getting a stale value when trying to access a state variable inside socket callback function. users variable inside socket callback is not having the latest value. Using useRef works but it does not rerender the component. What could be the better solution for this ?. Thanks!
const [users, setUsers] = useState([]);
const [socket, setSocket] = useState(null);
const result = useParams();
useEffect(() => {
const socket = io(`${config.host}?meetingId=${result.id}`);
socket.request = promise(socket);
setSocket(socket);
}, []);
useEffect(() => {
if (!socket) return;
let webrtc = null;
socket.on('UserLeft', ({ id }) => {
setUsers(users => users.filter(user => user.id !== id))
});
socket.on('UserAdded', ({ name, id }) => {
const newUser = new OtherUser(name, id);
setUsers(users => [...users, newUser])
})
socket.on('newProducer', async (data) => {
const { socketId, kind } = data;
const consumer = await webrtc.createConsumer(socketId, kind);
// Issue is here, the users array still has stale values
const user = users.find(ele => ele.id === socketId);
// Some more stuff with user
})
}, [socket]);
// Here the output is correct
console.log(users)
return <div>HELLO</div>
You could use useReducer to store mutating variables and add logic into the handler

useEffect overriding the state instead of appending the values while making firestore calls

const [data, setData] = useState([])
const getDataFromFirebase = async () => {
let response = await firestore.collection('someDatabase').get()
response.forEach(item => setData([...data, item.data().name]))
}
useEffect(() => {
getDataFromFirebase()
},[])
data is being overridden with the latest value instead of adding all the values to the array.
The reason is time taken to add item is very less thats why before reflecting, it got override. You have to use prevState in setData . Try this:
const [data, setData] = useState([])
const getDataFromFirebase = async () => {
let response = await firestore.collection('someDatabase').get()
response.forEach(item => setData(prevState => ([
...prevState, item.data().name])
);
}
useEffect(() => {
getDataFromFirebase()
},[])
Use the callback in setData
setData(prevState => ([
...prevState, item.data().name
]));
let response = await firestore.collection('someDatabase').get()
response.forEach(item => setData([...data, item.data().name]))
I'm not familiar with firestore, but that promise will be resolved once, and you should do something like this instead:
const dataToAdd = response.map(item => item.data().name)
setData(prevState => ([...prevState, ...dataToAdd])
You are rerending component each time the setData is being called and you shouldn't do it in a synced loop.
prevState is necessary here because you are working in an asynchronous function. In theory, it should work without it after using a solution with dataToAdd if you don't change the state anywhere else.
try this fire setState once but build the array before :
const [data, setData] = useState([])
const getDataFromFirebase = async () => {
let response = await firestore.collection('someDatabase').get()
const d = response.map(item=> item.data().name)
setData(d)
}
useEffect(() => {
getDataFromFirebase()
},[])
firing setData multiple times will cause multiple rerenders so here it's fire once.
In your code below the value of data will be always [] even if you change the data later.
const getDataFromFirebase = async () => {
let response = await firestore.collection('someDatabase').get()
response.forEach(item => setData([...data, item.data().name]))
}
This is what docs say about it
Mutations, subscriptions, timers, logging, and other side effects are
not allowed inside the main body of a function component (referred to
as React’s render phase). Doing so will lead to confusing bugs and
inconsistencies in the UI.
Its not a good idea to call setData in each loop. Populate an array and pass it to setData once loop is complete.
const getDataFromFirebase = async () => {
let response = await firestore.collection('someDatabase').get();
let newData = [];
response.forEach(item => newData.push(item.data().name));
// now set the new data
setData(prevData=> ([...prevData, ...newData]));
// or you can use Array.concat
// setData(prevData=> (prevData.concat(newData)));
}

Why when using setState(...) is the console.log null?

Why when I console.log(data) is does it log the data but when I try and log the item set by set state its null? I don't understand is it because it doesn't have time to update before console logging?
const [currentUser, setCurrentUser] = useState(null);
const [pending, setpending] = useState(true);
const [userData, setuserData] = useState({});
useEffect(() => {
Authentication.auth().onAuthStateChanged((user) => {
setCurrentUser(user)
setpending(false)
localStorage.setItem('user', user.uid);
console.log(localStorage.getItem('user'));
});
getData()
}, []);
const getData = () => {
Authentication.firestore().collection('Health_data')
.doc(localStorage.getItem('user'))
.get()
.then(doc => {
const data = doc.data();
localStorage.setItem('user_data', JSON.stringify(data));
setuserData(data)
console.log(data)
}).catch(function (error) {
console.error("Error reading health", error);
});
console.log(userData)
}
I'm going to assume you're confused by the output of console.log(userData); at the end of getData. There are two reasons why it doesn't show the updated user, either of which would be enough on its own. :-) They are:
The console.log(userData) happens before the setuserData call, because promise callbacks are always asynchronous.
userData is a constant within the Example function call, its value will not change. When the state is changed, Example will get called again, and that new userData constant will have the updated user in it. (Note that it doesn't matter whether it's declared with let or const, it's effectively a constant either way.) Each of those constants exists in an execution context that's tied to the specific call to Example the constant was created in. More in Dan Abramov's article A Complete Guide to useEffect (which is about much more than useEffect).
Here's a demonstration:
const { useState } = React;
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
let executionContext = 0;
const Example = () => {
const contextNumber = ++executionContext;
const [value, setValue] = useState(0);
const log = msg => console.log(`[${contextNumber}]: ${msg}`);
log(`Example called, value = ${value}`);
const onClick = () => {
log(`onClick`);
delay(800)
.then(() => {
const newValue = Math.floor(Math.random() * 10000);
log(`calling setValue(${newValue})...`);
setValue(newValue);
log(`value is still ${value} (reason #2)`);
});
log(`value is still ${value} (reason #1)`);
};
return (
<div>
{value}
<div>
<input type="button" onClick={onClick} value="Click Me" />
</div>
</div>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
If you are talking about this code:
Authentication.firestore().collection('Health_data')
.doc(localStorage.getItem('user'))
.get()
.then(doc => {
const data = doc.data();
localStorage.setItem('user_data', JSON.stringify(data));
setuserData(data)
console.log(data)
}).catch(function (error) {
console.error("Error reading health", error);
});
console.log(userData)
The problem is
Authentication.firestore().collection('Health_data')
.doc(localStorage.getItem('user'))
.get()
Is async execution, so console.log(userData), sometimes will execute before the Authentication call and the value will be null.
So you need to ensure that the Authentication call has finished
if(userData !== null) {
console.log(userData)
}
this code seems to work for me but I'm not sure how I can use this to access the information on other pages the same way?

Categories