State in react component won't render - javascript

My react component won't load the data from the state, at all.
My loading function works as expected, as well as the rendering for it, however, even though the state updates (I logged it, it does return the expected data) nothing with render related to it.
If posts are empty, the <p>nothing</> tag does not show, and if there is data, it's not printed in the p tag nor is it loaded into my carousel.
import React, { Component } from 'react';
import { withFirebase } from '../Firebase';
import AliceCarousel from 'react-alice-carousel';
import 'react-alice-carousel/lib/alice-carousel.css';
import PostItem from '../Market/PostItem';
class LandingPosts extends Component {
constructor(props) {
super(props);
this.state = {
text: '',
loading: false,
posts: [],
limit: 5,
};
}
componentDidMount() {
this.onListenForMessages();
}
onListenForMessages = () => {
this.setState({ loading: true });
this.props.firebase
.collectionGroup('settings')
.where('homepagepost', '==', true)
.get().then(snapshot => {
let posts = [];
snapshot.forEach(doc => {
doc.ref.parent.parent.get().then(doc => {
posts.push({ ...doc.data(), uid: doc.id });
console.log(posts);
});
});
this.setState({ posts: posts.reverse(), loading: false });
});
};
responsive = {
0: { items: 1 },
1024: { items: 3 },
};
render() {
const { loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{this.state.posts && (
<p>{this.state.posts[0]}</p>
)}
{!this.state.posts && (
<p>nothing</p>
)}
<AliceCarousel
items={this.state.posts.map(item => {return <PostItem data={item}/>})}
responsive={this.responsive}
autoPlayInterval={2000}
autoPlayDirection="rtl"
autoPlay={true}
fadeOutAnimation={true}
mouseDragEnabled={true}
disableAutoPlayOnAction={true}
buttonsDisabled={true}
/>
</div>
);
}
}
export default withFirebase(LandingPosts);

I think, following code is async in in your case.
doc.ref.parent.parent.get().then(doc => {
posts.push({ ...doc.data(), uid: doc.id });
console.log(posts);
});
If so try adding setting state in then or create array of promise like this.
posts.push(
doc.ref.parent.parent.get().then(doc => {
posts.push({ ...doc.data(), uid: doc.id });
console.log(posts);
});
)
Promise.all(posts).then((_posts)=>this.setState({ posts: _posts.reverse(), loading: false });)

I think you have to "repeat" your state declaration inside render.
Like this:
const {
text,
loading,
posts,
limit
} = this.state
At least that's how I have it in my components

Related

items not being displayed on table

