0

This is my code, in which I am running a map function on an array of objects, there are 2 condition check for each object, there is an async operation in the first condition and then the return statement. The error is this-

Uncaught Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

<ChannelList
          style={{
            padding: '8px',
          }}
        >
          {Promise.all(channelList.map((channel) => 
            {
              console.log(channel);
              const channelMetaData = JSON.parse(channel?.Metadata);
              //If it is one-to-one chat
              console.log(channelMetaData);  
              if(channelMetaData?.isOneToOne){
                listChannelMemberships(channel.ChannelArn,userId).then(async (res)=>{
                  if(res[0].Member.Name==userData.username){
                    console.log("I am this user",res[0].Member.Name);
                    var otherPerson = res[1].Member.Name;
                  }else if(res[1].Member.Name==userData.username){
                    console.log("I am this user",res[1].Member.Name);
                    var otherPerson = res[0].Member.Name;
                  }
                  console.log(otherPerson);
                  return <ChannelItem
                    key={channel.ChannelArn}
                    name={otherPerson}
                    actions={loadUserActions(userPermission.role, channel)}
                    isSelected={channel.ChannelArn === activeChannel.ChannelArn}
                    onClick={e => {
                      e.stopPropagation();
                      channelIdChangeHandler(channel.ChannelArn);
                    }}
                    unread={unreadChannels.includes(channel.ChannelArn)}
                    unreadBadgeLabel="New"
                    >
                    </ChannelItem>
                  })
             }
             else{
              return <ChannelItem
              key={channel.ChannelArn}
              name={channel.Name}
              actions={loadUserActions(userPermission.role, channel)}
              isSelected={channel.ChannelArn === activeChannel.ChannelArn}
              onClick={e => {
                e.stopPropagation();
                channelIdChangeHandler(channel.ChannelArn);
              }}
              unread={unreadChannels.includes(channel.ChannelArn)}
              unreadBadgeLabel="New"
              >
              </ChannelItem>
             }
            }
            ))}
        </ChannelList>
Prajwal Kulkarni
  • 1,480
  • 13
  • 22
  • 1
    If you separated this code out into helper functions (like API calls, and actions) from what the component needs to render you'd make it much easier to understand and debug. – Andy Nov 06 '21 at 13:39

1 Answers1

3

As the error says, objects are not valid children in React. You're using the result of Promise.all (a Promise object) as the result of that JSX expression:

{Promise.all(/*...*/)}

Instead, you need to wait for the promise from Promise.all to be fulfilled with the array of results. You can't do that when rendering, you need to do it outside of rendering and then set state based on the result, rendering that state instead.

Here's a simple example (look at the code in the <script type="text/babel"> script block; sadly, Stack Snippets use an out of date version of Babel so we can't do it normally):

<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

<script type="text/babel" data-presets="es2017,react">
const {useState, useEffect} = React;

// Stand-in for actual retriever function
function getThing(id, signal) {
    //                ^^^^^^−−−− this is so you can pass
    // it to anything that supports signals to cancel the
    // work if the component unmounts.
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(`Done getting thing #${id}`);
            resolve({id});
        }, Math.floor(Math.random() * 800));
    });
}

const Thing = ({thing}) => <div>{thing.id}</div>;

const Example = () => {
    const [things, setThings] = useState(null);

    // Load on mount
    useEffect(() => {
        const controller = new AbortController();
        const {signal} = controller;
        (async () => {
            try {
                const things = await Promise.all([1, 2, 3].map(async (id) => {
                    console.log(`Getting thing #${id}...`);
                    const thing = await getThing(id, {signal});
                    return <Thing thing={thing} />;
                }));
                setThings(things);
            } catch (error) {
                // ...handle/report error...
                console.error(error.message, error.stack);
            }
        })();
        return () => {
            controller.abort();
        }
    }, []); // [] = just on mount

    // Return
    return <div>
        {things === null && <em>Loading...</em>}
        {things}
    </div>;
};

ReactDOM.render(<Example />, document.getElementById("root"));
</script>
<script src="https://unpkg.com/regenerator-runtime@0.13.2/runtime.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.10.3/babel.min.js"></script>

