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 anArray.forEach()
loop simplifies the code (and will probably save you some bugs in the future!) - I separated
fetchNumbers()
, which fetchesthis.state.numbers
, fromfetchNumber()
, which fetchesthis.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 usingPromise.all()
incomponentDidMount()
(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>
</>
);
}
}