2

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: () => {},
};
Neo
  • 467
  • 1
  • 3
  • 17
  • For tags i could be wrong on javascript/html thats why i added javascript tag – Neo Dec 05 '21 at 18:48
  • 1
    Browser event handling does not understand `async` event handler functions. – Pointy Dec 05 '21 at 18:49
  • @Pointy is there anything i can do like event.preventDefault(); and then "event.unpreventDefault();" – Neo Dec 05 '21 at 18:53
  • No, there is not. When your handler returns, the browser will ignore the return value and consider the event to be completed. – Pointy Dec 05 '21 at 19:01
  • You could do the things suggested but the onClick needs to be on the Link, not the StyledButton as they are acting independently of one another (the Links redirects while the StyledButton runs the handler. – andyd_28 Dec 05 '21 at 20:57

1 Answers1

3

Issue

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.

Solution

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>
  );
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181