'React DOM update on fetch

I'm working on a typescript react project to add and display some accounts on a wallet.. I have created a useAPI function that makes my calls and returns the state of the call as well as the retrieved data

api/index.ts

import { useCallback, useEffect, useState } from "react";

export type ApiMethod = "GET" | "POST";

export type ApiState = "idle" | "loading" | "done" | "failed";

const fetcher = async (
    url: string,
    method: ApiMethod,
    payload?: string,
  ): Promise<any> => {
    const requestHeaders = new Headers();
    requestHeaders.set("Content-Type", "application/json");
    const res = await fetch(url, {
      body: payload ? JSON.stringify(payload) : undefined,
      headers: requestHeaders,
      method,
    });
    //const resobj = await res.json();
    return res;
  };

export function useApi(
  url: string,
  method: ApiMethod,
  payload?: any
): {
  apiState: ApiState;
  data: [];
  execute: () => void;
} {
  const [apiState, setApiState] = useState<ApiState>("idle");

  const [data, setData] = useState<[]>([]);
  const [toCallApi, setApiExecution] = useState(false);

  const execute = () => {
    console.log("executing api call");
    setApiExecution(true);
  };


  const fetchApi = useCallback(async () => {
    fetcher(url, method, payload)
    .then((res) => {
        if(!res.ok){
            return "error"
        }
        return res.json()
    })
    .then((result) => {
        if(result === "error") setApiState("failed")
        else{
            setData(result)
            setApiState("done")
        }
    })
  }, [method, payload, url]);

  // call api
  useEffect(() => {
    if (toCallApi &&  apiState === "idle") {
      console.log("calling api");
      setApiState("loading");
      fetchApi();
    }
  }, [apiState, fetchApi, toCallApi]);

  console.log(url,data, apiState, payload)   

  return {
    apiState,
    data,
    execute,
  };
}

After this I call this function on my app.tsx first inside a useEffect hook to retrieve the accounts when my DOM loads.

And then I have the possibility to create a new Account, and display with the previous accounts.

PROBLEM:

The problem here is, when I create a new Account or when I get a failed state, I cannot display the account or the error message until I reload my page.

Though I need to update my DOM once I create an account or get an error to actually display it.

app.tsx

function App() {
 
  const [selectedCurrency, setSelectedCurrency] = useState("")
  const [errorIsOpen, setErrorIsOpen] = useState(false)

  const accounts = useApi(
    `${process.env.REACT_APP_PROXY}/accounts`,
    "GET",
  )

  const wallets = useApi(
    `${process.env.REACT_APP_PROXY}/wallets`,
    "GET",
  )

  const createWallet = useApi(
    `${process.env.REACT_APP_PROXY}/accounts`,
    "POST",
    {currency: selectedCurrency}
  )

  const [isOpen, setIsOpen] = useState(false)

  useEffect(() => {
    accounts.execute()
  }, [accounts])

  useEffect(() => {
    if(isOpen){
      wallets.execute()
    }
  }, [isOpen, wallets])

  const handleWalletCreate = async () => {
    createWallet.execute()
    accounts.execute()
    if(createWallet.apiState === "failed"){
      setErrorIsOpen(true)
    }
  }
 
  

  return (
    <>
      <Modal isOpen={isOpen}>
        {
          wallets.apiState === "loading" && 
          <div className='center'>
            <Loader size={100} width={8}/>
          </div>
        }
        { 
          (wallets.apiState === "done" && wallets.data)  &&
          <div className="modal-content">
            <div className='modal-content-header'>
              <h3>Add new wallet</h3>
              <span onClick={() => setIsOpen(!isOpen)}>
                <Close />
              </span>
            </div>

            <div className='info'>
              <p>
              The crypto wallet will be created instantly and be available in your list of wallets.
              </p>
            </div>

            <div className='form'>
              <h4>Select wallet</h4>
              <select defaultValue="DEFAULT" onChange={(e) => setSelectedCurrency(e.target.value)}>
                <option value="DEFAULT" disabled>Select currency</option>
                {wallets.data.map((wallet: any, i) => 
                <option key={i} value={wallet.currency}>{wallet.name}</option>
                  )}
              </select>
              <div className='btn-container'>
                  <button className='btn-submit' onClick={handleWalletCreate}>
                    Create wallet
                  </button>
              </div>
            </div>

            {
              
              (createWallet.apiState === "failed" || errorIsOpen) &&
              <div className="create-wallet-error">
                <div>
                  <NetworkError />
                  <span>Network Error</span>
                </div>
                <CloseError onClick={() => setErrorIsOpen(!errorIsOpen)}/>
              </div>
              
            }
          </div>
        }

        {
          wallets.apiState === "failed" &&
          <div className='center'>
            <div className='network-error'>
              <div className='error-logo'>
                <ErrorIcon />
              </div>
              <h3>Network Error</h3>
              <div className="btn-container">
                <button className="btn-submit" onClick={wallets.execute}>
                  Try again
                </button>
              </div>
            </div>
          </div>
        }
      </Modal>

      <div className="main">
        <header>
          <div className="header-container"> 
            <div className='logo'>
                <Logo />
            </div>
            <div className='profile'>
              <span className='profile-picture'>
                O
              </span>
              <h3 className="profile-name">
                Oluwatobi Akindunjoye
              </h3>
            </div>
          </div>
        </header>

        <section className='dashboard'>
          <div className='dashboard-container'>
            <div className='left-section'>
              <ul>
                <li>Wallets</li>
                <li>Prices</li>
                <li>Peer2Peer</li>
                <li>Activity</li>
                <li>Settings</li>
              </ul>
            </div>
            <div className='right-section'>
              <div className='right-section-header'>
                <h1>Wallets</h1>
                <span onClick={() => setIsOpen(!isOpen)} className={accounts.apiState === "failed" ? "hide" : ""}>
                  + Add new wallet
                </span>
              </div>
              <div className={accounts.apiState === "failed" ? "hide" : "separator"}></div>
              <div className='right-section-content'>
                { 
                  accounts.apiState === "loading" && 
                  <div className='center'>
                    <Loader size={100} width={8}/>
                  </div>
                }

                {
                  accounts.apiState === "failed" &&
                  <div className='center'>
                    <div className='network-error'>
                      <div className='error-logo'>
                        <ErrorIcon />
                      </div>
                      <h3>Network Error</h3>
                      <div className="btn-container">
                        <button className="btn-submit" onClick={accounts.execute}>
                          Try again
                        </button>
                      </div>
                  </div>
                  </div>
                  
                }
                {
                  (accounts.apiState === "done" && accounts.data) &&
                  <div className='accounts-cards'>
                      { accounts.data.length &&
                        accounts.data.map((account: any, i) => 
                          <div className='card' key={i}>
                            <div className='card-header'>
                              <img src={account.imgURL} alt={account.name} />
                              <h4>{account.name}</h4>
                            </div>
                            <div className='card-balance'>
                              {account.balance.replace(/\B(?=(\d{3})+(?!\d))/g, ",")} {account.currency}
                            </div>
                            <div className='card-btn'>
                              <Arrow />
                            </div>
                          </div>
                        )
                      }
                  </div>
                }
              </div>
            </div>
          </div>
        </section>
      </div>
    </>
  );
}

Thanks for your help.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source