Reputation: 250
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
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
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