简体   繁体   中英

setState from inside of a callback to render related content in a React component

I'm stuck with conditional content rendering, depending on a value presence. In the component, if API key is not given, I want to show an input with a button. When the API key is stored using electron-json-storage and as a state, the component renders another html.

My problem is I cannot assign the state during component's construction.

// @flow
import React, { Component } from 'react';
import storage from 'electron-json-storage';
import {
        Button,
        Popover,
        PopoverInteractionKind,
        Position
       } from '@blueprintjs/core';

export default class UserButton extends Component {
  constructor() {
    super();

  // -----------------------------------------------------------
  // HERE - I'm trying to assign keys from storage via callback
  // Unfortunately it doesn't work
  // It throws Cannot read property 'is_loggedin' of null
  // when renders content={loginContent[this.state.is_loggedin]}
  // -----------------------------------------------------------
  storage.get('auth', (error, data) => {
    if (error) throw error;
    this.state = {
      api_key: data.user.api_key,
      is_loggedin: data.user.is_loggedin
    };
  });

    this.handleTextChange = this.handleTextChange.bind(this);
    this.handleSaveClick = this.handleSaveClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
  }

  handleTextChange(e) {
    this.setState({ api_key: e.target.value });
  }

  handleSaveClick() {
    this.setState({ is_loggedin: 1 });

    storage.set('auth', { user: this.state }, function (error) {
      if (error) throw error;
    });

    this.forceUpdate();
  }

  handleLogoutClick() {
    this.setState({ is_loggedin: 0 });

    storage.remove('auth', function (error) {
      if (error) throw error;
    });

    this.forceUpdate();
  }

  render() {
    const loginContent = ([
      // Not logged in
      <div>
        <label className="pt-label .modifier">
          <input
            onChange={this.handleTextChange}
            autoFocus={true}
            className="pt-input modifier"
            type="text"
            placeholder="Your API key"
          />
        </label>

        <Button
          className="pt-intent-primary pt-fill"
          onClick={this.handleSaveClick}
        >
          Save
        </Button>
      </div>,
      // Logged in
      <div>
        <Button
          className="pt-intent-primary pt-fill"
          onClick={this.handleLogoutClick}
        >
          Change API key
        </Button>
      </div>
    ]);

    return (
      <Popover
        content={loginContent[this.state.is_loggedin]}
        interactionKind={PopoverInteractionKind.CLICK}
        position={Position.BOTTOM_RIGHT}
        popoverClassName="pt-popover-content-sizing"
      >
        <Button className="pt-button pt-minimal pt-icon-user" />
      </Popover>
    );
  }
}

How this could be done?

You are probably rendering the component before the callback is called, and so state is still empty. What you could do is add a temporary, is_loading state in the constructor that renders a loading message/spinner in the page until the callback is called.

constructor() {
    super();

    this.state = { is_loading: true };

    storage.get('auth', (error, data) => {
        if (error) throw error;
        this.setState({
            is_loading: false,
            api_key: data.user.api_key,
            is_loggedin: data.user.is_loggedin
        });
    });

    this.handleTextChange = this.handleTextChange.bind(this);
    this.handleSaveClick = this.handleSaveClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
}

// ...

render() {
    // ...

    return (
        <Popover
            content={this.state.is_loading? "<div>Loading...</div>": loginContent[this.state.is_loggedin]}
            interactionKind={PopoverInteractionKind.CLICK}
            position={Position.BOTTOM_RIGHT}
            popoverClassName="pt-popover-content-sizing"
        >
            <Button className="pt-button pt-minimal pt-icon-user" />
        </Popover>
    );
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM