Reactjs setState not working - reactjs

I'm running into an issue where my console is telling me setState is not defind. I'm not sure what I'm doing wrong. I preload the my object (data) in the constructor. I then pass it through to a table where it is displayed. When a user then selects an option from a dropdown it should only show the filtered data. I've bound the function in the constructor so I'm not sure what I've done wrong. Code is below:
import React, {Component} from ‘react’;
import moment from ‘moment’;
import {Table} from ‘reactstrap’;
import classnames from ‘classnames’;
class Tasks extends Component {
constructor(props){
super(props);
this.state = {
data: [
{
“task_number”:””,
“work_end”:”10/01/2017”
},
{
etc…
}
]
}
this.checkDate = this.checkDate.bind(this);
}
checkDate(e) {
let d = moment.format(“MM/DD/YYYY”);
let currentData = this.state.data;
currentData.filter(function(newData){
if(newData.work_end < d){
//comes back as undefined
this.setState({data: newData});
}
});
}
render(){
const { data } = this.state;
let myData = data.map((header, i){
return (<tr key = {i}>
<td>{data[i].task_number}</td>
<td>{data[i].work_end}</td>
</tr>)
});
return(
<div>
<table>
{myData}
</table>
<Input type=“select”
name=“assigngroup”
id=“assigngroup”
value={this.state.value}
onChange={this.checkDate}
/>
</div>
);
}
}
export default Tasks;
I've also tried creating a new function and passing it from checkDate, but that failed as well.

In Javascript, functions have their own scope so in checkDate this is your component but, inside the function that you use to filter your currentData, this is something else.
Your code should go like this:
currentData.filter(newData => {
if(newData.work_end < d){
this.setState({data: newData});
}
});
or just save this in a variable:
var self = this;
currentData.filter(function(newData){
if(newData.work_end < d){
self.setState({data: newData});
}
});

The filter method returns a new filtered array.
Try this:
checkDate(e) {
let d = moment().format(“MM/DD/YYYY”)
let currentData = this.state.data
let filteredData = currentData.filter(item => item.work_end < d)
this.setState({ data: filteredData })
}

Related

Initialize Empty Class Array and populate from fetch Fetch function React

