I am developing a React app that takes data from a database on Google's Firebase.
The database is organized this way:
users {
user 1 {
name
surname
...
}
user 2 {
name
surname
...
}
}
How I retrieve data in React:
I take location and instrument from the url to filter the results of the search (using just location for now, but would like to use both), and I successfully manage to get the object printed in the console.
componentWillMount(){
const searchResults = FBAppDB.ref('users');
let location = this.props.location;
let instrument = this.props.instrument;
let profiles = [];
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
let users = snap.val();
Object.keys(users).map((key) => {
let user = users[key];
console.log(user);
profiles.push(user);
});
});
// this.setState({users: profiles[0]});
}
console output of the object:
Object {name: "Whatever", surname: "Surrname", ... }
What I seem to fail doing is to save these objects (could be more than one) in the state, so I can update the view when the user changes other filters on the page.
I tried to setState the actual user but the state results empty, any idea?
In addition to writing, what would be the best way to get the data back from the state? How do I loop through objects in the state?
Thanks
EDIT
This is how my render function looks like:
render(){
return(
<Row>
{
Object.keys(this.state.users).map(function (key) {
var user = this.state.users[key];
return <SearchResult user={user} />
})
}
</Row>
);
}
I tried to print this.state.users directly, but it doesn't work. Therefore I tried to come up with a (non-working) solution. I'm sorry for any noob mistake here.
I tried to setState the actual user but the state results empty, any
idea?
It is because your DB operation searchResult is asynchronous and JavaScript does not let the synchronous codes wait until the async code completes.
In your code, if you enable the commented setState invocation, that code will get executed while the async code is waiting for response.
You need to setState inside:
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
let users = snap.val();
const profiles = Object.keys(users).map((key) => users[key]);
this.setState({ users: profiles[0] });
});
Whenever you want something to be executed after an async code, put that inside a callback (to the async fn) or use a Promise.
What would be the best way to get the data back from the state? How do I loop through objects in the state?
setState can be asynchronous too. But you will get the new updated state on next render. So inside render() you can access users in the state as this.state.users and perform your operations using it.
UPDATE:
componentWillMount(){
const searchResults = FBAppDB.ref('users');
const location = this.props.location;
const instrument = this.props.instrument;
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
const users = snap.val();
const profiles = Object.keys(users).map((key) => users[key]);
// previously we were setting just profiles[0]; but I think you need
// the entire list of users as an array to render as search results
this.setState({ users: profiles }); // set the whole data to state
});
}
render(){
const users = this.state.users || []; // note that this.state.users is an array
return(
<Row>
{
users.length > 0
? users.map((user) => <SearchResult user={user} />)
: null
}
</Row>
);
}
Try putting setState inside the .on(() => ...) just after Object.keys(...). It's an asynchronous call to the server, right? I suppose that setState is called right after the call to the server is made, but before you retrieve any data.
Related
I am trying to learn firestore realtime functionality.
Here is my code where I fetch the data:
useEffect(() => {
let temp = [];
db.collection("users")
.doc(userId)
.onSnapshot((docs) => {
for (let t in docs.data().contacts) {
temp.push(docs.data().contacts[t]);
}
setContactArr(temp);
});
}, []);
Here is my database structure:
When I change the data in the database I am unable to see the change in realtime. I have to refresh the window to see the change.
Please guide me on what I am doing wrong.
Few issues with your useEffect hook:
You declared the temp array in the way that the array reference is persistent, setting data with setter function from useState requires the reference to be new in order to detect changes. So your temp array is updated (in a wrong way btw, you need to cleanup it due to now it will have duplicates) but React is not detectign changes due to the reference to array is not changed.
You are missing userId in the dependency array of useEffect. If userId is changed - you will continue getting the values for old userId.
onSnapshot returns the unsubscribe method, you have to call it on component unMount (or on deps array change) in order to stop this onSnapshot, or it will continue to work and it will be a leak.
useEffect(() => {
// no need to continue if userId is undefined or null
// (or '0' but i guess it is a string in your case)
if (!userId) return;
const unsub = db
.collection("users")
.doc(userId)
.onSnapshot((docs) => {
const newItems = Object.entries(
docs.data().contacts
).map(([key, values]) => ({ id: key, ...values }));
setContactArr(newItems);
});
// cleanup function
return () => {
unsub(); // unsubscribe
setContactArr([]); // clear contacts data (in case userId changed)
};
}, [userId]); // added userId
I'm making a react app that works with a API that provides data to my App. In my data base I have data about pins on a map. I want to show the info of those pins on my react app, I want them to render. I get that information with axios and this url: http://warm-hamlet-63390.herokuapp.com/pin/list
I want to retrieve the info from that url, with axios.get(url), stringify the JSON data and then parse it to an array of pins.
The Problem:
My page will be rendered before I get the data back from the server, because axios is async, so I will not be able to show anything. UseEffect and useState won't work because I need something in the first place (I think).
What i've tried:
I tried to use useEffect and useState, but as I said, I think I need something in the first place to change it after. I also tried to use await, but await won't stop the whole React App until it has a response, although it would be nice if there is something that stops the app and waits until I have the array with the info so I can show it on the App then. I tried everything with async. I'm fairly new to React so there might be something basic i'm mssing (?). I've been on this for days, I can't get this to work by any means.. Any help, youtube videos, documentation, examples, is help. Anything. How the hell do I render something that needs to wait for the server respond?
My code:
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
async function pin(){
const result = []
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
const res = await axios.get(url)
console.log(res.data.data);
if(res.data){
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
console.log(result);
}
return result;
}
class App extends React.Component{
render(){
return(
<div>
<Pin/>
<Mapa/>
</div>
)
}
}
export default App
I don't fully understand what you are trying to output but how you would usually handle this is with both the useState hook and the useEffect hook see example below.
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
const pin = () => {
const [result, setResults] = useState([]);
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
useEffect(() => {
//Attempt to retreive data
try {
const res = transformData();
if (res) {
// Add any data transformation
setResults(transformData(res))
}
else {
throw (error)
}
}
catch (error) {
//Handle error
}
}, [])
// Handle data transformation
const transformData = async () => {
const res = await axios.get(url)
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
return result
}
if (!result) {
// Return something until the data is loaded (usually a loader)
return null
}
// Return whatever you would like to return after response succeeded
return <></>;
}
This is all assuming that Pin is a component like you have shown in your code, alternatively, the call can be moved up to the parent component and you can add an inline check like below to render the pin and pass some data to it.
{result && <Pin property={someData} />}
Just a bit of background the useEffect hook has an empty dependency array shown at the end "[]" this means it will only run once, then once the data has updated the state this will cause a rerender and the change should be visible in your component
Rest assured, useEffect() will work. You need to use a condition to conditionally render the content when it comes back from the server.
In the example below if results has a length < 1 the message Loading ... will be rendered in the containing <div>, once you're results are received the state will be updated (triggering a re-render) and the condition in the template will be evaluated again. This time though results will have a length > 1 so results will be rendered instead of Loading ...
I’m operating under the assumption that you’re function pin() is returning the results array.
const app = (props) => {
const [results, setResult] = useState([]);
React.useEffect(() => {
const getPin = async () => {
if (!results) {
const results = await pin();
setResult([…results])
}
}
getPin();
},[results]);
return (
<div>
{result.length ? result : 'Loading ... '}
</div>
)
}
I am working on the backend of my React Native project with Firebase. I want to retrieve records from the database and render a flat ist from an array. I tried to make an array, but outside the function, it is empty again...
Here is my code:
var brand_list = [] // the list
//retrieve record from firebase database
firebase.firestore().collection('Brand').get().then((querySnapshot) => {
querySnapshot.forEach(snapshot => {
let data = snapshot.data();
brand_list.push(data);
})
console.log(brand_list) // contains the records
})
console.log(brand_list) // empty again
What is a possible fix for this, so I can nicely render my flatlist?
In react-native you need to use the state to first initialize the variable and then use setState() to update the variable, setState() functions tell the react-native to update the UI. if the array is empty outside the function that means you need to use async-await here, to make sure you await for a function to be finished first.
I think you try this:
constructor() {
super();
this.state = { dataFetched: [] };
}
async fetchList(){
await firebase.firestore().collection('Brand').get().then((querySnapshot) => {
querySnapshot.forEach(snapshot => {
let data = snapshot.data();
this.setState((prevState)=>{
dataFetched:[...prevState.dataFetched, data]
});
})
console.log(this.state.dataFetched);
// now you can use this.state.dataFetched to update the flatList
}
I have a file that stores an array of objects. I have a component that fetches data from this file then render the list. The file could be updated somewhere else, I need the component to be updated if the file is modified. I have following code example
const header = () => {
const [list, setList] = useState([]);
// fetch
useEffect(() => {
const loadList = async () => {
const tempList = await getList("/getlist"); // get call to fetch data from file
setList(tempList);
};
loadList ();
}, [list]);
// function to render content
const renderList = () => {
return list.map(obj => (
<div key={obj.name}>
{obj.name}
</div>
));
};
return (
<div>{renderList()}</div>
)
}
// get call
router.get('/getlist',
asyncWrapper(async (req, res) => {
const result = await getList();
res.status(200).json(result).end();
})
);
const getList= async () => {
const list = JSON.parse(await fs.readFile(listPath));
return list;
}
Code has been simplified. If I remove the list from useEffect, then it will only render once and will never update unless I refresh the page. If I include list there, loadList() will get called constantly, and component will get re-rendered again and again. This is not the behavior I want. I am just wondering without making header component async component, how do I only re-render this component when the file is changed?
Thank you very much.
There are two approaches you can take to this:
Polling
Request the URL on an interval, and clear it when the component is unmounted.
Replace loadList () with:
const interval = setInterval(loadList, 60000); // Adjust interval as desired
return () => clearInterval(interval)
Make sure the cache control headers set in the response to /getlist don't stop the browser from noticing updates.
Server push
Rip out your current code to get the data and replace it with something using websockets, possibly via Socket.IO. (There are plenty of tutorials for using Socket.io with React that can be found with Google, but its rather too involved to be part of a SO answer).
My application uses the eBay API and allows a user to save a list of favorite items.
I'm storing the user's Favorites data which includes the item ID for a product in a user field in my database.
I need to display the saved favorites yet with updated information from the eBay API, since the data saved (such as price, number of bids) is saved statically when a user selects a Favorite.
I've set up a separate action creator called getUpdates to the database which makes a call like this, and the call works fine.
ebayapi.com/itemID=id2342342,id3234234,id82829294,id234234234
In my Favorites component, i begin by making a call to the database to get the user's favorite items.
useEffect(() => {
props.getFavorites(props.loggedUser.id);
}, [props.loggedUser.id]);
This successfully returns and established my favorites state, which includes a list of the user's favorite items.
I'm trying to take each of the itemId's from the favorites state to pass to my ebay api to retrieve the updated data for each item.
Now, when I do something like what is shown below with a call to my getUpdates action creator...i'm getting an infinite loop, although it is storing the data properly in a new state field that I've defined favUpdates
const Favorites = props => {
useEffect(() => {
props.getFavorites(props.loggedUser.id);
}, [props.loggedUser.id]);
useEffect(() => {
setData(props.cardsToShow);
}, [props]);
const mapFAVS = props.favorites;
const data = Array.from(mapFAVS); //the favorites state is an array of objects so transforming data
const updatedFavs = data.map(item => item.id); //strips out the item id's
const formatFavs = updatedFavs.map(id => id.join(","));
props.getUpdates(formatFavs); //updates favUpdates state
I tried to change this so that the getUpdates call was within one of the useEffect hooks, like this...which also causes an infinite loop.
const Favorites = props => {
useEffect(() => {
props.getFavorites(props.loggedUser.id);
}, [props.loggedUser.id]);
useEffect(() => {
setData(props.cardsToShow);
}, [props]);
const mapFAVS = props.favorites;
const data = Array.from(mapFAVS);
const updatedFavs = data.map(item => item.id);
const formatFavs = updatedFavs.map(id => id.join(","));
useEffect(() => {
props.getUpdates(formatFavs);
}, [props]);
If i remove the second props argument the action creator does not get called and there is an axios error on the backend. Any ideas?
This was solved by adjusting the second argument in the useEffect hook.
useEffect(() => {
props.getUpdates(formatFavs);
}, [props.favorites]);