React: Can't access array data in component state with indices - javascript

As a practice project, I've started building a small Pokedex app in React.
import React, { Component} from 'react';
import './App.css';
import Card from './components/card/Card.component';
class App extends Component{
constructor(){
super();
this.state = {}
}
componentDidMount(){
let pokeDataArr = []
const getPokemonData = async() => {
const dataResponse = await fetch(
'https://pokeapi.co/api/v2/pokemon?limit=10'
);
const dataArr = await dataResponse.json();
const dataArr2 = await dataArr.results.forEach(i => {
fetch(i.url)
.then(dataResponse => dataResponse.json())
.then(json => pokeDataArr.push(json))
})
this.setState({ pokeDataArr }, () => console.log(this.state))
}
getPokemonData();
}
render(){
return(
<div>Pokedex!</div>
)
}
}
I'm having trouble accessing data from a specific index in an array.
When I log the entire state object to the console, I can see all the data I have retrieved from the AJAX call.
this.setState({ pokeDataArr }, () => console.log(this.state))
And this is the result in the console:
console result
However, if I try to log out data from an index in the array with:
this.setState({ pokeDataArr }, () => console.log(this.state.pokeDataArr[0]))
I get "undefined" in the console:
console result 2
As far as I'm aware, whatever function you run in the this.setState method's callback, it should run after setState has finished.
My goal is to use the data from this.state.pokeDataArr to make cards that display the info of each individual pokemon, but it seems like I'm stuck until I find a way to extract the data from the array and I have no clue what I'm missing.
Thank you for your time.

I think you messed up with your react state.
Usually, what people do is they set up their react state as an object with other elements (arrays, objects, strings, whatever) inside it. This looks something like this:
constructor(){
super();
this.state = {
myObject: {},
somethingElse: "",
anArray: []
}
}
This enables you to access parts of your state like this: this.state.myObject for instance. (this would return {})
In your example, you defined your state as an empty object.
constructor(){
super();
this.state = {}
}
And later, you set this object to an object, with an array inside:
this.setState({ pokeDataArr });
This will set your state to this: {[(your array)]}
To prevent this initialize your state like this:
constructor(){
super();
this.state = { pokeDataArr : {} }
}
And set your values like this:
this.setState({ pokeDataArr: pokeDataArr }, () => console.log(this.state.pokeDataArr[0]))
read more here: https://reactjs.org/docs/state-and-lifecycle.html

You'll need to use updater to use the callback instead of plain state update:
this.setState(
() => ({ pokeDataArr }),
() => console.log(this.state.pokeDataArr[0])
)
Read the note from the docs in the linked example:
Subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once. If the next state depends on the current state, we recommend using the updater function form

Related

React - Render page after props receive new data

I have few divs in my page and I want to display the latest data in them getting from an API after every 15 sec without showing the refresh of the page.
The issue is this.props have latest data but its not rendering on the page. Do I need to use componentWillUpdate ? or componentWillReceiveProps ? as both of them are obsolete.
How can I make sure to get the latest data on the page after every 15 sec.
componentDidMount(){
this.updateTimer = setInterval(() => this.getData(), 15000);
}
getData = e => {
const _this = this
admin.getDataFromAPI()
.then((response) => {
if(_this.props.getData.error){
this.context.store.dispatch(receivedDataFromAPI(response));
}
else if(_this.props.getData.data){
let updatedData = response.data;
let oldData = _this.props.getData.data;
oldData.map((col, i) => {
col.state = updatedData[i].state;
col.name = updatedData[i].name;
col.xValue = updatedData[i].xValue;
})
}
})
.catch((error) =>{
this.context.store.dispatch(failedDataFromAPI(error));
});
};
componentWillUnmount(){
clearInterval(this.updateTimer);
}
There are at least two ways of doing this
Using props you can tell parent to send different props which will cause rerender. I personally do not recommend this for sake of complexity.
Use states. Using states is really easy and I will show it to you in this example:
import React, { Component } from 'react';
class Results extends Component{
constructor(props){
super(props);
this.state = {
results : [] //Your empty array or initial value that you set
}
}
componentDidMount(){
//Here you can set interval which will make requests to API and wait for answers
//once you get response you do :
this.setState({
results : responseFromAjax
});
}
componentWillUnmount(){
}
componentDidUpdate(prevProps,prevState){
}
render(){
return(
<p>{this.state.results}</p>
)
}
}
export default Results;
In constructor of the Class Component you set its state which is basically JSON.
Once you want to change the value that is being displayed, you just call this.setState function and it pass the updated key:value pair which will replace the ones that are currently active.

How to change the value in Constructor ReactJs

