disable button in reactjs - reactjs

I have a series of buttons that execute internal logic(no forms not dependant on input), but call the functions asynchronously. I would like to disable the button after one click, and have tried several things on onclick() method but keep getting errors.
Code looks something like this:
{ this.state.isEthTransferVisible && <button id="button"
onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}>Check Balances</button>
}
this is the function called from within the onclick
async handleTransferFromEthereum(){
await parseAddress(this.state.sc)
this.setState(prevState => ({
isEthTransferVisible: !prevState.isEthTransferVisible,
isGoDeployedVisible: !prevState.isGoDeployedVisible
}));
}

Add another state variable, such as this.isEthTransferEnabled (Default true). Change your button to:
{ this.state.isEthTransferVisible && <button id="button"
disabled={this.state.isEthTransferEnabled}
onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}>Check Balances</button>
}
And change your handleTransferFromEthereum method:
async handleTransferFromEthereum(){
this.setState({ isEthTransferEnabled: false });
await parseAddress(this.state.sc)
this.setState(prevState => ({
isEthTransferVisible: !prevState.isEthTransferVisible,
isEthTransferEnabled: true,
isGoDeployedVisible: !prevState.isGoDeployedVisible
}));
}

onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}
Wrong syntax? It should be:
onClick={() => {
parseAddress(this.state.sc);
this.handleTransferFromEthereum();
}}

Related

ReactJS adding a value to Form.submit() on Menu click

