Async realtime updates with Firestore issue - javascript

I try to get realtime data back from Firestore with React-Native.
I have a helpers.js file where I store and export all the get and set functions for Firestore and I just import them in App.js to use it.
When I try to get only once (no realtime), it works perfectly.
This is working:
In helpers.js:
const waitlistRef = db.firestore().collection('waitlist');
export async function getWaitlist() {
let waitlist = [];
await waitlistRef.get().then(res => {
res.forEach(doc => {
waitlist.push(doc.data());
});
});
return waitlist;
}
In App.js:
import { getWaitlist } from './helpers';
...
useEffect(() => {
getWaitlist().then(waitlist => console.log(waitlist));
}, []);
So it works, but I only can get it once (no realtime). In order to get realtime updates, I've tried this method:
In helpers.js:
const waitlistRef = db.firestore().collection('waitlist');
export async function getWaitlist() {
let waitlist = [];
await waitlistRef.onSnapshot(snapshot => {
snapshot.forEach(doc =>
waitlist.push(doc.data()),
);
});
return waitlist;
}
In App.js:
import { getWaitlist } from './helpers';
...
useEffect(() => {
getWaitlist().then(waitlist => console.log(waitlist));
}, []);
But when I try to log the waitlist array which was supposed to be returned from the helper function, I just get an empty array.
Any ideas?

Related

How to Stub a module in Cypress

I'm trying to stub a module using Cypress. Here's what I've tried so far, but is not working.
This is the short version of my component/page
// SomeComponent.jsx
import { useSomething } from './useSomething'
const SomeComponent = () => {
// useSomething is a custom hook
const { data, error } = useSomething()
const renderData = () => {
// map the data into an array of JSX elements
return data.map(...)
}
return (
<div>
{renderData()}
</div>
)
}
export default SomeComponent
Here's how my custom hook looks like
// useSomething.js
import { useState } from 'react'
import { getData } from './db'
export const useSomething = () => {
const [data, setData] = useState({})
const [error, setError] = useState()
useEffect(() => {
getData().then(data => {
setData(data)
}).catch(err => {
setError(error)
})
// ... some other unrelated code here
}, [])
return { data, error }
}
Here's how getData looks like
// getData.js
export const getData = () => {
const data = // some API call from external services
return data
}
The method is exposed via db.js (actually db/index.js)
// db.js
export * from './getData'
I'm trying to stub the getData.js to make the e2e test more consistent. Here's what I did.
// something.spec.js
// I'm writing #src just to keep the code sample here short, it's the same file as the db.js I write above
import * as db from '#src/db'
...
// this is how I try to do the stubbing
cy.stub(db, 'getData').resolves(something)
...
The stubbing above doesn't work. The API call to the external service is still happening when running the test. The documentation itself leads me to deduce that I should write it this way, but it's not working.
You can expose db on the window
// useSomething.js
import { useState } from 'react'
import * as db from './db'
const { getData } = db;
if (window.Cypress) { // only when testing
window.db = db;
}
and in the test
cy.window().then(win => {
cy.stub(win.db, 'getData').resolves(something);
})
Or use intercept to stub the API call.

How do you return function results to a React Component?

Seems easy but I can't figure it out, how would you return the results from a function that's being imported to a component?
I get the correct results when i console log them from the .then() but can't seem to return them to the component.
example:
functions.js
export const getFeatures = (e) => {
let features = Client.getEntries({
content_type: '###',
'fields.type': `${e}`
})
.then(response => {
return response.items;
})
.catch(console.error)
}
Component.js
import {getFeatures} from './functions.js'
const App = () => {
let x = getFeatures('home');
console.log(x)
return ( ... )
// expecting the array response [{},{},{}, .. etc], but getting undefined instead
}
getFeatures doesn't return anything, you should change it to return its promise:
export const getFeatures = (e) => {
return Client.getEntries({
content_type: '###',
'fields.type': `${e}`
})
.then(response => {
return response.items;
})
.catch(console.error)
}
then at App add a features state, that will get updated when you call getFeatures on mount stage called by useEffect:
import { useState, useEffect } from 'react'
import {getFeatures} from './functions.js'
const App = () => {
// create a features state
const [features, setFeatures] = useState([])
// on mount call 'getFeatures'
useEffect(() => {
getFeatures('home')
.then(setFeatures) // chain returned promise and pass setFeatures to update features
}, []) // add empty array to tell to run the code on mount only
// do some mapping with features, this is for example purpose
// remember to add an unique key to each feature
return ( features.map(feature => {
return <div key={feature.id}>{feature.name}: {feature.realease}</div>
}))
}
getFeatures('home') will return undefined instead of response.items in your code.
Try this:
// functions.js
export const getFeatures = async (e) => {
const resp = await Client.getEntries({
content_type: '###',
'fields.type': `${e}`
})
return resp.items;
}
// App.js
import {getFeatures} from './functions.js'
const App = () => {
getFeatures('home').then(x => {
console.log(x);
// do something else
});
return ( ... )
}

Why am i getting and empty array when fetching an api with react hooks?