I am trying to populate a dropdown with values from API. I declared empty array in react class but cannot assign the values to it. I cannot use it as state variables as I have to make lot of changes to previously developed code. The way I did the code it says options is not defined.
The partial code is posted below which is causing the problem. Any help is really appreciated.
export default class LoadLimits extends Component {
constructor(props){
super(props);
this.options = []
this.getZoneOptions = this.getZoneOptions.bind(this)
}
render(){
return (
<PtSelect label="Assigned Zone" options={options}
onChange={this.onChangeDropdown}
disabled={this.props.disabled}
defaultVal={this.state.assignedZone} name="assignedZone" />
)
}
getZoneOptions = () =>{
const zoneOptions = []
const keys = []
fetch(`${config.server}/getzoneOptions/`+this.props.ownModel.agencyId)
.then(response=>
{
return response.json();
})
.then(data=>{
for (var i =0;i<data[0].length;i++){
if (data[0][i]['Zone_key']!==998){
zoneOptions.push(data[0][i]['description'])
keys.push(data[0][i]['Zone_key'])
}
}
let dropOptions = zoneOptions.map((option,idx)=>{
return {key:keys[idx],value: option, label:option}
});
this.options = dropOptions
})
.catch(error=>{
console.log(error);
});
}
}
Issue
The options being passed to PtSelect is not defined.
<PtSelect
label="Assigned Zone"
options={options} // <-- should be this.options
onChange={this.onChangeDropdown}
disabled={this.props.disabled}
defaultVal={this.state.assignedZone}
name="assignedZone"
/>
Solution
If you need a variable to hold a value that you don't want coupled to the React component lifecycle then you should probably use a React ref.
Import createRef from 'react'.
Create a mutable ref for the options.
Implement the componentDidMount lifecycle method to populate and set the current value of the options.
Pass the current options value to the PtSelect component.
Code
import React, { Component, createRef } from 'react';
class LoadLimits extends Component {
constructor(props) {
super(props);
this.getZoneOptions = this.getZoneOptions.bind(this);
this.options = createRef([]);
}
getZoneOptions = () => {
const zoneOptions = [];
const keys = [];
fetch(`${config.server}/getzoneOptions/` + this.props.ownModel.agencyId)
.then((response) => {
return response.json();
})
.then((data) => {
for (var i = 0; i < data[0].length; i++) {
if (data[0][i]["Zone_key"] !== 998) {
zoneOptions.push(data[0][i]["description"]);
keys.push(data[0][i]["Zone_key"]);
}
}
const dropOptions = zoneOptions.map((option, idx) => {
return { key: keys[idx], value: option, label: option };
});
this.options.current = dropOptions;
})
.catch((error) => {
console.log(error);
});
};
componentDidMount() {
this.getZoneOptions();
}
render() {
return (
<PtSelect
label="Assigned Zone"
options={this.options.current}
onChange={this.onChangeDropdown}
disabled={this.props.disabled}
defaultVal={this.state.assignedZone}
name="assignedZone"
/>
);
}
}
Alternative Solution - Use forceUpdate (not strongly suggested)
In addition to addressing the this.options issue in PtSelect, you can use forceUpdate to tell React to rerender regardless of any state and/or prop update. This should rerender the select with populated options.
component.forceUpdate(callback)
By default, when your component’s state or props change, your
component will re-render. If your render() method depends on some
other data, you can tell React that the component needs re-rendering
by calling forceUpdate().
Calling forceUpdate() will cause render() to be called on the
component, skipping shouldComponentUpdate(). This will trigger the
normal lifecycle methods for child components, including the
shouldComponentUpdate() method of each child. React will still only
update the DOM if the markup changes.
Normally you should try to avoid all uses of forceUpdate() and only
read from this.props and this.state in render().
getZoneOptions = () => {
const zoneOptions = [];
const keys = [];
fetch(`https://jsonplaceholder.typicode.com/users`)
.then((response) => {
return response.json();
})
.then((data) => {
for (var i = 0; i < data.length; i++) {
zoneOptions.push(data[i]["name"]);
keys.push(data[i]["id"]);
}
let dropOptions = zoneOptions.map((option, idx) => {
return { key: keys[idx], value: option, label: option };
});
this.options = dropOptions;
console.log("Options ", this.options);
this.forceUpdate(); // <-- trigger a rerender
})
.catch((error) => {
console.log(error);
});
};

Redux - Fetch data, but render in another component

