Rendering Firebase list on React - reactjs

I followed this https://www.youtube.com/watch?v=p4XTMvagQ2Q introductory tutorial on Firebase and React. I am trying to take this further but I have been stuck for a while. Basically, I want to retrieve a list of child from a firebase node and render it as list/table. Currently, only the last child is being rendered since every other one is being overwritten.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(){
super();
this.state = {
id : 0,
latitude : 0,
longitude : 0
};
}
componentDidMount(){
const rootRef = firebase.database().ref().child('drivers');
rootRef.on('child_added', snap => {
console.log(snap.key);
this.setState({
id : snap.key,
latitude : snap.val().lat,
longitude : snap.val().lon
});
});
}
render() {
return (
<div className="App">
<h1>{this.state.id}</h1>
<h1>{this.state.latitude}</h1>
<h1>{this.state.longitude}</h1>
</div>
);
}
}
export default App;
How do I update the render function to display the whole list?

What is happening is you're retrieving the whole list, but for every element in the list, you're overriding your state with its value. Then, in your state, you'll always have the last values from the list.
What you can do in this case is store a list of elements, let's say positions:
this.state = {
listOfPositions: []
};
Then when a child is added, you append the contents of the new child to your current list:
rootRef.on('child_added', snap => {
const previousList = this.state.listOfPositions;
previousList.append({
id: snap.key,
latitude: snap.val().lat,
longitude: snap.val().lon
});
this.setState({
listOfPositions: previousList;
});
});
Finally, in your render method, you generate a list of react elements for every element in your listOfPositions and render it in the render method:
render() {
const listOfPositions = this.state.listOfPositions.map(position =>
<div>
<h1>{position.id}</h1>
<h1>{position.latitude}</h1>
<h1>{position.longitude}</h1>
</div>
);
return (
<div>{listOfPositions}</div>
);
}
Note that when you're setting your reference to the database and you get your value from this.state.listOfPositions, you can't guarantee that the state value is the most updated one. So if you want to be sure about it use the other definition for the this.setState() method like this:
rootRef.on('child_added', snap => {
const newPosition = {
id: snap.key,
latitude: snap.val().lat,
longitude: snap.val().lon
};
this.setState(previousState => {
listOfPositions: previousState.listOfPositions.append(newPosition);
});
});
This will make sure when you call this.setState() you will have the last value of state in your previousState variable.

Related

How to render updated state in react Js?

I am working on React Js in class component I am declaring some states and then getting data from API and changing that state to new value but React is not rendering that new value of state. but if I console.log() that state it gives me new value on console.
My code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState({ unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}
This is printing 2 on console but rendering 0 on screen. How can I get updated state(2) to render on screen.
and if I visit another page and then return to this page then it is rendering new value of state (2).
Please call getUnread() function in componentDidMount, something like this
componentDidMount() {
this.getUnread()
}
This is because in React class components, while calling setState you it is safer to not directly pass a value to set the state (and hence, re-render the component). This is because what happens that the state is set as commanded, but when the component is rerendered, once again the state is set back to initial value and that is what gets rendered
You can read this issue and its solution as given in react docs
You pass a function that sets the value.
So, code for setState would be
this.setState((state) => { unread: data.count });
Hence, your updated code would be :
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState((state) => { unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}

react lifecycle method - correct way to filter

I am new to react. I have a product page component which is a page showing a particular product. What I am trying to do is that this ProductPage find the particular product in the products (which is an array of objects stored as props) and save it as the state before render it out.
I got these errors. I am not able to filter out single product from the array.
1) currentProduct is not defined.
2)Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Which lifecycle method should I use in order to resolve this? I am confused. Can someone explain to me?
In my ProductPage.js,
import React, { Component } from 'react';
import { connect } from 'react-redux';
class ProductPage extends Component {
constructor(props) {
super(props)
this.state = {
product: [],
productId: this.props.match.params._id,
}
this.productsFiltering = this.productsFiltering.bind(this);
}
// update the state so that a product can be rendered
componentDidUpdate() {
const currentProduct = this.productsFiltering(this.state.productId);
this.setState({
product: currentProduct,
})
}
// find a product based on the Id
// pass the _id from url to find out the single product
productsFiltering = (productId) => {
const productArray = this.props.products;
return productArray.find(product => product._id === productId)
}
render() {
// error
const {name, price, description} = product;
return (
<div>
<span>{product.name}</span>
<span>{product.price}</span>
<span>{product.description}</span>
</div>
)
}
}
const mapStateToProps = (state) => ({
products: state.productsContainer,
});
export default connect(
mapStateToProps,
)(ProductPage);
The root issue is that you're copying props to state, which there is no need to do.
ProductPage displays a product and doesn't change, therefore no state is needed. Simply grab the matching product and store it in a variable then render. No setState() needed.
you can remove both errors if you change your code to this:
class ProductPage extends Component {
constructor(props) {
super(props);
this.state = {
product: []
};
this.productsFiltering = this.productsFiltering.bind(this);
}
// update the state so that a product can be rendered
componentDidUpdate() {
const productId = this.props.match.params._id;
if (productId && this.state.product._id != productId) {
const currentProduct = this.productsFiltering(productId);
if (currentProduct) {
this.setState({
product: currentProduct
});
}
}
}
// find a product based on the Id
// pass the _id from url to find out the single product
productsFiltering = (productId) => {
const productArray = this.props.products;
if(productArray.length){
return productArray.find((product) => products._id === productId);
}
};
render() {
const { name, price, description } = this.state.product || {};
return (
<div>
<span>{product.name}</span>
<span>{product.price}</span>
<span>{product.description}</span>
</div>
);
}
}
const mapStateToProps = (state) => ({
products: state.productsContainer
});
export default connect(mapStateToProps)(ProductPage);
-- always check for undefined or null properties
-- don't use setState in componentDidMount if the new state is constantly changing
-- avoid using a state which is directly drived from props because it's a bad practice and you can get stale props