fellow Stackers! I probably have a simple question, but can't seem to find the answer...
What I want to achieve:
I have this kind of commenting logic. Basically, when a person comments without any status change a button calls postComment and all works fine. Now when a user comments & selects to change status it presses on Menu.Item (ref antd) which would send the element key for me to grab and work around some logic.
const onMenuClick = (e) => {
postComment(e);
};
<Menu onClick={onMenuClick}>
<Menu.Item key="Done">
<div>
<Row>
<Col md={2}>
<CheckCircleOutlined style={{ color: "limegreen", fontSize: '1.5em' }} className='mt-2 mr-2' />
</Col>
<Col md={20} className="ml-2">
<Row md={24}><Col className="font-weight-semibold"> Comment & Done</Col></Row>
<Row md={24}><Col className="text-muted">Comment & calculation status is done.</Col></Row>
</Col>
</Row>
</div>
</Menu.Item>
Above code does work, but for certain reasons (or my lack of knowledge) but It will jump over the usual HTML submit rules and now the function will only check for validation inside.
So what do I want? I want to use the Menu something like this:
<Menu onClick={(e) => formComment.submit(e)}>
This would submit a form and pass the Menu.Item key which I could use.
My postComment function:
const postComment = async (e) => {
console.log(e);
setCommentsLoading(true)
const formData = new FormData();
const { id } = props.match.params;
let comment;
formComment.validateFields().then(async (values) => {
//Upload and set Documents for post. This fires before validation, which is not ideal.
if (fileList.length > 0) {
fileList.forEach(file => {
formData.append('files', file);
});
await commentService.upload(formData).then((res) => { //Await only works after Async, so cant put it below validateFields()
res.data.forEach((element) => {
documents.push(element.id);
setDocuments(documents)
});
}).catch(error => {
message.error(error);
}).finally(() => {
setDocuments(documents)
}
)
} //This basically uploads and then does everything else.
console.log(values.comment)
//Checks if status should be changed.
if (e.key !== undefined) {
let variable = commentsVariable + 'Status';
let put = {
[variable]: e.key,
};
if (fileList.length <= 0 && e.key === "Done") {
message.error('Should have attachments.')
mainService.get(id).then(res => {
})
setCommentsLoading(false)
return
} else {
//No idea how to update status in the view.js, needs some sort of a trigger/callback.
mainService.put(put, id).then(res => { })
.catch(err => {
console.log(err)
return
})
}
}
if(values.comment === undefined) {
comment = `This was aut-generated comment.`;
} else {
comment = values.comment;
}
let post = {
comment: comment,
[commentsVariable]: id,
attachments: documents,
user: random(1, 4),
};
commentService.post(post).then((res) => {
mainService.get(id).then(res => {
setComments(res.data.data.attributes.comments.data)
message.success('Comment successfully posted!')
formComment.resetFields()
setFileList([])
setDocuments([])
})
});
}).catch(error => {
error.errorFields.forEach(item => {
message.error(item.errors)
setFileList([])
setDocuments([])
})
})
setCommentsLoading(false);
};
My form looks like this (I won't include Form.Items).
<Form
name="commentForm"
layout={"vertical"}
form={formComment}
onFinish={(e) => postComment(e)}
className="ant-advanced-search-form">
So in the end, I just want a proper HTML rule check before the function fires, no matter if I press the "Comment" button or press the ones with the status update.
So I kind of worked around it. Basically I just added a hidden field:
<Form.Item name="key" className={"mb-1"} hidden>
<Input/>
</Form.Item>
Then on Menu i addeda function that has onClick trigger, which sets the value to whatever key is there and submits:
const onMenuClick = (e) => {
console.log(e.key)
formComment.setFieldsValue({
key: String(e.key)
})
formComment.submit()
};
I'm pretty sure this is a dirty workaround, but hey, it works for a Junior dev :D Either way, I'll try to test it without hidden field where it just adds a new value.

How to make a delay in React

When you click on the button, another screen is rendered:
<button className="button _button_two_column" onClick={()=> dispatch({type: "NEXT_SCREEN"})}>
if (action.type === 'NEXT_SCREEN') {
return {
...state,
currentScreenIndex: state.currentScreenIndex + 1,
}
}
/when 1 is added to the currentScreenIndex the screen changes/
I need screen 1 to open for 3 seconds when I press the button and then screen 2
How to do it? I tried to do it using setTimeout, but nothing happened
I believe you are using the useReducer hook. You can do the dispatch inside the callback of setInterval. You need to carefully handle the memory leaks as well.
Try like this:
Add the following hooks to your component. trigger is to trigger the screen transitions. We call setInterval only once (using if check) to avoid memory leaks and clear it when the component unmounts.
const [trigger, setTrigger] = useState(false);
useEffect(() => {
let interval;
if (trigger) {
interval = setInterval(() => dispatch({ type: "NEXT_SCREEN" }), 3000);
}
return () => clearInterval(interval);
}, [trigger]);
Change the trigger to true when the button click happens
<button
className="button _button_two_column"
onClick={() => setTrigger(true)}
></button>
Demo code:
I suggest to use setTimeout() function to make a delay.
And avoid to use javascript function in html.
const onClick = () => {
setTimeout(dispatch({type: "NEXT_SCREEN"}), 1000)
}
<button className="button _button_two_column" onClick={onClick}>
The solution to my task:
useEffect(() => {
let interval;
if (stateScreen.currentScreenIndex===4) {
interval = setInterval(() => dispatch({ type: "NEXT_SCREEN" }), 2000);
}
return () => clearInterval(interval);
}, );

REACT - show loader spinner by setState inside .map()

Got an array on objects from the server.
Want to display for the user a loader spinner when the request is lodaing.
const onDownland = (reportId: string) => {
setDownloadLoadingState(() => true);
backendAPIAxios.get(`/download/${reportId}`)
.then((response: AxiosResponse<IDownloadResponse>) => {
})
.catch((e: AxiosError) => {
}).finally(() => {
setDownloadLoadingState(() => false);
});
};
The problem is I get multiple objects from the server, and I got one state that changes all of the objects UI.
<Button>
{!props.downloadLoadingState ?
<MSvg
name='download'
className={classes['svgContainerBlack']}
onClick={() => props.onDownload(history.id!)}
/> :
<Tooltip title={<h1 style={{ fontSize: '17px' }}>Loading</h1>} placement="left" arrow>
<CircularProgress color="inherit" />
</Tooltip>
}
</Button>
when loading
after loading
How can I display the loader spinner for each object when I fire the request.
Added -
If you move your loadingState into the Button component, you can have independent spinners for each object.
You can set the onDownload prop to an async function (i.e. a function returning a Promise), and manage the loading state inside the button component, instead of in its parent.
Something like this might work:
// your button component
const [downloadLoadingState, setDownloadLoadingState] = useState(false);
...
<Button>
{!downloadLoadingState ? // Now looking into local state, instead of prop
<MSvg
...
onClick={() => {
setDownloadLoadingState(true)
props.onDownload(history.id!).finally(() => setDownloadLoadingState(true))
}
}
/> :
<Tooltip ...>
...
</Tooltip>
}
</Button>
// in the parent component, keep only the fetching in onDownload and remove the loading state management
// then, return the axios promise so the .finally() can be used inside the button component
const onDownload = (reportId: string) => {
return backendAPIAxios.get(`/download/${reportId}`)
.then((response: AxiosResponse<IDownloadResponse>) => {
})
.catch((e: AxiosError) => {
});
};

prevent a button being clicked consecutively in React

I am trying to prevent a button being clicked consecutively in my project and only allow a single click. I would also like it to only be a single click and not allow a double click if that is possible?
To do this I would like to add a time out of maybe 5 seconds before the button can be pressed again but I'm not sure how to do this. The button is a link to redirect the user back to the homepage.
Is the a way to set the button on a timer when clicked?
<Button id="back-btn" variant="link" className="btn btn-link" onClick={props.goBack} alt="homepage">
Homepage
</Button>
Any ideas?
Cheers
R
basically you need to use a disabled state with a timer.
check this codepen: https://codepen.io/hasanagh/pen/MWaLxVK
state = {
disabled: false,
};
handleButtonClicked = () => {
//going back logic
this.setState({
disabled: true,
});
setTimeout(() => {
this.setState(() => ({
disabled: false,
}));
}, 5000);
};
render() {
const { disabled } = this.state;
return (
<button
onClick={this.handleButtonClicked}
disabled={disabled}
>
Button to be disabled
</button>
);
}
Also, not sure why you need it to be 5 sec if this is related to a certain event better bind to event than time.
It's probably most re-useable to make your button component. You could handle the onClick event to set a disabled state, then start a timer to set it back to false. Example:
const DebouncedButton = ({ as = button, delay, onClick, ...props }) => {
const [isDisabled, setDisabled] = useState(false);
useEffect(() => {
if (!isDisabled) {
// timeout elapsed, nothing to do
return;
}
// isDisabled was changed to true, set back to false after `delay`
const handle = setTimeout(() => {
setDisabled(false);
}, delay);
return () => clearTimeout(handle);
}, [isDisabled, delay]);
const handleClick = (e) => {
if (isDisabled) {
return;
}
setDisabled(true);
return onClick(e);
};
const Component = as;
return <Component {...props} disabled={isDisabled} onClick={handleClick} />;
};
You would use this component just like you'd use a button, except that you pass it a delay which is the amount of time in milliseconds it should be disabled after clicking. The as prop lets you pass the component which is used for the button itself, defaulting to <button>.
<DebouncedButton
as={Button}
delay={5000}
id="back-btn"
variant="link"
className="btn btn-link"
onClick={() => console.log('click!')}
alt="homepage"
/>
Currently it sets the disabled property of the button to true, but if you don't want the visual, just remove disabled={isDisabled} from the component.

Conditional onClick from ternary

I'm trying to have an onClick event fire depending on the location of the user.
if the location is '/favorites' i want to return null i've tried using a ternary operator to achieve this, and cannot find my mistake. heres what i've tried:
{this.props.location==='/favorites' ? null :
onClick={() => {
this.props.updateFavsState(this.props.character);
this.setState(prevState => ({
loved: !prevState.loved
}));
}}
}
Put logic in the function and call function onClick event, location pass to the function as argument.
Solution:
handleClick = (location) => {
if (location === '/favorites') {
return null;
}
this.props.updateFavsState(this.props.character);
this.setState(prevState => ({
loved: !prevState.loved
});
}
// somewhere in your render component
onClick={() => { handleClick(this.props.location)}}
what you want is
onClick={this.props.location==='/favorites' ? null :
() => {
this.props.updateFavsState(this.props.character);
this.setState(prevState => ({
loved: !prevState.loved
}));
}}

Resources