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

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.

Related

How to declare a variable from a firestore query?

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.

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?

How to access useEffect's async data in another function within a component in React

I have a functional component with Hooks:
const Filters = () => {
const [options, setOptions] = useState([]);
const getOption = type => options.find(el => el.name === type);
useEffect(() => {
fetch('someURL')
.then(resp => resp.json())
.then(result => setOptions(result.data), error => console.log(error));
// trying to check options array now
console.log(getOption('type')); // returns undefined
}, []);
}
The purpose of this approach is to fetch a data, then run this data through a computed function, to get a single object based on getOption(type). If i use useEffect(() => fetch(), [options]); then i'll get endless loop with console.log() outputs.
So, setOptions(result.data) is async i guess, just like setState in a class component, but doesn't accept a second parameter to use when async request is done.
I want to modify my options array after a fetch() is done with my getOption() function.
You could use another useEffect to execute a function when options is modified:
const Filters = () => {
const [options, setOptions] = useState([]);
const getOption = type => options.find(el => el.name === type);
useEffect(() => {
fetch('someURL')
.then(resp => resp.json())
.then(result => setOptions(result.data), error => console.log(error));
}, []);
useEffect(() => {
if (!options) {
return;
}
console.log(getOption('type'));
}, [options])
}

How delay redux saga action to store data update?

Can't delay action firing (initialize from redux-form) to store data update after fetching.
Store is initializing with empty account object.
At initial render getAccount action firing and triggering update of store.
useEffect see store updating and triggering getAccount action second time
second data request
END
const {getAccount, initialize} = props
prepareData = () => {...prepared obj}
useEffect(() => {
const begin = async () => {
await getAccount();
await initialize(prepareData());
};
begin();
}, [account.id]);
main aim to avoid unnecessary second request
What if you put a conditional on the begin call?
const {getAccount, initialize} = props
const {begun, setBegun} = useState(false)
prepareData = () => {...prepared obj}
useEffect(() => {
const begin = async () => {
await setBegun(true);
await getAccount();
await initialize(prepareData());
};
begun || begin();
}, [account.id]);
Should getAccount be called if account.id doesn't exist? If not then simply check that account.id exists before calling begin.
const {getAccount, initialize} = props
prepareData = () => {...prepared obj}
useEffect(() => {
// add a guard condition to exit early if account.id doesn't exist yet
if (!account.id) {
return;
}
const begin = async () => {
await getAccount();
await initialize(prepareData());
};
begin();
}, [account.id]);

Categories