1

I am trying to build a simple UI displaying the progress of an ethereum transaction to mint an NFT using ERC-20 token as payment. So a single mint involves these steps:

  1. Get Token metadata URI from backend server
  2. Get token spending approval on ERC-20 contract
  3. Mint the NFT
  4. Lock the NFT in a staking contract

On react side, I am maintaining the steps and status of each step on react side as:

The Data types

enum LoadingState {
  DONE,
  IN_PROGRESS,
  PENDING,
  FAILED,
}

type Step = {
  title: string;
  state: LoadingState;
  error?: Error;
};

const _steps: Step[] = [
  {
    title: "Getting Token metadata URI from backend server",
    state: LoadingState.PENDING,
  },
  {
    title: "Getting token spending approval",
    state: LoadingState.PENDING,
  },
  {
    title: "Minting NFT",
    state: LoadingState.PENDING,
  },
  {
    title: "Stacking NFT",
    state: LoadingState.PENDING,
  },
];

Inside react component; I have some long code; but I will summarise the flow with some comments. At the moment I am calling a single stackAndMint(..) function that then calls a function for each individual step.

const MintComponent {
   const [steps, setSteps] = useState<>(_steps);

   const stackAndMint = async (..) {
      // Update the step 0 state to IN_PROGRESS
      setSteps((steps_) => {
         steps_[0].state = LoadingState.IN_PROGRESS;
         return steps_;
      });
      const metadata = await getMetaData(tokenID);
      
      // Update step 0 state to DONE and step 1 state to IN_PROGRESS
      setSteps((steps_) => {
         steps_[0].state = LoadingState.DONE;
         steps_[1].state = LoadingState.IN_PROGRESS;
         return steps_;
      });

      // Getting Token Approval
      await approveTokens(spender, amount);
      
      // Update step 1 state to DONE and step 2 state to IN_PROGRESS
      setSteps((steps_) => {
         steps_[1].state = LoadingState.DONE;
         steps_[2].state = LoadingState.IN_PROGRESS;
         return steps_;
      });

      // MintToken
      await mintToken(owner, tokenID, tokenURI);
      
      // Update step 2 state to DONE and step 3 state to IN_PROGRESS
      setSteps((steps_) => {
         steps_[2].state = LoadingState.DONE;
         steps_[3].state = LoadingState.IN_PROGRESS;
         return steps_;
      });

      // Stake token
      await stakeToken(tokenID);
      
      // Update step 3 state to DONE
      setSteps((steps_) => {
         steps_[3].state = LoadingState.DONE;
         return steps_;
      });

   }

}

Now I have a simple list in the UI as following:

<ul>
    {steps.map((step) => (
          <li>{getIcon(step.state)} - {step.title}</li>
     ));
    }
</ul>

Now I want that as the code executes and state of each step changes, the icon beside each step's title should change as per the state. But; in my case, the icons remains unchanged and then as all the steps are completed; all the icons changes to completed state at once at the end.

As per my exploration the reason behind this is; the react batches the state update to minimize the re-rendring of the components UI.

So my query is; How can i achieve the the result so that the icon behind each step updates as the step state is updated.

1

1 Answer 1

2

The stackAndMint handler is simply just mutating state and returning it instead of returning new state. The result is that React doesn't "see" that state changed and the UI needs to rerender.

Update to shallow copy the current state and update and return a new state reference.

Example:

setSteps((steps_) => {
  // Shallow copy the array into new array reference
  const nextSteps = steps_.slice();

  // Create new element object reference
  nextSteps[0] = {
    // Shallow copy the previous element
    ...nextSteps[0],
    // Update properties
    state: LoadingState.IN_PROGRESS
  };

  // Return new new state value
  return nextSteps;
});

Code:

const stackAndMint = async (..) {
  // Update the step 0 state to IN_PROGRESS
  setSteps((steps_) => {
    const nextSteps = steps_.slice();
    nextSteps[0] = {
      ...nextSteps[0],
      state: LoadingState.IN_PROGRESS
    };
    return nextSteps;
  });

  const metadata = await getMetaData(tokenID);
      
  // Update step 0 state to DONE and step 1 state to IN_PROGRESS
  setSteps((steps_) => {
    const nextSteps = steps_.slice();
    nextSteps[0] = {
      ...nextSteps[0],
      state: LoadingState.DONE
    };
    nextSteps[1] = {
      ...nextSteps[1],
      state: LoadingState.IN_PROGRESS
    };
    return nextSteps;
  });

  // Getting Token Approval
  await approveTokens(spender, amount);
  
  // Update step 1 state to DONE and step 2 state to IN_PROGRESS
  setSteps((steps_) => {
    const nextSteps = steps_.slice();
    nextSteps[1] = {
      ...nextSteps[1],
      state: LoadingState.DONE
    };
    nextSteps[2] = {
      ...nextSteps[2],
      state: LoadingState.IN_PROGRESS
    };
    return nextSteps;
  });

  // MintToken
  await mintToken(owner, tokenID, tokenURI);
  
  // Update step 2 state to DONE and step 3 state to IN_PROGRESS
  setSteps((steps_) => {
    const nextSteps = steps_.slice();
    nextSteps[2] = {
      ...nextSteps[2],
      state: LoadingState.DONE
    };
    nextSteps[3] = {
      ...nextSteps[3],
      state: LoadingState.IN_PROGRESS
    };
    return nextSteps;
  });

  // Stake token
  await stakeToken(tokenID);
  
  // Update step 3 state to DONE
  setSteps((steps_) => {
    const nextSteps = steps_.slice();
    nextSteps[3] = {
      ...nextSteps[3],
      state: LoadingState.DONE
    };
    return nextSteps;
  });
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.