Hey I am trying to make small application for React-router I am trying to send API request (I do not have backend thats why im using promises) When i click Link it is redirecting not waiting my async/await could you tell me what am i doing wrong? First thought i can pass history with props and then
history.push("/someURL");
But my second thought is if i could do this with <Link I will pass less props (history).
To sum up i need to wait until my request(promise) over after that i need to change url
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { toast } from 'react-toastify';
const StyledButton = styled.button`
width: 100%;
border-radius: 10px;
background-color: #24b571;
color: #ffffff;
font-weight: bold;
margin-bottom: 5px;
`;
const SaveButton = ({
children,
saveValidation,
message,
to,
setIsLoading,
}) => {
const promiseFunc = () =>
new Promise((resolve) => {
setTimeout(() => {
resolve(5);
}, 5000);
});
const onClickHandler = async (event) => {
setIsLoading(true);
const control = saveValidation(); // returns true or false
if (!control) {
toast.error(message);
event.preventDefault();
} else {
try {
const a = await promiseFunc();
alert(a);
} catch {
alert('error');
}
console.log(control);
}
setIsLoading(false);
};
return (
<Link to={to}>
<StyledButton onClick={onClickHandler}>{children}</StyledButton>
</Link>
);
};
export default SaveButton;
SaveButton.propTypes = {
children: PropTypes.node.isRequired,
saveValidation: PropTypes.func.isRequired,
to: PropTypes.string.isRequired,
message: PropTypes.string,
setIsLoading: PropTypes.func,
};
SaveButton.defaultProps = {
message: 'Fill all values',
setIsLoading: () => {},
};
The main issue here is that you are rendering TWO components/elements a user could interact with, which is generally considered anti-pattern in UI/UX. The button is clicked and its onClick handler is invoked, and the click event bubbles and the Link handles it by navigating. These events are handled separately from one another. You could stop the onClick event propagation in the button's click handler, but then the Link would stop working.
I suggest using the styled button alone and at the end of the asynchronous logic issue an imperative navigation instead of the declarative navigation offered by the Link. Use the useHistory hook from react-router-dom (or useNavigate if using RRDv6).
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { useHistory } from 'react-router-dom'; // <-- import hook
import { toast } from 'react-toastify';
const StyledButton = styled.button`
...
`;
const SaveButton = ({
children,
saveValidation,
message,
setIsLoading,
to,
}) => {
const history = useHistory(); // <-- use hook
const promiseFunc = () => new Promise((resolve) => {
...
});
const onClickHandler = async (event) => {
setIsLoading(true);
const control = saveValidation();
if (!control) {
toast.error(message);
event.preventDefault();
} else {
try {
const a = await promiseFunc();
alert(a);
} catch {
alert('error');
}
console.log(control);
}
setIsLoading(false);
history.push(to); // <-- imperative navigation
};
return (
<StyledButton onClick={onClickHandler}>{children}</StyledButton>
);
};