I've been trying to create this search app where I can display the items in a table and delete items using react redux. However, on the initial load, the app shows a table but there is no data in the table. It's an empty table. If i search for another movie name which have more than one movie for that search term, then 2 tables would be shown but I want to show everything on the same table itself. The delete button is not working as well. Is there something wrong with my action and reducer files?
Action.js
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "./types";
const fetchMoviePendig = () => ({
type: FETCH_MOVIE_PENDING
});
const fetchMovieSuccess = json => ({
type: FETCH_MOVIE_SUCCESS,
payload: json
});
const fetchMovieError = error => ({
type: FETCH_MOVIE_ERROR,
payload: error
});
export const fetchMovie = name => {
return async dispatch => {
dispatch(fetchMoviePendig());
try {
const url = `https://jsonmock.hackerrank.com/api/movies/search/?Title=${name}`;
const response = await fetch(url);
const result = await response.json(response);
console.log(result);
dispatch(fetchMovieSuccess(result.data));
} catch (error) {
dispatch(fetchMovieError(error));
}
};
};
export const deleteEvent = id => async dispatch => {
try {
dispatch({
type: DELETE_MOVIE,
payload: id
});
} catch (err) {
console.log(err);
}
};
Reducer
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "../action/types";
const initialState = {
data: [],
loading: false,
error: ""
};
const moviesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MOVIE_PENDING:
return {
...state,
loading: true
};
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: [...state.data, action.payload]
};
case FETCH_MOVIE_ERROR:
return {
...state,
loading: false,
error: action.payload
};
case DELETE_MOVIE:
return {
...state,
data: state.data.filter(movie => movie.id !== action.payload)
};
default:
return state;
}
};
export default moviesReducer;
App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMovie } from "./action/movieActions";
import Input from "./components/Input";
import MovieTable from "./components/MovieTable";
class App extends Component {
state = {
searchInput: "The Rain"
};
componentDidMount() {
this.props.getMovieList(this.state.searchInput);
}
_getMovie = () => {
this.props.getMovieList(this.state.searchInput);
};
_onChangeHandler = e => {
this.setState({
searchInput: e.target.value
});
console.log(this.state.searchInput);
};
render() {
const { data, loading } = this.props.movies;
return (
<div className="center">
<div>
<h2 className="center white-text">Movie Search</h2>
</div>
<div className="container">
<Input
value={this.state.searchInput}
onChange={this._onChangeHandler}
onClick={this._getMovie}
/>
<div className="row">
{loading ? (
<p>Loading</p>
) : (
data.map(item => (
<MovieTable
key={item.imdbID}
year={item.Year}
name={item.Title}
movieId={item.imdbId}
/>
))
)}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
movies: state.movies
};
};
const mapDispatchToProps = dispatch => {
return {
getMovieList: name => dispatch(fetchMovie(name))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Hello please take a look at the sandbox : https://codesandbox.io/s/prod-wind-4hgq2?file=/src/App.js
I have edited
<MovieTable
data={data.map(d => ({
year: d.Year,
name: d.Title,
movieId: d.imdbId
}))}
/>
and
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: action.payload
};
And ... Currently the delete button has no event, that's why it can't work
I see data having the following pattern:
Object {page: 1, per_page: 10, total: 1, total_pages: 1, data: Array[1]}
page: 1
per_page: 10
total: 1
total_pages: 1
data: Array[1]
0: Object
Title: "Sin in the Rain"
Year: 2006
imdbID: "tt1072449"
And you are accessing wrong properties in the component render logic, can you fix that.
Duplicate table is created the way you have written the logic.
Pass the data to MovieTable component and let it render and create the table
and fill it.
In reducer (FETCH_MOVIE_SUCCESS) you need don't need to append data you have to
replace or use the current movie data only.

How to properly time data rendering in react?

I am attempting to pull data from Open Data to put together a quick heat map. In the process, I want to add some stats. Almost everything runs well in that I have the data and am able to render the map, but I am unsure how to deal with calculations once I get the data since it takes time for data to come in. How do I set things up so that I can run a function on a state variable if it hasn't necessarily received data yet? Currently I am getting a null as the number that is passed as props to StatCard.
Below are my attempts:
App.js
import React, { Component } from 'react';
import Leaf from './Leaf';
import Dates from './Dates';
import StatCard from './StatCard';
import classes from './app.module.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data:[],
cleanData:[],
dateInput: '2019-10-01',
loading: false,
totalInspections: null,
calculate: false
};
}
componentDidMount() {
try {
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
fetchData=()=>{
const requestData = async () => {
await fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({ data: res, loading: true})
)
}
const calculateInspections = () => {
this.setState({totalInspections: this.state.data.length})
}
//call the function
requestData();
if(this.state.data) {
calculateInspections();
}
}
handleDateInput = (e) => {
console.log(e.target.value);
this.setState({dateInput:e.target.value, loading: false}) //update state with the new date value
this.updateData();
//this.processGraph(e.target.value)
}
updateData =() => {
this.fetchData();
}
LoadingMessage=()=> {
return (
<div className={classes.splash_screen}>
<div className={classes.loader}></div>
</div>
);
}
//inspection_date >= '${this.state.dateInput}'&
// https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=inspection_date >= '2019-10-10T12:00:00'
render() {
return (
<div>
<div>{!this.state.loading ?
this.LoadingMessage() :
<div></div>}
</div>
{this.state.totalInspections && <StatCard totalInspections={this.state.totalInspections} /> }
<Dates handleDateInput={this.handleDateInput}/>
<Leaf data={this.state.data} />
</div>
);
}
}
export default App;
StatCard.js
import React from 'react';
const StatCard = ( props ) => {
return (
<div >
{ `Total Inspections: ${props.totalInspections}`}
</div>
)
};
export default StatCard;
Attempt Repair
componentDidMount() {
try {
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
componentDidUpdate () {
if(this.state.data) {
this.setState({totalInspections: this.state.data.length})
}
}
fetchData= async ()=>{
const requestData = () => {
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({ data: res, loading: true})
)
}
//call the function
await requestData();
}
So your problem is that isLoading state needs to be set synchronously before any async calls.
So in your componentDidMount:
componentDidMount() {
try {
this.setState({ loading: true }); // YOU NEED TO SET TRUE HERE
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
This ensures loading as soon as you make the call.
Then your call is made and that part is asynchronous.
As soon as data comes through, the loading is done:
.then(data => {
this.setState({
data: data,
loading: false, // THIS NEEDS TO BE FALSE
totalInspections: this.state.data.length
})
})
Furthermore, your render method can have multiple return statements. Instead of having conditional JSX, return your loading layout:
render() {
if (this.state.loading) {
return <div> I am loading </div>
}
return <div> Proper Content </div>;
}
Only render <StatCard /> if you have the data you need:
{this.state.totalInspections && <StatCard totalInspections={this.state.totalInspections} /> }
First of all, I don't think you need a separate function calculateInspections(). You can put that logic in the then callback.
fetchData = () => {
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(data => {
this.setState({
data: data,
loading: true,
totalInspections: this.state.data.length
})
})
}
Secondly, setting this.state.totalInspections is effectively redundant, since you can simple do:
{this.state.data && <StatCard totalInspections={this.state.data.length} /> }
Lastly, avoid using componentDidUpdate() hook when you're new to react. Most of the time you end up shooting yourself in the foot.
Currently your Attempt Repair just got you into an infinite render loop. This happens because whenever you call setState(), it'll call componentDidUpdate() lifecycle hook after rendering. But within componentDidUpdate() you call again setState(), which induces a follow-up call to the same lifecycle hook, and thus the loop goes on and on.
If you must use componentDidUpdate() and call setState() inside, rule of thumbs, always put a stop-condition ahead of it. In you case, it'll be:
componentDidUpdate () {
if (this.state.data) {
if (this.state.totalInspections !== this.state.data.length) {
this.setState({ totalInspections: this.state.data.length })
}
}
}
Here is my solution.
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
dateInput: '2019-10-01',
loading: false,
error: false
};
}
async componentDidMount() {
try {
await this.fetchData(this.state.dateInput);
} catch (err) {
this.setState({ loading: false, error: true });
}
}
fetchData = (date) => new Promise(resolve => {
this.setState({ loading: true });
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${date}'&$limit=50000`)
.then(res => res.json())
.then(res => {
this.setState({ data: res, loading: false, error: false });
resolve(res.data);
});
})
handleDateInput = e => {
this.setState({ dateInput: e.target.value }) //update state with the new date value
this.fetchData(e.target.value);
}
render() {
const { loading, data } = this.state;
return (
<div>
{loading && (
<div className={classes.splash_screen}>
<div className={classes.loader}></div>
</div>
)}
{data && <StatCard totalInspections={data.length} />}
<Dates handleDateInput={this.handleDateInput} />
<Leaf data={data} />
</div>
);
}
}
There are two ways of achieving this:
You can put calculator in componentDidUpdate() and write a condition to just calculate once
componentDidUpdate(prevProps, prevState) {
const data = this.state.data;
// this line check if we have data or we have new data,
// calculate length once
if (data.length || !isEqual(data, prevState.data)) {
calculateInspections()
}
}
// isEqual() is a lodash function to compare two object or array
You can stop your rendering until data is fetched
async componentDidMount() {
await fetchData()
}
fetchData = () => {
const requestData = async() => {
await fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({
data: res,
loading: true,
totalInspections: res.length
})
)
}
// in above situation you just setState when you are sure
// that data has come
//call the function
requestData();
}