I am new with react hooks, i'm trying to get info from an API but when i do the request i get 2 responses first an empty array and then the data of the API, why am i getting that empty array! , this is my first question, i'm sorry.
Thanks for helping me !
import {useState, useEffect} from 'react';
const getSlides = (API) => {
const[data,setData] = useState([]);
const getData = () =>
fetch(`${API}`)
.then((res) => res.json())
useEffect(() => {
getData().then((data) => setData(data))
},[])
return data
}
export default getSlides;
The useEffect() hook runs after the first render. Since you've initialized the data state with an empty array, the first render returns an empty array.
If you're component depends on data to render, you can always conditionally return null until your data is loaded.
Also, I recommend using an async function for api requests, it allows you to use the await keyword which makes your code easier to read. The only caveat, is that you cannot pass an async function to useEffect, instead define an async function inside your hook, and then call it.
import React, { useState, useEffect } from "react";
const API = "https://example.com/data";
const GetSlides = (props) => {
const [data, setData] = useState();
useEffect(() => {
async function getData() {
const request = fetch(API);
const response = await request;
const parsed = await response.json();
setData(parsed);
}
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (data === undefined) {
return null;
}
return <>data</>;
};
export default GetSlides;
Of course, you can still use Promise chaining if you desire.
useEffect(() => {
async function getData() {
await fetch(API)
.then((res) => res.json())
.then((data) => setData(data));
}
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
<GetSlides api="https://yay.com" />
react components need to be title case
import React, { useState, useEffect } from 'react'
const GetSlides = ({ api }) => {
const [data, setData] = useState(null)
const getData = async () =>
await fetch(`${api}`)
.then((res) => res.json())
.then((data) => setData(data))
useEffect(() => {
getData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
console.log(data)
return <div>slides</div>
}
export default GetSlides
The effect callback function is called after the render of your component. (Just like componentDidMount) So during the first render phase, the data state has not been set yet.
You initialize your data with and empty array here:
const[data,setData] = useState([] <- empty array);
useEffect runs after your component is mounted, and then calls the API, that it might take a few seconds or minutes to retrieve the data, but you return the data right away before knowing if the API finished its call.
If you want to return the data after it has been retrieved from the API, you should declare and async method
const getSlides = async (API) => {
try {
const res = await fetch(API);
const data = await res.json();
return data;
} catch (e) {
throw new Error(e);
}
}
Note that it is not necessary hooks for this function

Invalid Hook Call with Async API Call

I am trying to use a react hook like useMemo or useEffect inside my functional component. The API call is async, and I think that may be what's causing the error.
Service file:
export const getData = (): wretch =>
fetch('/d/get_data')
.get()
.json();
Data formatting logic file:
import {getData} from './services';
export const formatData = (onClick, onSort) => {
// logic here - omitted
const formattedData = [];
return getData().then(res => {
res.forEach(
// more data formatting logic
)
return {formattedData: formattedData, originalData: res};
})};
Rendering file:
import {formatData} from './formatData';
const MyTable = () => {
useEffect(() => formatData({onClick: handleClick, onSort: handleSort}).then(res => setData(res)), []);
Error message:
You're correct about the part where you cant have async code in your useEffect. A workaroud for that is similar to what you're doing.
useEffect(() => {
async function myfunc(){
// Do async work
const response = await apiCall();
setData(response);
}
myFunc();
},[])
This might not answer your question, but it is a common pattern you might find useful :)
Please try this one.
const MyTable = () => {
useEffect(async () => {
const data = await formatData({onClick: handleClick, onSort: handleSort});
setData(data);
},
[]);
}

Handling promises with fetch() in React?

I'm creating my first MERN stack application, and trying to implement a simple API that calls my express server from my React front-end components. I have the API working on the back end, and it is sending the data correctly through fetch(), but I'm having trouble resolving the promise from fetch() in my React component, with the call not stopping firing. My code looks as follows (assuming as of right now all API calls return a dummy format like { title: 'foo', ... }:
import React, { useState } from 'react';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
const getApiData = async (route) => {
try {
let apiData = await fetch(route);
let apiDataJson = await apiData.json();
return apiDataJson;
} catch (err) {
throw new Error('Error on fetch', {
error: err
})
}
}
var retrieve_data = async (route, setterCallback) => {
await getApiData(`/api/${route}`).then((data) => {
console.log('Data retrieved from API')
setterCallback(<div>{data.title}</div>)
}).catch(() => {
setterCallback(<div>ERROR</div>)
})
}
const MyComponent = () => {
const [innerDiv, setinnerDiv] = useState(0);
let data = retrieve_data('myEndpoint', setinnerDiv);
return(
<div>
<h1>Data Retrieved in MyComponent:</h1>
{innerDiv}
</div>
);
}
When I compile the above the component successfully renders (i.e. <MyComponent /> looks like:
<div>
<h1>Data Retrieved in MyComponent:</h1>
<div>foo</div>
</div>
However, then then block keeps executing (i.e. the 'Data retrieved from API' logs to the console hundreds of times/second until I close the application. How can I stop this from executing once it has set the component? Thanks!
You need to useEffect to stop the component from re-rendering. Try something like this.
const MyComponent = () => {
const [innerDiv, setinnerDiv] = useState(0);
useEffect(() => {
retrieve_data('myEndpoint', setinnerDiv);
}, []);
return(
<div>
<h1>Data Retrieved in MyComponent:</h1>
{innerDiv}
</div>
);
}

Categories