I'm currently fetching data in Component1, then dispatching an action to update the store with the response. The data can be seen in Component2 in this.props, but how can I render it when the response is returned? I need a way to reload the component when the data comes back.
Initially I had a series of functions run in componentDidMount but those are all executed before the data is returned to the Redux store from Component1. Is there some sort of async/await style between components?
class Component1 extends React.Component {
componentDidMount() {
this.retrieveData()
}
retrieveData = async () => {
let res = await axios.get('url')
updateParam(res.data) // Redux action creator
}
}
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
sortData = props => {
const { param } = this.props
let result = param.sort((a,b) => a - b)
}
}
mapStateToProps = state => {
return { param: state.param }
}
connect(mapStateToProps)(Component2)
In Component2, this.props is undefined initially because the data has not yet returned. By the time it is returned, the component will not rerender despite this.props being populated with data.
Assuming updateParam action creator is correctly wrapped in call to dispatch in mapDispatchToProps in the connect HOC AND properly accessed from props in Component1, then I suggest checking/comparing props with previous props in componentDidUpdate and calling sortData if specifically the param prop value updated.
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
componentDidUpdate(prevProps) {
const { param } = this.props;
if (prevProps.param !== param) { // <-- if param prop updated, sort
this.sortData();
}
}
sortData = () => {
const { param } = this.props
let result = param.sort((a, b) => a - b));
// do something with result
}
}
mapStateToProps = state => ({
param: state.param,
});
connect(mapStateToProps)(Component2);
EDIT
Given component code from repository
let appointmentDates: object = {};
class Appointments extends React.Component<ApptProps> {
componentDidUpdate(prevProps: any) {
if (prevProps.apptList !== this.props.apptList) {
appointmentDates = {};
this.setAppointmentDates();
this.sortAppointmentsByDate();
this.forceUpdate();
}
}
setAppointmentDates = () => {
const { date } = this.props;
for (let i = 0; i < 5; i++) {
const d = new Date(
new Date(date).setDate(new Date(date).getDate() + i)
);
let month = new Date(d).toLocaleString("default", {
month: "long"
});
let dateOfMonth = new Date(d).getDate();
let dayOfWeek = new Date(d).toLocaleString("default", {
weekday: "short"
});
// #ts-ignore
appointmentDates[dayOfWeek + ". " + month + " " + dateOfMonth] = [];
}
};
sortAppointmentsByDate = () => {
const { apptList } = this.props;
let dates: string[] = [];
dates = Object.keys(appointmentDates);
apptList.map((appt: AppointmentQuery) => {
return dates.map(date => {
if (
new Date(appt.appointmentTime).getDate().toString() ===
// #ts-ignore
date.match(/\d+/)[0]
) {
// #ts-ignore
appointmentDates[date].push(appt);
}
return null;
});
});
};
render() {
let list: any = appointmentDates;
return (
<section id="appointmentContainer">
{Object.keys(appointmentDates).map(date => {
return (
<div className="appointmentDateColumn" key={date}>
<span className="appointmentDate">{date}</span>
{list[date].map(
(apptInfo: AppointmentQuery, i: number) => {
return (
<AppointmentCard
key={i}
apptInfo={apptInfo}
/>
);
}
)}
</div>
);
})}
</section>
);
}
}
appointmentDates should really be a local component state object, then when you update it in a lifecycle function react will correctly rerender and you won't need to force anything. OR since you aren't doing anything other than computing formatted data to render, Appointments should just call setAppointmentDates and sortAppointmentsByDate in the render function.

JSON Array mapping in ReactJS from request