That version stores actual React elements in state. You might instead store just the information you need (an object or whatever) and then render the elements as needed:

<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

<script type="text/babel" data-presets="es2017,react">
const {useState, useEffect} = React;

// Stand-in for actual retriever function
function getThing(id, signal) {
    //                ^^^^^^−−−− this is so you can pass
    // it to anything that supports signals to cancel the
    // work if the component unmounts.
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(`Done getting thing #${id}`);
            resolve({id});
        }, Math.floor(Math.random() * 800));
    });
}

const Thing = ({thing}) => <div>{thing.id}</div>;

const Example = () => {
    const [things, setThings] = useState(null);

    // Load on mount
    useEffect(() => {
        const controller = new AbortController();
        const {signal} = controller;
        (async () => {
            try {
                const things = await Promise.all([1, 2, 3].map(async (id) => {
                    console.log(`Getting thing #${id}...`);
                    const thing = await getThing(id, {signal});
                    return thing;
                }));
                setThings(things);
            } catch (error) {
                // ...handle/report error...
                console.error(error.message, error.stack);
            }
        })();
        return () => {
            controller.abort();
        }
    }, []); // [] = just on mount

    // Return
    return <div>
        {things === null && <em>Loading...</em>}
        {things && things.map(thing => <Thing thing={thing} />)}
    </div>;
};

ReactDOM.render(<Example />, document.getElementById("root"));
</script>
<script src="https://unpkg.com/regenerator-runtime@0.13.2/runtime.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.10.3/babel.min.js"></script>

The map callback also has issues. For instance, the true branch in if(channelMetaData?.isOneToOne){ doesn't return anything. You have a return in a then callback, but you don't have one in the map callback. You may find it useful to use an async function for the map callback and use await rather than .then. That might look something like this:

// (In an `async` function)
const result = await Promise.all(channelList.map(async (channel) => {
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^
    console.log(channel);
    const channelMetaData = JSON.parse(channel?.Metadata);
    //If it is one-to-one chat
    console.log(channelMetaData);
    if (channelMetaData?.isOneToOne) {
        const res = await listChannelMemberships(channel.ChannelArn, userId);
// −−−−−−−−−−−−−−−−−^^^^^
        let otherPerson; // Declare it once (and `var` is deprecated)
        if (res[0].Member.Name == userData.username) {
            console.log("I am this user", res[0].Member.Name);
            otherPerson = res[1].Member.Name;
        } else if (res[1].Member.Name == userData.username) {
            console.log("I am this user", res[1].Member.Name);
            otherPerson = res[0].Member.Name;
        }
        console.log(otherPerson);
        return <ChannelItem
            key={channel.ChannelArn}
            name={otherPerson}
            actions={loadUserActions(userPermission.role, channel)}
            isSelected={channel.ChannelArn === activeChannel.ChannelArn}
            onClick={e => {
                e.stopPropagation();
                channelIdChangeHandler(channel.ChannelArn);
            }}
            unread={unreadChannels.includes(channel.ChannelArn)}
            unreadBadgeLabel="New"
        >
        </ChannelItem>;
    } else {
        return <ChannelItem
            key={channel.ChannelArn}
            name={channel.Name}
            actions={loadUserActions(userPermission.role, channel)}
            isSelected={channel.ChannelArn === activeChannel.ChannelArn}
            onClick={e => {
                e.stopPropagation();
                channelIdChangeHandler(channel.ChannelArn);
            }}
            unread={unreadChannels.includes(channel.ChannelArn)}
            unreadBadgeLabel="New"
        >
        </ChannelItem>;
    }
}));
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I have edited the code in the question,now as you can see the Promise.all is inside the ChannelList component,so how can i display the result of the Promise.all inside the ChannelList component?I understand that i should use await. – Arun Pratap Singh Nov 06 '21 at 14:24
  • @ArunPratapSingh - Basically the way I show in the example. The `ChannelList` will need to become stateful, and have a state where it's waiting for the information, then a state where it has the information. (Either that, or its parent needs to pass it the information as a prop and not even render it until it [the parent] has the info.) – T.J. Crowder Nov 06 '21 at 14:27
  • Okay,got it.Thankyou. – Arun Pratap Singh Nov 06 '21 at 14:29