Error rendering item from state array in React

I have a problem getting state data in the render function.
It works fine and returns the array when I use
console.log(items)
But trying to get first item from the array yields an error
console.log(items[0])
Full code is:
import React from "react";
import StatsSection from "./../components/StatsSection";
import { db } from "./../util/database";
class IndexPage extends React.Component {
constructor(props) {
console.log(props)
super(props);
this.state = {};
}
componentDidMount() {
var data = []
db.collection('test')
.get().then(snapshot => {
snapshot.forEach(doc => {
data.push(doc.data())
})
})
this.setState({
items: data
});
}
render() {
const { items } = this.state
console.log(items[0])
return (
<StatsSection
color="white"
size="medium"
backgroundImage=""
backgroundImageOpacity={1}
items={[
{
title: "Following",
stat: "123"
},
{
title: "Followers",
stat: "456k"
},
{
title: "Likes",
stat: "789"
}
]}
/>
);
}
}
export default IndexPage;
Where am I making the mistake?
You're only setting one item, so items is actually just one item and items[0] fails.
this.setState({
items: data
});
should be inside the .then() so that it only runs after all the items are populated with the .forEach().
Update your componentDidMount() like so:
componentDidMount() {
db.collection('test').get().then(snapshot => {
var data = []
snapshot.forEach(doc => {
data.push(doc.data())
})
this.setState({ items: data })
})
}

How to manually remove Firebase firestore listeners?

