whelanevan6
whelanevan6

Reputation: 153

I have to press a button twice to fill react state from firebase realtime database

In my react component, when the component mounts, I want to run the loadData() function to populate an array of people objects in the state.

This component is called UserDisplaySection.js

componentDidMount() {
    this.loadData();
}

Which calls the function to load data:

loadData = () => {
    const db = fire.database();
    const ref = db.ref('User');

    let currentState = this.state.people;
    ref.on('value', (snapshot) => {
      snapshot.forEach( (data) => {
        const currentStudent = data.val();
        let user ={
          "email" : currentStudent.Email,
          "name": currentStudent.Name,
          "age": currentStudent.Age,
          "location": currentStudent.Location,
          "course": currentStudent.Course,
          "interests": currentStudent.Interests
        }
        currentState.push(user);

      });
    });

    this.setState({
      people: currentState
    });

  }

From another component called Home.js, I render UserDisplaySection.js with the toggle of a button which sets the state of a boolean called willDisplayUser which is initialised as false by default.

handleDisplayUsers = e => {
    e.preventDefault();
    this.setState({
      willDisplayUsers: !(this.state.willDisplayUsers),

    });
  }

    render() {
        return (
          <div>
            <button className="mainButtons" onClick={this.handleDisplayUsers}>View Users</button> <br />
            {this.state.willDisplayUsers ? <UserDisplaySection/>: null}

          </div>
        );

  }

Note: The function works and populates the state with the correct data, but only after I render the UserDisplaySection.js component for a second time and works perfectly until I reload the page.

Upvotes: 0

Views: 826

Answers (2)

Gleb Kostyunin
Gleb Kostyunin

Reputation: 3863

The root of the problem is that you are setting state outside of the ref.on callback.

Data fetch is async and the callback will be executed later in time. Since set state is outside the callback, it gets called called immediately after registering a listener, but before the data finished fetching. That's why it does not work on initial render/button click: it shows initial state, not waiting for the fetch to finish.

Also, using once instead of on might serve better for your case.

loadData = () => {
    const db = fire.database();
    const ref = db.ref('User');

    let currentState = this.state.people;
    ref.once('value', (snapshot) => {
      // callback start
      snapshot.forEach( (data) => {
        const currentStudent = data.val();
        let user ={
          "email" : currentStudent.Email,
          "name": currentStudent.Name,
          "age": currentStudent.Age,
          "location": currentStudent.Location,
          "course": currentStudent.Course,
          "interests": currentStudent.Interests
        }
        currentState.push(user);
      });

      this.setState({
        people: currentState
      });
      // callback end
    });

    // setState used to be here, outside of the callback
  }

Upvotes: 1

Renaud Tarnec
Renaud Tarnec

Reputation: 83093

By doing ref.on() you are actually declaring a listener that "listens for data changes at a particular location", as explained in the doc. The "callback will be triggered for the initial data and again whenever the data changes". In other words it is disconnected from your button onClick event.

If you want to fetch data on demand, i.e. when your button is clicked, you should use the once() method instead, which "listens for exactly one event of the specified event type, and then stops listening".

Upvotes: 3

Related Questions