Currently i'm rewriting a class component to a function component. I need to do this since i need to use the useSelector hook from redux. Now i'm getting pretty close but i'm having some trouble with the json array getting mapped. It's letting me know it's not a function. In the fetch i'm logging the leaderboard which has returned. This gives me the json i was expecting.
[
{
"ID": 1,
"teamName": "Developers",
"time": "19:54"
},
{
"ID": 1591621934400,
"teamName": "h435hfg",
"time": "19:54"
}
]
Then here is my code that im having trouble with:
import React, {useEffect, useState} from 'react';
import '../style/App.scss';
import {useSelector} from "react-redux";
function Leaderboard() {
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
const [leaderboard, setLeaderboard] = useState([]);
const timerState = useSelector(state => state.timerState);
useEffect(() => {
socket.emit("addTeamToLeaderboard", getTeam());
fetch('http://localhost:3000/leaderboard')
.then(response => response.json())
.then(leaderboard => {
leaderboard.push(getTeam()); // this is just so your team score renders the first time
setLeaderboard({leaderboard})
console.log(leaderboard)
});
}, [socket]);
const getTeam = () => {
let team = JSON.parse(sessionStorage.getItem('currentTeam')) ;
team.time = timerState;
return team;
}
const leaderboardElements = leaderboard.map((data, key) => {
return (
<tr key={key} className={ data.ID === getTeam().ID ? "currentTeam" : "" }>
<td>{data.teamName}</td>
<td>{data.time}</td>
</tr>
)
})
return (
<div>
<h1>Leaderboard</h1>
<table className="leaderboard">
<tr>
<th>Team</th>
<th>Time</th>
</tr>
{leaderboardElements}
</table>
</div>
);
}
export default Leaderboard;
The old code which im rewriting:
import React from 'react';
import '../style/App.scss';
class Leaderboard extends React.Component {
state = {
leaderboard: []
}
compare(a, b) {
if (a.time < b.time) {
return -1;
}
if (a.time > b.time) {
return 1;
}
return 0;
}
getTeam(){
let team = JSON.parse(sessionStorage.getItem('currentTeam')) ;
team.time = 12.13; //Todo add actual playing time
return team;
}
componentDidMount() {
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
socket.emit("addTeamToLeaderboard", this.getTeam());
fetch('http://localhost:3000/leaderboard')
.then(response => response.json())
.then(leaderboard => {
leaderboard.push(this.getTeam()); // this is just so your team score renders the first time
this.setState({ leaderboard })
});
}
render() {
return (
<div>
<h1>Leaderboard</h1>
<table className="leaderboard">
<tr>
<th>Team</th>
<th>Time</th>
</tr>
{
this.state.leaderboard.sort(this.compare).map((data, key) => {
return (
<tr key={key} className={ data.ID == this.getTeam().ID ? "currentTeam" : "" }>
<td>{data.teamName}</td>
<td>{data.time}</td>
</tr>
)
})
}
</table>
</div>
);
}
}
export default Leaderboard;
I'm not following why you are changing leaderboard data type. If it is an array you shouldn't do setLeaderboard({leaderboard}) because you are assigning an object to the state.
You should pass a new array to the setLeaderboard like:
setLeaderboard([...leaderboard]);
Also if you do
setLeaderboard([...leaderboard]);
console.log(leaderboard);
You will not get the updated state right in the log, because set state is an asynchronous call.
Another tip, I would highly recommend you to put the socket connection not in the useEffect function, put outside the functional component.
const io = require('socket.io-client');
const socket = io.connect("http://localhost:3001/", {
reconnection: false
});
function Leaderboard() {
...
}
It's letting me know it's not a function
/* fetch data */
leaderboard.push(getTeam());
setLeaderboard({leaderboard}) // => change to setLeaderboard(leaderboard.concat(getTeam()))
console.log(leaderboard)
/* other functions below */
the difference between setState and the setLeaderboard that is returned from useState is that (when giving none callback argument)
setState expects an object with {[key: stateYouAreChanging]: [value: newState],
setLeaderboard expects the newStatValue as the argument.
So your code above is setting leaderboard state to be an object with that looks like this
leaderboard = {
leaderboard: NEW_LEADERBOARD_FETCHED_FROM_REQUEST
}

Trying to use function in a react stateless class component

I am trying to build a react app for weather. I am using openweathermap api.
i have a component that supposed to render 5 weather components, each one for each day (it's a 5 day forecast).
however, something is not working.
this is the weather component:
class Weather extends Component{
getAllDates = () =>{
const list = this.props.list;
let date = list[0]["dt_txt"].split(" ")[0];
const datesList = [{date: date, indexes: [0]}];
let indexes = [];
for(let i = 1; i < list.length; i++){
const currDate = list[i]["dt_txt"].split(" ")[0];
if(date !== currDate){
datesList.push({
date: currDate,
indexes: indexes});
date = currDate;
indexes = [];
}
else{
const toUpdate = datesList.pop();
const oldIndexes = toUpdate.indexes;
const newIndexes = oldIndexes.push(i);
toUpdate.indexes = newIndexes;
datesList.push(toUpdate);
}
}
return datesList;
}
render(){
const datesList = this.getAllDates();
return(
<React.Fragment>
{datesList.map((date, key) => {
return <DayWeather date = {date}
key = {key}
forecastList = {this.props.list} />
})}
</React.Fragment>
)
}
and in App.js I am rendering it conditionally like this:
{this.state.data === undefined ? <h1>Choose City</h1> :
<Weather list = {this.state.data.list}/>}
The problem is, the getAllDates is called twice from the render function in Weather component.
the loop goes for 2 iterations instead of 40, and then getAllDates is called again. I can't understand why because I have conditional rendering in App.js.
Also, the indexes variable at some point turns into a number and not an array,
and then I get an error that I can't do .push to indexes.
I have no idea what is hapenning here,
would appreciate any kind of help!
thank you!
**here I am fetching the data:
class App extends Component{
constructor(){
super();
this.state = {
data: undefined
}
}
onSelectHandler = (event) =>{
const city = event.target.value;
const apiCall = `http://api.openweathermap.org/data/2.5/forecast?q=${city},il&APPID=${API_KEY}`
fetch(apiCall)
.then(response => response.json())
.then(response =>
{
this.setState({
data: response
})
}
)
}

Cannot access array inside of map function to return props to different component

I am fetching data from an API. I am building an array of 5 objects using the API call. What I am trying to do is iterate over the array, use the data inside each array index to build a component and pass along the props to another component.
I've tried accessing the element the same way I normally would by doing:
img={pokemon.name} but it keeps returning undefined. When I type in
console.log(pokemon) I get the individual pokemon stored within the array of objects.
import React, { Component } from "react";
import Pokecard from "./Pokecard";
async function getPokemon() {
const randomID = Math.floor(Math.random() * 151) + 1;
const pokeRes = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomID}/`);
const pokemonJSON = await pokeRes.json();
return pokemonJSON;
}
function buildPokemon() {
let pokemonArr = [];
let builtPokemon = {};
getPokemon()
.then(data => {
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
pokemonArr.push(builtPokemon);
})
.catch(err => console.log(err));
return pokemonArr;
}
class Pokedex extends Component {
constructor(props) {
super(props);
this.state = { pokemonArr: [] };
}
componentDidMount() {
const pokemonArr = [];
for (let i = 0; i < 5; i++) {
pokemonArr.push(buildPokemon());
}
this.setState({ pokemonArr });
}
render() {
console.log(this.state.pokemonArr);
return (
<div className="Pokedex">
{this.state.pokemonArr.map(pokemon => console.log(pokemon))}
</div>
);
}
}
export default Pokedex;
What should happen is that when I map the pokemonArr I want to create 5 separate pokemon by doing
this.state.pokemonArr.map(pokemon => <Pokecard name={pokemon.name} but I keep getting undefined whenever I check this.props in the Pokecard component.
I think my buildPokemon() function is working because when I call it in the componentDidMount() and then I console.log this.state.pokemonArr in the render() function, I actually get an array returned with 5 different pokemon with the proper fields filled out.
And also when I map out this.state.pokemonArr.map(pokemon => clg(pokemon)), it actually displays each individual pokemon. When I pass the pokemon item into a component like this
<Pokecard name={pokemon}/>, I see all the pokemon data.
when I type <Pokecard name={pokemon.name} I get undefined
There are several problems with your approach but the main one is that getPokemon() is asynchronous.
Return the getPokemon() promise from buildPokemon() and return the object from it's then()
In your for() loop create an array of these promises and use Promise.all() to set state once they have all resolved
function buildPokemon() {
let builtPokemon = {};
// return the promise
return getPokemon()
.then(data => {
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
// return the object
return builtPokemon
});
}
componentDidMount() {
const pokemonPromises = [];
for (let i = 0; i < 5; i++) {
pokemonPromises.push(buildPokemon());
}
Promise.all(pokemonPromises).then(pokemonArr => this.setState({ pokemonArr }));
}
componentDidMount executes after first render, initially your state is pokemonArr: [] (whch is empty) so you are getting an error. You need to conditionally render like,
{this.state.pokemonArr.length > 0 && this.state.pokemonArr.map(pokemon => console.log(pokemon))}
Side Note:
In buildPokemon function you are returning an array, and again in componentDidMount you are storing it in array which creates array of array's, you just need to return object from buildPokemon function.
The problem is mainly how the Promise should be resolved.
The data isn't available right away so the state (pokemonArr) should only be set once data is available.
Here's the refactored component:
class Pokedex extends Component {
constructor(props) {
super(props);
this.state = { pokemonArr: [] };
}
componentDidMount() {
for (let i = 0; i < 5; i++) {
this.getPokemon()
.then((pokemon) => this.buildPokemon(pokemon));
}
}
async getPokemon() {
const randomID = Math.floor(Math.random() * 151) + 1;
const pokeRes = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomID}/`);
return pokeRes.json();
}
setPokemon(pokemon) {
this.setState({
pokemonArr: [
...this.state.pokemonArr, pokemon
],
});
}
buildPokemon(data) {
let builtPokemon = {};
builtPokemon.name = data.forms[0].name;
builtPokemon.exp = data.base_experience;
builtPokemon.img = data.sprites.front_default;
builtPokemon.type = data.types[0].type.name;
this.setPokemon(builtPokemon);
}
render() {
return (
<div className="Pokedex">
{this.state.pokemonArr.map(pokemon => console.log(pokemon))}
</div>
);
}
}

Resources