I understand from this SO answer, that we must manually remove Firebase listeners. How can I do that in the following use case? My successful attempt is shown in the below code.
I tried to use some of the ideas from this answer too. But unsuccessfully.
What am I doing wrong?
import React, { Component } from 'react';
// redacted for brevity
import firebase from '#firebase/app';
import '#firebase/firestore';
class CRUDContainer extends Component {
state = {
items: [],
path: null,
isError: false,
isLoading: true,
};
componentWillUnmount () {
// cancel subscriptions and async tasks to stop memory leaks
this.unsubscribe(this.path);
}
unsubscribe = path => path && firebase.firestore().collection(path).onSnapshot(() => {})
getItems = path => {
const out = [];
const db = firebase.firestore();
if(!db) return;
db.collection(path)
.orderBy('timestamp', 'desc')
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
out.push(doc.data());
});
return out;
})
.then(result => {
const newState = {
path,
items: result,
isError: false,
isLoading: false,
};
this.setState(newState);
return result;
})
.then(() => {
this.unsubscribe(path);
return path;
})
.catch(error => {
console.error('Error getting documents: \n', error);
const newState = {
isError: true,
isLoading: false,
};
this.setState(newState);
});
};
Child = ({ match: { params: { id }}}) => {
// redacted for brevity
getItems(path);
return (
this.state.isLoading
?
<Loading/>
:
(
this.state.isError
?
<ErrorMessage/>
:
(items && (
<CRUDView items={items} />
)))
)
};
render() {
return <Route path="/:id" component={this.Child} />
}
}
export default CRUDContainer;

React Input Field logging empty string as first keystroke

I'm not sure what I'm doing wrong, but I have an input field for entering a search term and trying to filter results based on the search term. The problem is that the first value being passed is an empty string and input is offset by 1 item for each keypress after that. For example, if I type 'sea', it would update the search term to be ' se'. Then, when I try to delete the value, it is offset the other direction, so deleting ' se' ends with 's', which can't be deleted.
(Here's a link to the app in progress: https://vibrant-yonath-715bf2.netlify.com/allpokemon. The full search functionality isn't working quite yet. I'm pretty new at this.)
import React, { Component } from 'react';
import Pokemon from './Pokemon';
class PokemonList extends Component {
constructor(props) {
super(props);
this.state = {
pokemonList: [],
searchTerm: '',
fetched: false,
loading: false
};
this.updateResults = this.updateResults.bind(this);
}
componentWillMount() {
this.setState({
loading: true
});
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(res => res.json())
.then(response => {
this.setState({
pokemonList: response.results,
loading: true,
fetched: true
});
});
}
handleSearchTermChange = (
event: SyntheticKeyboardEvent & { target: HTMLInputElement }
) => {
this.setState({ searchTerm: event.target.value });
this.updateResults();
};
updateResults() {
const filteredList = this.state.pokemonList.filter(
pokemon =>
pokemon.name.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0
);
console.log(this.state.searchTerm);
this.setState({
pokemonList: filteredList
});
}
render() {
const { fetched, loading, pokemonList } = this.state;
let content;
if (fetched) {
content = (
<div className="flex-grid">
{pokemonList.map((pokemon, index) => (
<Pokemon key={pokemon.name} id={index + 1} pokemon={pokemon} />
))}
</div>
);
} else if (loading && !fetched) {
content = <p> Loading ...</p>;
} else {
content = <div />;
}
return (
<div>
<input
onChange={this.handleSearchTermChange}
value={this.state.searchTerm}
type="text"
placeholder="Search"
/>
{content}
</div>
);
}
}
export default PokemonList;
setState is asynchronous, so your this.state.searchTerm is not updated when you call updateResults. You could e.g. filter the array in render instead.
Example
class App extends Component {
state = {
pokemonList: [
{ name: "pikachu" },
{ name: "bulbasaur" },
{ name: "squirtle" }
],
searchTerm: ""
};
changeSearchTerm = event => {
this.setState({ searchTerm: event.target.value });
};
render() {
const { pokemonList, searchTerm } = this.state;
const filteredList = pokemonList.filter(pokemon =>
pokemon.name.toUpperCase().includes(searchTerm.toUpperCase())
);
return (
<div>
<input value={searchTerm} onChange={this.changeSearchTerm} />
{filteredList.map(pokemon => <div>{pokemon.name}</div>)}
</div>
);
}
}
I think the problem is that you call this.updateResults();
and then calling this.setState({ searchTerm: event.target.value }); instead of using the callback function for setState.
For example:
this.setState({ searchTerm: event.target.value }, () => this.updateResults());
Hope I got it right.
Update:
Also I see many problems in your code, for example, why you update the list with a filtered list? you don't need to do that:
this.setState({
pokemonList: filteredList
});
Instead of updating the results in the state, you simply need to render the filtered list... meaning your state stay with the original list, also your filterd value, just in the render you pass the filtered list..

Categories