skip to Main Content

The problem: I have a Parent component that has multiple buttons. Each button will fetch data from the API for different endpoints. The requirement is when a button is clicked it should go into a loading state and the other buttons should be disabled till the call completes.

What I have tried: I created two separate button components <Button1> and <Button2> which have their own isLoading state. I have declared a disabled state in the Parent component and I am passing this and its set handler down as props to the child button components. When the onClick function is hit I set the isLoading state to true and the setDisabled property to true notifying the parent component about it. I have written the logic for the disabled attribute on each child button component as diabled={!isLoading && disabled} which means that the button will be disabled if it is not the one making the API call/loading.

Adding the code sandbox for this approach’s implementation and the one needed: https://codesandbox.io/s/sharp-hill-r4rsk0?file=/src/App.js

Solution support required: I need a way to reduce code duplication by using one single <Button> component instead of multiple button components as a child in parent multiple times but don’t know how to separate their isLoading, disabled, and onClick states and handlers when either of them is clicked and it should work as in the approach 1 as seen in the sandbox above.

2

Answers


  1. React allows you to keep multiple instances of the same component by passing different props. It automatically manages the different states of each instance. So the same approach that you are currently using with different button components can be used with single button component.

    Here the modified codesandbox having single button component with multiple instances.

    The button component is as follows.

    function Button({ label, changeDisabledState, disabled }) {
      const [isLoading, setIsLoading] = useState(false);
    
      const onSubmit = () => {
        changeDisabledState(true);
        setIsLoading(true);
        setTimeout(() => {
          changeDisabledState(false);
          setIsLoading(false);
        }, 3000);
      };
    
      return (
        <div>
          {isLoading ? (
            <div>Loading...</div>
          ) : (
            <button disabled={!isLoading && disabled} onClick={onSubmit}>
              {label}
            </button>
          )}
        </div>
      );
    }
    

    when the function onSubmit is called on click, it will change the isLoading state of only that particular button instance to true. Other instances of this component will still have isLoading states as false. The disabled state is being maintained in the parent component. So this state will be same for all the button instances. As we are applying disabled state as disabled={!isLoading && disabled}, the current clicked button will not be disabled because isLoading will be true in this instance.

    Login or Signup to reply.
  2. Please check whether my code meets your requirement.

    import { useState } from "react";
    
    const Button = ({ children, loading, disabled, onClick }) => {
      return (
        <button disabled={disabled} onClick={onClick}>
          {loading ? "Loading..." : children}
        </button>
      );
    };
    
    const ButtonWrapper = ({ url, onFetching, disabled, children }) => {
      const [loading, setLoading] = useState(false);
      const [data, setData] = useState();
    
      const getData = () => {
        setLoading(true);
        onFetching(true);
        fetch(url)
          .then((response) => response.json())
          .then((json) => setData(json))
          .finally(() => {
            setLoading(false);
            onFetching(false);
          });
      };
    
      return (
        <Button loading={loading} disabled={disabled} onClick={() => getData()}>
          {children}
        </Button>
      );
    };
    
    export default function App() {
      const [isFetching, setIsFetching] = useState(false);
    
      return (
        <div className="App">
          <ButtonWrapper
            onFetching={setIsFetching}
            disabled={isFetching}
            url="https://jsonplaceholder.typicode.com/posts"
          >
            Fetch Posts
          </ButtonWrapper>
    
          <ButtonWrapper
            onFetching={setIsFetching}
            disabled={isFetching}
            url="https://jsonplaceholder.typicode.com/albums"
          >
            Fetch Albums
          </ButtonWrapper>
    
          <ButtonWrapper
            onFetching={setIsFetching}
            disabled={isFetching}
            url="https://jsonplaceholder.typicode.com/users"
          >
            Fetch Users
          </ButtonWrapper>
        </div>
      );
    }
    

    I have coded in the same file but you can make a separate file, also make sure to change the network Network throttling to Slow 3G or something to see the functionality clearly.

    demo: https://codesandbox.io/s/cranky-shannon-vtwmz8?file=/src/App.js

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search