Search code examples
javascriptreactjsstatereact-propsreact-lifecycle

React: Data rendering on second click, but not on first


I have a modal displaying data that I'm receiving in props. When I open the modal, I should see select data displayed from my props. However, the modal is empty the first time I open it, and populates the second time.

If I go on to change the data in props, the modal stays the same on the first new click, and refreshes on the second new click.

I've tried forcing it with setTimeout, messing with combos of componentDidMount, componentDidUpdate, and other lifecycle methods, but nothing seems to work. I'm sure it has something to do with my using the prevData param in componentDidMount. But even thought react devtools shows this.state.pricesData updates, when I try rendering from state I get blanks every time. When I invoke a console log as a callback of setState, I get an empty array bracket in the console log (which I can expand to show all the correct array data, but I guess that's populated async after the log).

Here's the code:

import React, { Component } from "react";

import "../../App.css";

let explanations = [];

export default class ExplanationModal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      pricesData: [],
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.pricesData !== prevState.pricesData) {
      return { pricesData: nextProps.pricesData };
    } else {
      return null;
    }
  }

  // to allow for async rendering
  getSnapshotBeforeUpdate(prevProps) {
    if (prevProps.pricesData !== this.state.pricesData) {
      return this.state.pricesData;
    }
  }

  componentDidMount = () => {
    this.setState({ pricesData: this.props.pricesData }, () =>
      console.log(this.state.pricesData)
    );
  };

  componentDidUpdate = (prevData) => {
    this.renderExp(prevData.pricesData);
  };

  renderExp = (data) => {
    explanations = [];
    data.forEach((set) =>
      explanations.push({ title: set.titel, explanation: set.explenation })
    );
  };

  onClose = () => {
    this.props.hideModal();
  };

  render() {
    return (
      <div className="modal">
        <div>
          {explanations.map((item) => (
            <span>
              <h4>{item.title}</h4>
              <p>{item.explanation}</p>
            </span>
          ))}
        </div>
        <button onClick={this.onClose} className="update">
          Close
        </button>
      </div>
    );
  }
}

Solution

  • you have to keep your explanation array in your state. then update the state when new data arrives. because react doesn't trigger a re render if you don't update the state .

    your constructor should be

        super(props);
        this.state = {
          pricesData: [],
          explanations : []
        };
      }
    

    and your renderExp function should be

    renderExp = (data) => {
        explanations = [];
        data.forEach((set) =>
          explanations.push({ title: set.titel, explanation: set.explenation })
        );
        this.setState({ explanations })
      };
    

    inside your render function

    render() {
        return (
          <div className="modal">
            <div>
              {this.state.explanations.map((item) => (
                <span>
                  <h4>{item.title}</h4>
                  <p>{item.explanation}</p>
                </span>
              ))}
            </div>
            <button onClick={this.onClose} className="update">
              Close
            </button>
          </div>
        );
      }
    }
    

    This way you will get the updated data when it arrives.