Home > Software design >  How to wait for setState in useEffect until render?
How to wait for setState in useEffect until render?

Time:03-07

  let [item, setItem] = useState({});
  let [comments, setComments] = useState([]);
  useEffect(async () => {
    await axios
      .all([
        axios.get(`https://dummyapi.io/data/v1/post/${id}`, {
          headers: { "app-id": process.env.REACT_APP_API_KEY }
        }),
        axios.get(`https://dummyapi.io/data/v1/post/${id}/comment`, {
          headers: { "app-id": process.env.REACT_APP_API_KEY }
        })
      ])
      .then(
        axios.spread((detail, comment) => {
          setItem({ ...detail.data })
          setComments([...comment.data.data])
        })
      )
      .catch((detail_err, comment_err) => {
        console.error(detail_err);
        console.error(comment_err);
      });
  }, []);

i setStated like above. and I was trying to use the State in return(), but it seems it didn't wait for the data set.

return (
          <div>
            {item.tags.map((tag, index) => {
              return <Chip label={tag} key={index} />
            })}
          </div>
)

because i got an error message like this : Uncaught TypeError: Cannot read properties of undefined (reading 'map'). Since i initialized 'item' just empty {object}, so it can't read 'item.tags', which is set by setState in useEffect.

How can i wait for the data set?

CodePudding user response:

let [item, setItem] = useState({});

Your initial state is an empty object, and there will always be at least one render that uses this initial state. Your code thus needs to be able to work correctly when it has this state. For example, you could check if item.tags exists before you try to use it:

if (item.tags) {
  return (
    <div>
      {item.tags.map((tag, index) => {
        return <Chip label={tag} key={index] />
      })}
    </div>
  );
} else {
  return <div>Loading...</div>
}

Alternatively, you could change your initial state so it has the same shape that it will have once loading has finished:

let [item, setItem] = useState({ tags: [] });

CodePudding user response:

In generic, it would set a state isFetched to determine if the data from api is ready or not. And when the isFetched equal to true, it means the item.tags have value.

const [isFetched, setIsFetched] = useState(false);
useEffect(async () => {
  await axios.all(...).then(() => {
    ...
    ...
    setIsFetched(true);
  })
}, [])

// You could return null or an Loader component meaning the api is not ready
if (!isFetched) return null;
return (
      <div>
        {item.tags.map((tag, index) => {
          return <Chip label={tag} key={index} />
        })}
      </div>
)

On the other hand, you could use optional chaining to avoid using map from an undefined value (that is item.tags), the right way is replace item.tags.map to item.tags?.map.

CodePudding user response:

Initially, item is an empty JSON ({}). You should be using the optional chaining operator(?.) to easily get rid of the null or undefined exceptions.

return (
          <div>
            {item?.tags?.map((tag, index) => {
              return <Chip label={tag} key={index} />
            })}
          </div>
)
  • Related