Home > Software engineering >  Show/hide button based on state React
Show/hide button based on state React

Time:12-27

I have a button called download, it is a component. The functionality is written in a container.

I want the button to be hidden as default, unless the "(i === number)" condition is true in the download function.

However, it is tricky, since this condition will only be validated when I click "download". I am not sure how do I validate this logic beforehand to determine if the button needs to be displayed or not.

Can you please help? I am trying to set a state for showing button, but it doesn't work.

My code basically looks like this -

container:

state = {
  showButton: false,
};

componentDidMount() {
  this.download();
};

download = async () => {
  const data = await this.props.client
      .query({
          query: numberQuery,
          fetchPolicy: "no-cache",
      })
  // retrive data from number qeury ....
  const contentData = data.data.content;
 
  // retrieve and format numbers
  const numbers = this.getNumbers(contentData);
 
  // call get number and get the individual number here
  const number = await this.getNumber();
 
  numbers.forEach((i) => {
     // this is to check if numbers contain the number from getNumber(), if 
     // number matches number in numbers
     if (i === number) {
       this.setState({
        showButton: true,
       });
          // call functions, start downloading
     };
   });
  };
 
 render() {
   return (
     {this.state.showButton ? 
      <Download 
      onStartDownload={() => this.download()}
      /> : null}
    );
 };

component:

 class Download extends Component {
  state = {
    startDownload: false,
  };
 
  startDownload = () => {
    this.props.onStartDownload();
  };
 
  render() {
    return (
      <Fragment>
        <Button
          id="download"
          onClick={this.startDownload}
        >
          Download
        </Button>
      </Fragment>
     );
    };
  };

CodePudding user response:

If I understand correctly, your problem is that the numbers are fetched (and, in turn, the logic for showing/hiding the button is run) only after the download button was clicked, but you want it to run as soon as possible (that is, on the component's mount).

Generally, the fetching of data in React is separated from the render/event handler logic. In your case, a solution is to fetch the data when the component mounts, save it in state (such as a numbers and number fields), then, when rendering, check if number is in the numbers array.

For example:

// Container component; child component remains the same

state = {
  number: null,
  numbers: []
};

componentDidMount() {
  this.fetchNumbers();
  this.fetchNumber();
};

async fetchNumbers() {
  const data = await this.props.client
  .query({
      query: numberQuery,
      fetchPolicy: 'no-cache',
  });
  // retrive data from number qeury ....
  const contentData = data.data.content;

  // retrieve and format numbers
  const numbers = this.getNumbers(contentData);

  this.setState({ numbers });
}

async fetchNumber() {
  // Assuming this is another HTTP request or something similar
  const number = await this.getNumber();

  this.setState({ number });
}

download = async () => {
  // *only* downloading logic
};
 
 render() {
    const { number, numbers } = this.state;
    const showDownload = numbers.includes(number);

    return showDownload 
        ? <Download onStartDownload={() => this.download()}/> 
        : null;
 };
}

Notes:

  • Using Array.includes() instead of an Array.forEach() loop simplifies the code (and will probably save you some bugs in the future!)
  • I separated fetchNumbers(), which fetches this.state.numbers, from fetchNumber(), which fetches this.state.number, simply because they seem like two separate pieces of state (and therefore fetching them independently is most efficient); you could improve it even further by having the two functions return the data (instead of changing the state), then using Promise.all() in componentDidMount() (I left it out for simplicity's sake).
  • Generally in web dev, a best practice when fetching core data asynchronously is to indicate that the component is not loaded until the data arrives (using a loader, for example). For practice or prototypes it might not be your top priority, but keep that in mind for production.

CodePudding user response:

you need a constructor in your class component and to update the state you need to use setState(), review the code to follow :

 class Download extends react.Component {
  constructor() {
    super();// required
    this.state = {
      startDownload: false
    };
  }

  startDownload = () => {
    this.setState((prevState) => ({
      // I use prevState, to bring the previous state value
      startDownload: !prevState.startDownload // here the value is inverted
    }));
  };

  render() {
    return (
      <> // this is equivalent to the fragment
        <button id="download" onClick={this.startDownload}>
          Download
        </button>
      </>
    );
  }
}
  • Related