My problem is that the code is working correctly
I would like to be able to change the value val: 'yolo' by either a component from another page or direct by my database
Do you have an idea, how to fix this? Neff
import React from 'react'
import axios from 'axios'
const entrypoint = process.env.REACT_APP_API_ENTRYPOINT + '/api';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
}
getRandom = async () => {
const res = await axios.get(
entrypoint + "/alluserpls"
)
this.setState({ data: res.data })
}
componentDidMount() {
this.getRandom()
}
clickHandler(){
this.setState({currentPosition: (this.state.currentPosition + 1)%this.state.totalLength})
}
render() {
return (
<div >
<button onClick={this.clickHandler} >Move to the Right</button>
{
Array.from(
{length: this.state.totalLength},
(_,i) => (
<div key={i} className="slot">
<p>{i === this.state.currentPosition ? this.state.val : null}</p>
</div>
)
)
}
</div>
)}
}
export default App;
one way you can change the value of Yolo similar way as you are getting data from the server.
as for changing it from another component , you can do it by either getting it as a props from its parent component where you use this component
<App yoloVal = {"yoloValue"}/>
and you can receive it in props either when it mounts or when it updates
componentDidMount(){
this.setState({
yolo : this.props.yoloVal
}
}
or when it updates
componentDidUpdate(){
if(this.props.yoloVal !== prevProps.yoloVal){
this.setState({
yolo : this.props.yoloVal
}
}
}
you can also get this value from a child in the App by passing it a method
write a method in the App Component
setYoloValue(val){
this.setState({
yolo : val
}
}
now pass this method in render method of App to a child component
return (
<ChildComponent setYoloValue = {this.setYoloValue.bind(this)}
)
we are using bind so when this method is called the context remains of the parent instead of the caller(child component)
now you can use this method anywhere in the child to set the value of Yolo on parent
class ChildComponent extends Component {
componentDidMount(){
this.props.setYoloValue("new Yolo Value by child")
}
}
Now as for passing data between siblings , you can give the data by using the above two methods , first have a common parent , pass the data to parent by using second method then pass that data parent received to the other children as the first method. that is how you can acheive communication between siblings components.
as for setting the value from any other component in the app that is not directly related to you component , you need Redux or similar that does the job for you by keeping the values in a common store and components listen to that store and receive the update when the value in the store updates.
I would like to be able to change the value val: 'yolo'
1.by either a component,
2.from another page
3.or direct by my database
i'm actually surprised by the following piece of code, and not even sure, it 's a valid one. you are initializing this.state twice inside your constructor.
constructor(props) {
super(props);
--> this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
--> this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
}
you initialize your entire variables inside your constructor..
constructor(props) {
super(props);
this.state = {
data: [],
currentPosition: 0,
totalLength: 3,
val: 'yolo',
};
this.clickHandler = this.clickHandler.bind(this)
}
idea is to pass a function(prevState) as a callback to update the local state so as to escape batching.
getRandom = async () => {
const res = await axios.get(
entrypoint + "/alluserpls"
)
this.setState(prevState => ({
...prevState,
data: res.data,
}))
}
i'm not sure this will work as you expected..
clickHandler(){
this.setState({currentPosition: (this.state.currentPosition + 1)%this.state.totalLength})
}
since you are doing a division, it's good to Math.floor or ceil(you need to find whichever value meets your requirement.)
//1. by a component..
handleValChange(val) => {
this.setState(prevState => ({
...prevState,
val,
}))
}
//now u can pass it to a child component.
render() {
const { handleValChange } = this
return (
<div>
<...rest of the div.../>
<ChildComponent {...{ handleValChange }} />
</div>
)
}
from another page.
from another page means, probable a diffrent route. in such cases u need to update this globally(redux, mobx etc..) and the value should also live globally not locally. u can pass id's and stuff via url but function, not possible.
direct by db.
this is where u make an api call and based on the response u update the state. that means, it's time to extract your application into a global state(redux, mobx etc..)
this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
You should not have two states in one constructor. Change it to one state:
this.state {
data: [],
currentPosition: 0,
totalLength: 3,
val: 'yolo',
}
As for changing the value from another component, there are two easy solutions.
1) Using Redux to handle state, instead of local state, probably the best solution.
2) Use a callback function that call setState in that component, and pass it to the other component, if it is a child of this component.
const myCallbackFunction(value: string) {
this.setState({ val: value })
}

set multiple states, and push to state of array in one onClick function

I'm running into a recurring issue in my code where I want to grab multiple pieces of data from a component to set as states, and push those into an array which is having its own state updated. The way I am doing it currently isn't working and I think it's because I do not understand the order of the way things happen in js and react.
Here's an example of something I'm doing that doesn't work: jsfiddle here or code below.
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
}
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
setCategoryStates = (categoryTitle, categorySubtitle) => {
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
render() {
return (
<CategoryComponent
setCategoryStates={this.setCategoryStates}
categoryTitle={'Category Title Text'}
categorySubtitle={'Category Subtitle Text'}
/>
);
}
}
class CategoryComponent extends Component {
render() {
var categoryTitle = this.props.categoryTitle;
var categorySubtitle = this.props.categorySubtitle;
return (
<div onClick={() => (this.props.setCategoryStates(
categoryTitle,
categorySubtitle,
))}
>
<h1>{categoryTitle}</h1>
<h2>{categorySubtitle}</h2>
</div>
);
}
}
I can see in the console that I am grabbing the categoryTitle and categorySubtitle that I want, but they get pushed as null into this.state.categoryArray. Is this a scenario where I need to be using promises? Taking another approach?
This occurs because setState is asynchronous (https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly).
Here's the problem
//State has categoryTitle as null and categorySubtitle as null.
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
//This gets the correct values in the parameters
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
//This method is using the state, which as can be seen from the constructor is null and hence you're pushing null into your array.
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
Solution to your problem: pass callback to setState
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
}, () => {
/*
Add state to the array
This callback will be called once the async state update has succeeded
So accessing state in this variable will be correct.
*/
this.pushToCategoryArray()
})
}
and change
pushToCategoryArray = () => {
//You don't need state, you can simply make these regular JavaScript variables
this.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
I think React doesn't re-render because of the pushToCategoryArray that directly change state. Need to assign new array in this.setState function.
// this.state.categoryArray.push({...})
const prevCategoryArray = this.state.categoryArray
this.setState({
categoryArray: [ newObject, ...prevCategoryArray],
)}

Connected component initial render fails because Redux state is not yet populated from server

I am using Redux thunk and axios to make server calls and modify the state depending on the result.
The problem is that when I use a connected component, and its initial state depends on data from the server, it does not render (the connected props are empty)
render () (<div>{this.props.someData}</data>) // empty, or error, if nested
...
const mapStateToProps = state => ({
someData: state.someData
})
I also tried this:
componentWillMount = () => {
this.setState({
someData: this.props.someData
})
}
And used state in render, but it didn't help.
Is there a way to wait for the server response before rendering or some other solution?
You can conditionally render that part. Use a property to indicate fetching status (property name is loading in my example).
class UserDetails extends React.Component {
state = {
loading: true,
data: null
}
fetch() {
this.setState({
loading: true
})
// an axios call in your case
setTimeout(() => this.setState({
loading: false,
data: {
nestedValue: 'nested value'
}
}), 500)
}
componentDidMount() {
this.fetch()
}
render() {
return <div>
{this.state.loading ? <span>loading...</span> : <div>nested prop value: {this.state.data.nestedValue}</div>}
</div>
}
}
Typically you would use the Component.defaultProps object to initialize your props. So in your case, something like this will set your someData prop to an initial value before axios receives the data.
class MyComponent extends React.Component {
// ... your implementation
}
MyComponent.defaultProps = {
someData: [] // empty array or whatever the data type is
};
Edit: docs

Fetch data and then render it to dom React

Hi I am fetching data from an api and I would like to take the data and render it to the dom but I am the error "Uncaught TypeError: Cannot read property 'map' of undefined at Topicselect.render"
Here is essentially what I am doing, although I have abstracted away anything that is not directly relevant to the question, such as actual topic names, imports, etc :
class Topics extends Component{
constructor(props){
super(props);
this.state = {
topics: []
}
}
componentWillMount(){
fetch('/api').then((res)=>r.json().then((data)=>{
// push topics into this.state.topics somehow
})
console.log(this.state.topics) //returns ['topic1','topic2','topic3'];
}
render(){
const list = this.state.topics.map((topic)=>{
return(<li>{topic}</li>);
})
return(
<ul>
{list}
</ul>
)
}
}
Can anyone tell me how to fix this? I saw an answer on here that said to use componentDidMount instead of componentWillMount but that isn't working for me
You are missing a closing bracket ) after the fetch and it's indeed recommended to use componentDidMount() instead of componentWillMount() for fetching data from an API.
Also don't forget to use this.setState({ topics: data.howeverYourDataIsStructured }); after you receive the data from the API to ensure a rerender of the component.
class Topics extends Component{
constructor(props){
super(props);
this.state = {
topics: []
}
}
componentDidMount() {
fetch('/api').then((res)=>r.json().then((data)=>{
this.setState({ topics: data.topics });
}));
console.log(this.state.topics) //returns [];
}
render() {
console.log(this.state.topics) //returns [] the first render, returns ['topic1','topic2','topic3'] on the second render;
return(
<ul>
{this.state.topics.map(topic => (
<li>{topic}</li>
))}
</ul>
)
}
}
Make sure you use setState() to update your state, otherwise render() won't be triggered to update the dom. Also make sure you don't just overwrite the current state but add your new topics to the old ones. (not relevant for this case, but still important to mention)
One way to do it would be:
componentDidMount() {
var currentTopics = this.state.topics;
fetch('/api').then((res) => r.json().then((data) => {
currentTopics.push(data);
}));
this.setState({'topics': currentTopics});
}
But you can also call setState() inside the loop. setState() does not work synchronously so it will first wait if there are some other changes to be made before it will actually execute the changes and then trigger render.
componentDidMount() {
fetch('/api').then((res) => r.json().then((data) => {
this.setState((state) => ({ topics: [...state.topics, data]}));
}));
}

Categories

Resources