Edward Black
Edward Black

Reputation: 250

React - Array in state is defined, but Array[index] is undefined

I am writing a react app to fetch vocabulary from a backend database and then display it to the user, along with some information about each particular word. My problem is that I can't get the word to display inside render() as the index of the array is undefined, however according to chrome devtools the array is fully defined.

Here's my code:

import React from 'react';
import "./Vocab.css"

 class Vocab extends React.Component {
   //HEADER HEADER HEADER

   state = {
     vocabulary: null,
     var2: null
   };

   componentDidMount(){
     //Initializing Routine Here
     this.getVocab()
   }

   getVocab = () => {
     var newVocab = [];
     if(this.state.vocabulary == null){
       newVocab = newVocab;
     }
     else{
       newVocab = [this.state.vocabulary];
     }
     this.props.HandleFetch("GET", this.props.backLink + "vocab")
      .then(r => JSON.parse(r))
      .then(data => newVocab.push(data[0]))
      .then(this.setState({vocabulary:newVocab}))
   }

   //In render() variable display and the if/else statements are used exclusively for testing purposes
   render() {
     var display = "X";
     if(this.state.vocabulary == null){
       display = "Y";
     }
     else{
       console.log(this.state.vocabulary); //Outputs the array 
       console.log(this.state.vocabulary[0]) //Outputs "undefined"
       display = "Z";
     }
     return (
       <>
       <h1>Test</h1>
       {display}
       </>
     );
   }
 }
 export default Vocab;

The output of console.log(this.state.vocabulary):

0:
Definition: "TestDef"
IPA: "TestIPA"
IsCard: true
Language: "TestLang"
LastStudyDate: "2021-01-27"
Mnem: ""
PoS: "TestPO"
Source: "TestDict"
SourceLearned: "TestSource"
Word: "Test"

The output of this.state.vocabulary[0]:

undefined

Additionally, according to chrome devtools and through typing $r.state.vocabulary[0] in the console I get the dictionary at the array's index, which I need:

{Word: "Test", Mnem: "", LastStudyDate: "2021-01-27", SourceLearned: "TestSource", IsCard: true, …}

In the console the array is fully navigable, but the web page itself won't render it. Any idea what I might be doing wrong?

UPDATE I was able to add a button the onClick goes to a function which prints out vocabulary and vocabulary[0]. This DOES work, but the render() function and componentDidUpdate() function still cannot access vocabulary[0]. I don't see anything in the react documentation that talks about a setState() finishing or any function happening after componentDidUpdate().

Additionally during some earlier testing I tried to console.log newVocab[0] before assigning it to state in a .then string. This worked, but outputted in the console only after all my other console calls, which indicates that it is happening after the component rerender and update cycle, even though I was calling it before I called setState().

My current test code so far:

import "./Vocab.css"

 class Vocab extends React.Component {
   //HEADER HEADER HEADER

   state = {
     vocabulary: null,
     var2: null
   };

   componentDidMount(){
     //Initializing Routine Here
     this.getVocab()
   }

   componentDidUpdate(){
     console.log("Vocab update");
     console.log(this.state.vocabulary);
     console.log("Vocab[0] update");
     console.log(this.state.vocabulary[0]);
     ///Outputs the vocabulary array, and then 'undefined'
   }

   getVocab = () => {
     var newVocab = [];
     if(this.state.vocabulary == null){
       newVocab = newVocab;
     }
     else{
       newVocab = this.state.vocabulary;
     }
     this.props.HandleFetch("GET", this.props.backLink + "vocab")
      .then(r => JSON.parse(r))
      .then(data => newVocab.push(data[0]))
      .then(this.setState({vocabulary:newVocab}))
   }

   logVocab = () => {
     console.log("Vocab");
     console.log(this.state.vocabulary);
     console.log("Vocab[0]");
     console.log(this.state.vocabulary[0]);
     ///Outputs the vocabulary array, and then the dictionary at index[0].
   }

   render() {
     var display = "X";
     if(this.state.vocabulary == null){
       display = "Y";
       console.log("Y");
     }
     else{
       console.log("Vocab Z");
       console.log(this.state.vocabulary);
       console.log("Vocab[0] Z");
       console.log(this.state.vocabulary[0]);
       display = "Z";
       ///Outputs the vocabulary array, and then 'undefined'
     }
     return (
       <>
       <h1>Test</h1>
       {this.state.vocabulary ? this.state.vocabulary[0] : ""}
       <button onClick = {this.logVocab}> CLICK </button>
       </>
     );
   }
 }
 export default Vocab;

Upvotes: 0

Views: 1851

Answers (2)

Edward Black
Edward Black

Reputation: 250

Update: I was able to track down the problem.

The issue was with the .then string. If you use .then but do not pass an argument to the code you want to execute, it looks like the .then portion will just be omitted and it will run without waiting, which is what was causing things to run out of order.

I believe the relevant line from the documentation (here) is

If one or both arguments are omitted or are provided non-functions, then then will be missing the handler(s), but will not generate any errors. If the Promise that then is called on adopts a state (fulfillment or rejection) for which then has no handler, the returned promise adopts the final state of the original Promise on which then was called.

My updated, and working, getVocab() function:

     var newVocab = [];
     if(this.state.vocabulary == null){
       newVocab = newVocab;
     }
     else{
       newVocab = this.state.vocabulary;
     }
     this.props.HandleFetch("GET", this.props.backLink + "vocab")
      .then(r => JSON.parse(r))
      .then(data => {newVocab.push(data[0]);
                     this.setState({vocabulary:newVocab})}) 
       //Outputs correctly due to 'data' argument

   }

Thank you to everyone who commented or helped me with this.

Upvotes: 1

oozywaters
oozywaters

Reputation: 1241

The problem is the state.vocabulary is still null when you're trying to log out vocabulary[0]. Since this.props.HandleFetch is an async operation, you need to wait until this.state.vocabulary is set, like this:

render() {
  const { vocabulary } = this.state;
  if (!vocabulary) {
    // render nothing if vocabulary is null
    return null;
  }

  // This will log out the first element of vocabulary instead of undefined
  console.log(this.state.vocabulary[0]);

  return (
    ...
  );
}

UPD: You also have incorrect syntax in getVocab method:

      .then(this.setState({vocabulary:newVocab}))

What it does is immediately assign vocabulary to the newVocab variable which is an empty array at the moment of execution. Then, when this.props.HandleFetch gets fulfilled, newVocab is filled with response data.

You can fix this like the following:

   this.props.HandleFetch("GET", this.props.backLink + "vocab")
      .then(r => JSON.parse(r))
      .then(data => this.setState({ vocabulary: data[0] }))

Upvotes: 0

Related Questions