Search code examples
buttonstyled-componentsreact-typescript

React: Styled-components loading button


I am using React- typescript for my app. For Styling I am using Styled-components. I have created one global button component. Inside the button component i used loader. My target is when user will click the button it will display loading. From the parent component I have created one fake api call and in there I added settime-out 5s. But when I click the button it does not display the loader which was in Button component. From the parent component set-time out works and it display in my console. I don't know why it does not the display the loading ..... Also i added the disabled option when fake api will call button should be disabled. That logic also does not work.

Here is the Button component

import * as React from "react";
import styled, { css } from "styled-components";

interface IButtonProps {
  children?: React.ReactChild;
  className?: string;
  size?: "small" | "medium" | "big";
  themes?: "primary" | "secondary" | "dark" | "light";
  disabled?: boolean;
  loading?: boolean;
  style?: React.CSSProperties;
  onClick?: () => void;
  onSubmit?: () => void;
}

const Button = ({
  children,
  className,
  size,
  themes,
  disabled,
  loading,
  style,
  onClick,
  onSubmit
}: IButtonProps) => (
    <button
      className={className}
      onClick={onClick}
      onSubmit={onSubmit}
      style={
        disabled && disabled 
          ? { opacity: 0.5, pointerEvents: `none` }
          : loading ? { ...style, pointerEvents: `none` } : //This is my disabled condition.
            style
      }
    >
      {loading ? <p>loading...</p> : children} //This is my loading condition.
    </button>
  );

const sizes = {
  small: css`
    padding: 5px 20px;
    font-size: 12px;
  `,
  medium: css`
    padding: 10px 30px;
    font-size: 14px;
  `,
  big: css`
    padding: 15px 40px;
    font-size: 18px;
  `
};

const ButtonThemes = {
  primary: css`
    border: 1px solid tomato;
    background: tomato;
    color: white;
  `,
  secondary: css`
    border: 1px solid palevioletred;
    background: palevioletred;
    color: white;
  `,
  dark: css`
    border: 1px solid #273444;
    background: #273444;
    color: white;
  `,
  light: css`
    border: 1px solid #eff2f7;
    background: #f9fafc;
    color: #273444;
  `
};

const StyledButton = styled(Button)`
  ${({ size = "small" }) => sizes[size]};
  ${({ themes = "primary" }) => ButtonThemes[themes]};
  outline: none;
  border-radius: 5px;
  cursor: pointer;
`;

export default StyledButton;

This is my parent component. Where I used settimeout and import Button component.

const handleClick = () => { 
    setTimeout(() => {
      console.log("check it out") //It does display when the button click 
    }, 5000);
  }

   //This is the Button 
       <Button size="medium" themes="primary" onClick={handleClick}>click</Button>

Solution

  • You should create a loading state by using useState hook if your parent component is function component, if using class component you can define a state in constructor like this this.state = { loading: false } in your parent component, and set it to true in handleClick and pass the state as prop to your Button component:

    // In case function component
    const [loading, setLoading] = useState(false);
    const handleClick = () => {
        setLoading(true); 
        setTimeout(() => {
          setLoading(false); // When finish, set it to false
          console.log("check it out") //It does display when the button click 
        }, 5000);
    }
    
    //This is the Button 
    <Button 
      size="medium" 
      themes="primary" 
      onClick={handleClick} 
      loading={loading}
    >
       click
    </Button>