Issue iterating state from firebase in react - javascript

I have this component in react that get todo from firebase, build an array and set a state, but when I render the component I only can see the elements the first time, if I reload the page the state seems to be empty.
import React, { PureComponent } from 'react';
import firebase from 'firebase'
class Charts extends PureComponent {
constructor(props) {
super(props);
this.state = { data: [] };
this.todoRef = firebase.database().ref('Todo');
}
componentDidMount = () => {
var data = [];
this.todoRef.on('value', (snapshot) => {
const todos = snapshot.val();
for (let id in todos) {
data.push({ id, ...todos[id] });
}
});
this.setState({ data: data })
}
render() {
return <div>
{this.state.data.map(item => (
<p>{item.name}</p>
))}
</div>
}
}
export default Charts;
If I use console log I get an array(0) with elements inside. I have tried locating the setState in different life cicles methods but don't seems to work.

Issue
You are calling this.setState outside the snapshot handler, so you are only passing the empty ([]) data array to the state updater.
Solution
You should move the state update into the function processing the snapshot.
componentDidMount = () => {
const data = [];
this.todoRef.on('value', (snapshot) => {
const todos = snapshot.val();
for (let id in todos) {
data.push({ id, ...todos[id] });
}
this.setState({ data });
});
}

Related

How to do a for loop with React and why is my Component not getting rendered

