javascriptreactjsjsx

Ensure that Promise has been resolved before rendering view


What specific syntax needs to be changed in the code below in order for the value returned by the currentSession() function to correctly set the html that is rendered to the user in the web browser?

Specifically, what needs to be changed so that the console.log("other groups is: ", groups); line returns a valid array of groups, and so that the logic in the {isGroupOne ? <p>Welcome back!</p> : <p>Please log in.</p>} line is able to execute with isGroupOne having been returned as True?

USE CASE:

A React component needs to render different content for users that are in different user groups. The groups for a given user are indeed returned from a backend service.

PROBLEM:

The problem is that the React component is printing out the user's groups as a pending promise instead of as a valid array of group names that can be transformed into a Boolean to indicate whether or not the user is in a specific group.

As a result, the wrong content is being printed to the web browser in the example broken code below. Currently, Please log in. is being printed in the browser, even though you can see from the logs below that the promise eventually resolves to give isGroupOne the value of true.

MINIMAL CODE TO REPRODUCE PROBLEM:

Here is the minimal code required to reproduce the problem:

import { fetchAuthSession } from 'aws-amplify/auth';

function Index() {
  let isGroupOne = false;

  async function currentSession() {
    try {
      const { accessToken, idToken } = (await fetchAuthSession()).tokens ?? {};
      const groups = accessToken["payload"]["cognito:groups"]
      console.log("these groups: ", groups);
      return groups;
    } catch (err) {
      console.log(err);
    }
  }

  const groups = currentSession().then(groups => {
    console.log("those groups is: ", groups);
    //iterate groups array to see if "GroupOne" is in the array
    groups.forEach(group => {
      if (group === "GroupOne") {
        console.log("User is in GroupOne group");
        isGroupOne = true;
        console.log("isGroupOne: ", isGroupOne);
      }
    });

  });
  console.log("other groups is: ", groups);

  return (
    <div>
        {isGroupOne ? <p>Welcome back!</p> : <p>Please log in.</p>}
    </div>

  );
}

export default Index;

LOGS ILLUSTRATING THE PROBLEM:

The following is printed to the console when the page defined by the above minimal code is rendered in the web browser:

other groups is:  Promise {<pending>}
index.js:29 other groups is:  Promise {<pending>}
index.js:10 these groups:  ['GroupOne']
index.js:18 those groups is:  ['GroupOne']
index.js:22 User is in GroupOne group
index.js:24 isGroupOne:  true
index.js:10 these groups:  ['GroupOne']
index.js:18 those groups is:  ['GroupOne']
index.js:22 User is in GroupOne group
index.js:24 isGroupOne:  true

Solution

  • It is a classic Frontend issue: rendering data that is asynchronously fetched from Backend.

    There is no magic, the only solution is to conditionally render a loading UI while waiting for the Backend data, then once received, you can proceed as originally planned. Some Frontend frameworks or libraries may provide helpers to automatically manage this behavior, but it always implements the same scheme under the hood.

    For example in your case, using useAsync hook from react-use:

    npm install react-use
    
    import React, { useMemo } from "react";
    import { useAsync } from "react-use";
    
    function Index() {
      const groupsAsync = useAsync(currentSession, []); // Fill the dependency array if session should be refreshed on some criteria
    
      const isGroupOne = useMemo(() =>
        !groupsAsync.loading && 
        groupsAsync.value?.some(
          (group) => group === "GroupOne"
        ), [groupsAsync.loading, groupsAsync.value]);
    
      return (
        <div>
          {groupsAsync.loading
            ? "Loading..." // Any UI you want as loader, e.g. skeleton, or nothing at all...
            : isGroupOne
            ? <p>Welcome back!</p>
            : <p>Please log in.</p>}
        </div>
      );
    }