Search code examples
reactjsuse-effect

How to wait for setState in useEffect until render?


  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?


Solution

  • 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.