How to filter data from an object array according to their property values, in ReactJs?

I wrote a simple code to just pass all the data taken from a firebase real time database into a react component, which is called cubetemplate, via an object array. Here is a screenshot of the firebase I used to do the test:
Here's the code I used:
<div className="gal">
{Object.keys(this.state.gallery)
.sort()
.map(item => {
//inserting into the component
return <Cubetemplate source={this.state.gallery[item]["img"]} key={item}/>;
})}
</div>
Now, the problem is that, I want to pass only the objects which verify the condition "type"="vid", into the Cubetemplate component. How do I accomplish this?
Here's the full code to take a better understanding of the situation:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Cubetemplate from './components/cubes/Cubetemplate';
import './Gallery.css';
import * as firebase from 'firebase';
class Gallery extends Component {
//this is the place where you declar your vars
constructor(props) {
super(props);
this.state = {
loading: false,
list: [1, 2, 3],
value: '',
gallery:{},
isLoggedIn: false,
};
this.readDB = this.readDB.bind(this); //no idea what the hell this is
}
readDB() {
var that = this;
var starCountRef = firebase.database().ref("/gallery");
//the values from the firebase will be printed in the console
starCountRef.once('value', snapshot => {
snapshot.forEach(function (childSnapshot) {
var childKey = childSnapshot.key;
var childData = childSnapshot.val();
console.log("Key: " + childKey + " Title: " + childData.title);
});
that.setState({ isLoggedIn: true });
//just to check the values of snapshot
// console.log(snapshot.val());
//takes the data from friebase to snapshot to news.
this.setState({ gallery: snapshot.val() });
console.log(this.gallery);
}, err=> {
//errors
console.log(err);
});
};
componentDidMount(){
this.readDB();
};
render() {
return (
<div className="gallerycontainer">
<div className="gal">
{Object.keys(this.state.gallery)
.sort()
//map
.map(item => {
//inserting into the component
return <Cubetemplate source={this.state.gallery[item]["img"]} key={item}/>;
})}
</div>
</div>
);
}
}
export default Gallery;
Thanks in advance stackoverflowers!
You could use firebase filtering like Frank mentioned or you could simply do some conditional rendering like this:
<div className="gal">
{Object.keys(this.state.gallery)
.sort()
.map(item => {
//Only render if the item type is 'vid'
if (this.state.gallery[item]["type"] == "vid") {
<Cubetemplate source={this.state.gallery[item]["img"]} key={item}/>;
}
})}
</div>
To load only the gallery child nodes with type equal to vid, you'd use a Firebase query like this:
var galleryRef = firebase.database().ref("/gallery");
var query = galleryRef.orderByChild("type").equalTo("vid");
query.once('value', snapshot => {
...
For more on this, I highly recommend studying the Firebase documentation on ordering and filtering data.

setState not updating the state object

I'm learning react from the book full stack react. In one of the examples votingapp where you have products and a button to vote for the product. That button supposes to increase the number of votes for that product and I store the votes number in a component Parent's state and display it in a child component. That voting feature is not working.
I created Parent component where it displays child component that present product description, id, color and votes (the number of votes the product received)
import React, { Component } from 'react';
import './App.css';
import Product from "./Product";
var products = [
{
id: 1,
name: "one",
color: "blue",
votes:0
},
{
id: 2,
name: "two",
color: "green",
votes : 0
},
{
id: 3,
name: "three",
color: "Red",
votes : 1
}
];
class App extends Component {
//this function will be passed to child component so the child can pass any data needed back to the parent using function argument.
handleProductUpVote(productId) {
console.log("Product #" + " " +productId + " was upvoted")
};
render() {
const productComponents = products.map((product) => {
return <Product
key={"product-" + product.id}
id={product.id}
name={product.name}
color={product.color}
votes={product.votes}
/*this is how we pass the function from parent to the child as a prop*/
onVote={this.handleProductUpVote}
/>
});
return (
<div className="App">
{productComponents}
</div>
);
}
}
export default App;
and here is my child component where it renders the product details
import React, { Component } from 'react';
class Product extends Component {
constructor(props) {
super(props);
this.handleUpVote = this.handleUpVote.bind(this);
}
//using the function passed from parent in a child. We pass any data needed back to parent component using parent's function arugments
// invoke this function using onClick event inside the button
handleUpVote() {
this.props.onVote(this.props.id);
};
render() {
return (
<div>
<p> name: {this.props.name} </p>
<p> MYcolor: {this.props.color} </p>
{/*invoke the function using onClick so it will invoke handleUpVote that will update the prop and pass the data back to parent*/}
<button onClick={this.handleUpVote}> Upvote Me </button>
<p>{this.props.votes}</p>
<hr></hr>
</div>
)
}
};
export default Product;
this is working and I log to the console the message when I hit the button "Upvoteme"
But When I'm trying to move the setup to use state. It doesn't work Here is the parent component with the state and setState. When I click on the vote button nothing happens to the vote count.
import React, { Component } from 'react';
import './App.css';
import Child from "./Child";
var productseed = [
{
id: 1,
name: "one",
color: "blue",
votes: 0
},
{
id: 2,
name: "two",
color: "green",
votes : 0
},
{
id: 3,
name: "three",
color: "Red",
votes : 1
}
];
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
};
this.handleProductUpVote = this.handleProductUpVote.bind(this);
}
componentDidMount() {
this.setState ({ products: productseed });
}
//this function will be passed to child component so the child can pass any data needed back to the parent using function argument.
handleProductUpVote(productId) {
// updating the vote in state
const nextProducts = this.state.products.map((product) => {
if (product.id === productId) {
return Object.assign({}, product, {
votes: product.votes + 1,
});
} else {
return product
}
});
this.setState({
products: nextProducts,
});
}
render() {
const productComponents = productseed.map((product) => {
return <Child
key={"product-" + product.id}
id={product.id}
name={product.name}
color={product.color}
votes={product.votes}
/*this is how we pass the function from parent to the child as a prop*/
onVote={this.handleProductUpVote}
/>
});
return (
<div className="App">
parent
{productComponents}
</div>
);
}
}
export default Parent;
The line below suppose to point to the products in the state but when I highlight it, it doesn't highlight the products in the this.state.
The consolelog is logging the message to my developer console. This is the issue, products in this.setState isn't pointing to the this.state.products and therefore not updating the state.
componentDidMount() {
this.setState({ products: productseed });
console.log("this products not pointing to the this.state.products, why?")
}
I read every question on stackoverflow related to setState not working and yet I have the same problem. If you an expert with react and able to take a look at this and figure out where is the issue, I would be so grateful. I'm unable to figure out when I assigned the this.setState({products: productseed}) and it doesn't update the state. I spent almost the past 4 hours reading and researching, please help.
Your problem lies in the render method of your Parent component. You're iterating over the productseeds array instead of your state. Since you're updating the state and not the seed array react sees no reason to rerender anything and therefore nothing changes.
So if you change that line from
const productComponents = productseed.map((product) => {...
to
const productComponents = this.state.products.map((product) => {...
you should be fine.
Moreover about your:
The line below suppose to point to the products in the state but when I highlight it, it doesn't highlight the products in the this.state.
This is just something related to the IDE you're using and nothing specific about react. You're passing an object with attributes and most IDEs (or all (?)) don't connect the combination with this.setState to the state object.

What is best approach to set data to component from API in React JS

We have product detail page which contains multiple component in single page.
Product Component looks like:
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
Here we have 3 APIs for
- Details
- Similar Product
- Other Products
Now from Detail API we need to set data to these components
<Gallery/>
<Video/>
<Details/>
<Contact/>
In which component we need to make a call to API and how to set data to other components. Lets say we need to assign a,b,c,d value to each component
componentWillMount(props) {
fetch('/deatail.json').then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ...');
}
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
OR
Do we need to create separate api for each components?
Since it's three different components you need to make the call in the component where all the components meet. And pass down the state from the parent component to child components. If your app is dynamic then you should use "Redux" or "MobX" for state management. I personally advise you to use Redux
class ParentComponent extends React.PureComponent {
constructor (props) {
super(props);
this.state = {
gallery: '',
similarPdts: '',
otherPdts: ''
}
}
componentWillMount () {
//make api call and set data
}
render () {
//render your all components
}
}
The Product component is the best place to place your API call because it's the common ancestor for all the components that need that data.
I'd recommend that you move the actual call out of the component, and into a common place with all API calls.
Anyways, something like this is what you're looking for:
import React from "react";
import { render } from "react-dom";
import {
SearchBar,
Gallery,
Video,
Details,
Contact,
SimilarProd,
OtherProd
} from "./components/components";
class Product extends React.Component {
constructor(props) {
super(props);
// Set default values for state
this.state = {
data: {
a: 1,
b: 2,
c: 3,
d: 4
},
error: null,
isLoading: true
};
}
componentWillMount() {
this.loadData();
}
loadData() {
fetch('/detail.json')
.then(response => {
// if (response.ok) {
// return response.json();
// } else {
// throw new Error('Something went wrong ...');
// }
return Promise.resolve({
a: 5,
b: 6,
c: 7,
d: 8
});
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
if (this.state.error) return <h1>Error</h1>;
if (this.state.isLoading) return <h1>Loading</h1>;
const data = this.state.data;
return (
<div>
<SearchBar/>
<Gallery a={data.a} b={data.b} c={data.c} d={data.d} />
<Video a={data.a} b={data.b} c={data.c} d={data.d} />
<Details a={data.a} b={data.b} c={data.c} d={data.d} />
<Contact a={data.a} b={data.b} c={data.c} d={data.d} />
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
render(<Product />, document.getElementById("root"));
Working example here:
https://codesandbox.io/s/ymj07k6jrv
You API calls will be in the product component. Catering your need to best practices, I want to make sure that you are using an implementation of FLUX architecture for data flow. If not do visit phrontend
You should send you API calls in componentWillMount() having your state a loading indicator that will render a loader till the data is not fetched.
Each of your Components should be watching the state for their respective data. Let say you have a state like {loading:true, galleryData:{}, details:{}, simProducts:{}, otherProducts:{}}. In render the similar products component should render if it finds the respective data in state. What you have to do is to just update the state whenever you receive the data.
Here is the working code snippet:
ProductComponent:
import React from 'react';
import SampleStore from '/storepath/SampleStore';
export default class ParentComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
loading:true,
}
}
componentWillMount () {
//Bind Store or network callback function
this.handleResponse = this.handleResponse
//API call here.
}
handleResponse(response){
// check Response Validity and update state
// if you have multiple APIs so you can have a API request identifier that will tell you which data to expect.
if(response.err){
//retry or show error message
}else{
this.state.loading = false;
//set data here in state either for similar products or other products and just call setState(this.state)
this.state.similarProducts = response.data.simProds;
this.setState(this.state);
}
}
render () {
return(
<div>
{this.state.loading} ? <LoaderComponent/> :
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
{this.state.similarProducts && <SimilarProd data={this.state.similarProducts}/>}
{this.state.otherProducts && <OtherProd data={this.state.otherProducts}/>}
</div>
</div>
);
}
}
Just keep on setting the data in the state as soon as you are receiving it and render you components should be state aware.
In which component we need to make a call to API and how to set data
to other components.
The API call should be made in the Product component as explained in the other answers.Now for setting up data considering you need to make 3 API calls(Details, Similar Product, Other Products) what you can do is execute the below logic in componentDidMount() :
var apiRequest1 = fetch('/detail.json').then((response) => {
this.setState({detailData: response.json()})
return response.json();
});
var apiRequest2 = fetch('/similarProduct.json').then((response) => { //The endpoint I am just faking it
this.setState({similarProductData: response.json()})
return response.json();
});
var apiRequest3 = fetch('/otherProduct.json').then((response) => { //Same here
this.setState({otherProductData: response.json()})
return response.json();
});
Promise.all([apiRequest1,apiRequest2, apiRequest3]).then((data) => {
console.log(data) //It will be an array of response
//You can set the state here too.
});
Another shorter way will be:
const urls = ['details.json', 'similarProducts.json', 'otherProducts.json'];
// separate function to make code more clear
const grabContent = url => fetch(url).then(res => res.json())
Promise.all(urls.map(grabContent)).then((response) => {
this.setState({detailData: response[0]})
this.setState({similarProductData: response[1]})
this.setState({otherProductData: response[2]})
});
And then in your Product render() funtion you can pass the API data as
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details details={this.state.detailData}/>
<Contact/>
<SimilarProd similar={this.state.similarProductData}/>
<OtherProd other={this.state.otherProductData}/>
</div>
);
}
}
And in the respective component you can access the data as :
this.props.details //Considering in details component.

Resources