0

My component code does something like this:

const [imageMap, setImageMap] = useState([]);

//...

useEffect(async () => {
        const x = await getList();
        setImageMap(x);
}, []);

console.log(imageMap);

The console.log works fine and prints the list that returned from the function getList() (which is a promise and takes a while to finish, since it retrieves data from the internet).

In the return statement of my component, I use imageMap.map(item => ...) to exhibit all elements of imageMap but it seems to be empty, since nothing appears. Am I doing something wrong?

Daniel
  • 7,357
  • 7
  • 32
  • 84
  • The first render will use the default empty array. Then when getList() completes and the updated state is set it triggers another render with the updated array. You are logging before that request completes – charlietfl Apr 16 '21 at 00:59
  • But the log is correct, it prints the list as it should be. The return statement, on the other hand, gives me nothing, as if `imageMap` was empty. – Daniel Apr 16 '21 at 01:01
  • Try `console.log(JSON.stringify(imageMap))` and you will see `"[]"`. See [Weird behavior with objects & console.log](https://stackoverflow.com/questions/23429203/weird-behavior-with-objects-console-log) – charlietfl Apr 16 '21 at 01:08
  • True, with this stringify it doesn't print anything, otherwise without, it prints, pretty strange. Any ideas on how to deal with that? The log part is just to clarify my function is working and returning things, but the problem is the state is empty when I need it (in the return statement). – Daniel Apr 16 '21 at 01:23
  • If you read the Wierd Behavior link it's not strange. I don't thing you can make the useEffect callback itself async and the first answer is correct approach – charlietfl Apr 16 '21 at 01:28
  • My problem was `getList()` had a loop where each iteration called a promise, that for some reason broken the workflow. I added all promises in a list and called `Promise.All`. Then I used `async` and `await` to get the list of answers of the promises. Problem solved. – Daniel Apr 16 '21 at 03:11

2 Answers2

0
const [imageMap, setImageMap] = useState([]);

//...

useEffect(() => {
        async function fetchData() {
            const x = await getList();
            setImageMap(x);
        }
        fetchData()
}, []);

// This will run one time
console.log(imageMap);

// This will run on each change    
useEffect(() => {
    console.log(imageMap);
}, [imageMap]);
ibra
  • 390
  • 3
  • 13
  • An educational explanation would help – charlietfl Apr 16 '21 at 01:10
  • Doesn't work and doesn't seem much different from what I already have. – Daniel Apr 16 '21 at 01:18
  • @charlietfl In previous syntax Promise will be returned to `useEffect` but it expects a callback or nothing to be returned – ibra Apr 16 '21 at 01:25
  • I meant in the answer to explain what you did and why so others reading this understand. Code only answers are not very helpful – charlietfl Apr 16 '21 at 01:27
  • @Daniel `console.log` is outside `useEffect` so it will log on first render. If you need to log the imageMap on every change you must put it inside another useEffect. but you need to pass imageMap inside the empty array in the second param of useEffect to be watched – ibra Apr 16 '21 at 01:29
  • @charlietfl You are right, that was a mistake to answer without explanation. – ibra Apr 16 '21 at 01:30
-1

The imageMap.map(item => ...) might be executed before the data recevied. Add a condition before execute imageMap.map(item => ...), to check if the imageMap is not empty

something like this:

if (imageMap.length !== 0) {
  imageMap.map(item => ...)
}

or you can add condition inside jsx:

<SomeComponent>
  {imageMap.length !== 0 ?
    <>
      {imageMap.map(item => ...)}
    </>
    :
    <OtherComponent />
  }
</SomeComponent>