import React, { Component } from "react";
import Pokecard from "./Pokecard";
import "./Pokedex.css";
class Pokedex extends Component {
static defaultProps = {
pokemon: [],
getData() {
for (let i = 1; i <= 40; i++) {
fetch(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then((response) => response.json())
.then((data) => {
this.pokemon.push({
id: data.id,
namePoke: data.name,
type: data.types[0].type.name,
base_experience: data.base_experience,
});
});
}
},
};
render() {
this.props.getData();
return (
<div className="Pokedex">
<div className="Pokedex-grid">
{this.props.pokemon.map((p) => (
<Pokecard
id={p.id}
name={p.namePoke}
type={p.type}
exp={p.base_experience}
key={p.id}
/>
))}
</div>
</div>
);
}
}
export default Pokedex;
I am new to React and I don't understand why my for loop runs multiple times so I get the Pokecards twice or more.
Also, the whole Component Pokedex is not showing up when reloading the page. What do I do wrong?
#azium made a great comment. You are calling to get data in your render, which is setting state, and causing a re-render, which is calling getData again, which is fetching data again and then setting state again, and the cycle continues on and on indefinitely. Also, default props should only define properties default values, but in this case you don't need a getData default prop. All you need to do is call the getData method in your componentDidMount. And your method needs to store the data in state, and not do a direct property change (like you are doing). Here is an example:
import React, { Component } from "react";
import Pokecard from "./Pokecard";
import "./Pokedex.css";
class Pokedex extends Component {
static state = {
pokemon: []
};
componentDidMount() {
this.getData();
}
getData() {
for (let i = 1; i <= 40; i++) {
const pokemon = [...this.state.pokemon];
fetch(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then((response) => response.json())
.then((data) => {
pokemon.push({
id: data.id,
namePoke: data.name,
type: data.types[0].type.name,
base_experience: data.base_experience,
});
});
this.setState({pokemon});
}
}
render() {
return (
<div className="Pokedex">
<div className="Pokedex-grid">
{this.state.pokemon.map((p) => (
<Pokecard
id={p.id}
name={p.namePoke}
type={p.type}
exp={p.base_experience}
key={p.id}
/>
))}
</div>
</div>
);
}
}
export default Pokedex;

React component does not re-render when updating state from Context

I am trying to rerender a component in React.
The setup:
I am using React Context to fetch some data from a Firestore database. So the fetching is Async.
My component is then fetching the data using: static contextType = MyContext and accessing via this.context
I store this context data on the components state to try to trigger a rerender whenever this data is changed.
I pass the data to a child component where it renders a list based on this data.
The problem:
I manage to update the state and even when debugging I can see my state updating to the correct data BUT the component does not rerender either the childcomponent or the list.
The expected list shows as soon as I click anything on the page so my guess is that the data is trapped in some kind of middle stage.
What I've tried:
I tried using the componentDidUpdate to make a check if the context is different than the current state and trigger a function that sets the state (I have even tried with setState function directly after the check) => Still state updates but no rerender is triggered (I can see the new data on state)
I tried using the getDerivedStateFromProps on the child component to do a check if the Props have changed and also tried storing the props in the child components own state => Still same result as before.
I am not sure what else to try, I thought that React triggers a rerender everytime state chages but probably I am doing something really wrong.
Here is my parent:
import React, { Component } from 'react';
import styles from './artistdropdown.module.css';
import { returnCollection } from '../../utils/Firebase.js';
import MyContext from '../../utils/MyContext.js';
import ArtistSelected from './ArtistSelected.js';
import ArtistsList from './ArtistsList';
export class ArtistDropdown extends Component {
static contextType = MyContext;
constructor(props) {
super(props);
this.state = {
artists: [],
currentArtist: {
id: null,
name: null
}
};
this.fetchArtist = (aId, artists) => {
const state = {
id: null,
name: null,
};
artists && artists.forEach((a) => {
if (a.id === aId) {
state = {
...state,
id: a.id,
name: a.name,
}
}
})
return state;
}
this.loadToState = (state) => {
this.setState({
...this.state,
...state,
})
}
this.click = (id) => {
this.context.handleArtistSelection(id);
this.props.handleBandDropdown();
}
}
componentDidMount() {
const aId = this.context.state.user.last_artist;
const artists = this.context.state.user.artists;
const currentArtist = this.fetchArtist(aId, artists);
const state = {
artists: artists,
currentArtist: currentArtist,
}
this.loadToState(state);
}
componentDidUpdate(props, state) {
if (this.state.artists !== this.context.state.user.artists) {
const aId = this.context.state.user.last_artist;
const artists = this.context.state.user.artists;
const currentArtist = this.fetchArtist(aId, artists);
const state = {
artists: artists,
currentArtist: currentArtist,
}
this.loadToState(state);
}
}
render() {
const bandDropdown = this.props.bandDropdown;
return (
<>
<ArtistSelected
currentBand={this.state.currentArtist.name}
handleDropdown={this.props.handleBandDropdown}
expanded={bandDropdown}
/>
<ul className={bandDropdown ? styles.band_options + ' ' + styles.expanded : styles.band_options}>
<ArtistsList artists={this.state.artists} />
</ul>
</>
)
}
}
export default ArtistDropdown
and here is my child:
import React, { Component } from 'react';
import MyContext from '../../utils/MyContext.js';
import ArtistItem from './ArtistItem.js';
export class ArtistsList extends Component {
static contextType = MyContext;
constructor(props) {
super(props);
this.state = {
artists: [],
};
this.loadToState = (state) => {
this.setState({
...this.state,
...state,
}, () => { console.log(this.state) })
}
}
componentDidMount() {
const artists = this.props.artists;
const state = {
artists: artists,
}
this.loadToState(state);
}
componentDidUpdate(props, state) {
if (state.artists !== this.state.artists) {
this.loadToState(state);
}
}
static getDerivedStateFromProps(props, state) {
if (props.artists !== state.artists) {
return {
artists: props.artists,
}
} else {
return null;
}
}
render() {
// const artistList = this.state.artists;
const artistList = this.state.artists;
const list = artistList && artistList.map((a) => {
return (<ArtistItem key={a.id} onClick={() => this.click(a.id)} name={a.name} />)
})
return (
<>
{list}
</>
)
}
}
export default ArtistsList

Updating state of a class inside a function in React

I am trying to update the state of this class with the array of objects stored in the variable childData. However, when I use setState({childData: childData)}, and use it later on by calling this.state.childData, it is undefined, so it never updates the states with the information.
class Users extends React.Component {
state = {
childData: ""
}
retrieve = () => {
let childData;
var leadsRef = database.ref('users');
leadsRef.on('value', function(snapshot) {
childData = snapshot.val();
console.log(childData)
this.setState({
childData: childData
})
});
}
componentDidMount() {
this.retrieve()
}
render() {
return(
<div>
<h3>These are all the users in the app</h3>
{console.log(this.state.childData)}
</div>
)
}
}
export default Users
You have a couple issues going on. First, you do indeed need to set state within the callback function. However, as is, you'll hit an infinite loop. That's because you shouldn't be performing the async function in the render method. Instead, do it in the componentDidMount method so it only fires when the component mounts.
class Users extends React.Component {
state = {
childData: ""
}
retrieve = () => {
let childData;
var leadsRef = database.ref('users');
leadsRef.on('value', snapshot => {
childData = snapshot.val();
console.log(childData)
this.setState({
childData: childData
})
});
}
componentDidMount() {
this.retrieve()
}
render() {
return(
<div>
<h3>These are all the users in the app</h3>
{console.log(this.state.childData)}
</div>
)
}
}
export default Users
Try setting state inside the leadsRef.on callback function. For example:
leadsRef.on('value', snapshot => {
const childData = snapshot.val()
this.setState({childData})
})
Use this.setState in your callback. The code you are executing is non blocking so this.setState will be executed before you retrieved childDate.
Also make you callback function an arrow function.
Is this helpfull, I am not sure if it is correct.

how to create a function that selects another object and refreshes in the browser

I am trying to create a function that when clicking the “next” button pass to another random object of my api. But I'm not able to do the function retrieve the other values and update the information in the browser.
App.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
state = {
data: [],
chosenPlanet: 0,
}
componentDidMount() {
const url = 'http://localhost:4000/results'
fetch(url)
.then(response => response.json())
.then(response => {
this.setState({
data: response.results,
})
})
}
renderPlanet = (event) => {
const planetRandom = Math.floor(Math.random() * this.state.data)
return planetRandom
}
render() {
const index = Math.floor(Math.random() * this.state.data.length)
const planet = this.state.data[index]
console.log(planet)
return (
<div className="App">
<div>{this.planet.name}</div>
<button onClick={this.renderPlanet}>Next</button>
</div>
)
}
}
export default App;
In event handlers, state must be updated. State updates causes component to update itself or re-render. In this case, since no state is changed, and only an update needs to be triggered, a forceUpdate can be used.
handleNext = (event) => {
this.forceUpdate();
}

React js filter not working correctly it won't return items when deleting characters

Im trying to create a filter function based on the user input. The filter works fine but it won't return items when I delete characters. I know it has something to do with updating the state. I hope someone can help me out.
import React, {Component} from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Fetch extends Component {
constructor(){
super();
this.state = {
data: []
}
this.handleChange = this.handleChange.bind(this)
}
handleChange = (event) => {
console.log(event.target.value);
return this.setState({data: this.state.data.filter(data => data.title.toLowerCase().includes(event.target.value.toLowerCase()))})
}
async componentDidMount() {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos`);
const json = await response.json();
this.setState({ data: json });
}
catch (error) {
console.error(error)
}
}
render() {
return(
<div>
<p><Link to={`/`}>Link to homepage</Link></p>
<form>
<input type="text" onChange={this.handleChange}></input>
</form>
<ul>
{
this.state.data.map(data => (
<li key={data.id}>{data.id} => {data.title}</li>
))
}
</ul>
</div>
)
}
}
export default Fetch;
It is because you don't keep the initial data obtained from the HTTP request. Here is the problem:
Initially state: data = []
ComponentDidMount: data = ['abc', 'bcd', 'cdf']
Filter for keyword b: data = ['abc', 'bcd'] (as cdf does not contain the letter b)
Erase the filter (filter = '') but your data variable has the value data = ['abc', 'bcd'], so it can return at most 2 values.
your code looks fine, but your filter function is overwriting the state's data property. I suggest storing the full array in data (as you are right now) and store the filtered results in another property of the state, something like this:
import React, {Component} from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Fetch extends Component {
constructor(){
super();
this.state = {
data: [],
filtered: [] // This will store your filtered elements from data
}
this.handleChange = this.handleChange.bind(this)
}
handleChange = (event) => {
console.log(event.target.value);
// Filter the array stored in data, but never update it.
// Update filtered instead.
return this.setState({filtered: this.state.data.filter(data => data.title.toLowerCase().includes(event.target.value.toLowerCase()))})
}
async componentDidMount() {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos`);
const json = await response.json();
// Keep the original data here.
this.setState({ data: json });
}
catch (error) {
console.error(error)
}
}
render() {
return(
<div>
<p><Link to={`/`}>Link to homepage</Link></p>
<form>
<input type="text" onChange={this.handleChange}></input>
</form>
<ul>
{
this.state.filtered.map(data => (
<li key={data.id}>{data.id} => {data.title}</li>
))
}
</ul>
</div>
)
}
}
export default Fetch;
Remember that filter don't modify the original array,it always returns a new one.
You can use the following solution to solve your problem:
this.setState({ data: this.state.data.filter(data => data.title.toLowerCase().indexOf(event.target.value.toLowerCase().trim() !== -1) ) })

Categories

Resources