1

I'm using useEffect to fetch some data from Trello and set some states. First I grab the card I'm looking for and call setCard and setCardLocation. Everything is working fine. Then I get into my else case and no matter what I do setPublishDate will never be set, the loop continues to run. Why do all of these other hooks work but my last one doesn't? Thanks.

export default function Home(props) {
  const [performedFetch, setPerformedFetch] = useState(false);
  const [slug, setSlug] = useState(null);
  const [cardLocation, setCardLocation] = useState(1);
  const [card, setCard] = useState(null);
  const [publishDate, setPublishDate] = useState(null);

  const key = ''; // imagine these are here
  const token = '';

  useEffect(() => {
    setSlug(
      new URLSearchParams(window.location.search).get('slug')
    );

    if (!performedFetch && !!slug) {
      fetch(`https://api.trello.com/1/lists/${listId}/cards?key=${key}&token=${token}`)
          .then(response => response.json())
          .then(data => {
            setPerformedFetch(true);

            data.forEach((c, index) => {
              if (c.desc.includes(slug)) {                    
                setCard(c)
                setCardLocation(index + 1)
              } else if (!publishDate && index > cardLocation) { 
                console.log(publishDate); // why is this always null?? also runs multiple times

                const name = c.name;
                const frontHalf = name.split("/")[0].split(" ");
                const month = frontHalf[frontHalf.length - 1];
                const day = name.split("/")[1].split(")")[0];
                setPublishDate(`${month}/${day}`);
              }
            });
        });
    }
  });
2
  • I think this is related to this: reactjs.org/docs/… Commented May 16, 2020 at 20:24
  • 1
    Two useEffect() as @TaghiKhavari says, or just use slug locally within the useEffect - const slug = new URLSearchParams(window.location.search).get('slug'). Commented May 16, 2020 at 21:04

2 Answers 2

1

As already mentioned by @TaghiKhavari, you should have two useEffects (Multiple effects to separate concerns).

Also, it is important to optimize the performance by skipping effects by providing a dependency array as second argument to the useEffect. So the effect will only re-run if any of its dependencies would change.

First effect for slug:

useEffect(() => {
  setSlug(
    new URLSearchParams(window.location.search).get('slug')
  );
}, []) // Note: Remove "[]" if you want to set slug at each update / render Or keep it if you want to set it only once (at mount)

Second effect to fetch and set card and other details:

useEffect(() => {
  if (!performedFetch && slug) {
    fetch(
      `https://api.trello.com/1/lists/${listId}/cards?key=${key}&token=${token}`
    )
      .then((response) => response.json())
      .then((data) => {
        setPerformedFetch(true)

      // Note: if there can be only ONE matching card
      const index = data.findIndex((card) => card.desc.includes(slug))
      if (index > -1) {
        const card = data[index]
        setCard(card)
        setCardLocation(index + 1)
        const name = card.name
        const frontHalf = name.split('/')[0].split(' ')
        const month = frontHalf[frontHalf.length - 1]
        const day = name.split('/')[1].split(')')[0]
        setPublishDate(`${month}/${day}`)
      }

      // Setting State in a LOOP? is a problem
      /*
      data.forEach((card, index) => {
        if (card.desc.includes(slug)) {
          setCard(card)
          setCardLocation(index + 1)
        } else if (!publishDate && index > cardLocation) {
          const name = card.name
          const frontHalf = name.split('/')[0].split(' ')
          const month = frontHalf[frontHalf.length - 1]
          const day = name.split('/')[1].split(')')[0]
          setPublishDate(`${month}/${day}`)
        }
      })*/

    })
  }
}, [slug, performedFetch])

Set states may be async to improve performance:

So, you should not set states in a loop as you are doing currently. If you must iterate through a loop and set all or few elements of the array in state, you can loop through the array and push all relevant items in a local array variable and set it to state after loop ends. Hope it helps!

Sign up to request clarification or add additional context in comments.

Comments

1

It's because usually react states updates asynchronously and at the time you're checking for slug it hasn't set yet

you need to do something like this:

function Home(props) {
  const [performedFetch, setPerformedFetch] = useState(false);
  const [slug, setSlug] = useState(null);
  const [cardLocation, setCardLocation] = useState(1);
  const [card, setCard] = useState(null);
  const [publishDate, setPublishDate] = useState(null);

  const key = ""; // imagine these are here
  const token = "";

  useEffect(() => {
    setSlug(new URLSearchParams(window.location.search).get("slug"));
  });

  useEffect(() => {
    console.log(slug)
    if (!performedFetch && !!slug) {
      fetch(`https://api.trello.com/1/lists/${listId}/cards?key=${key}&token=${token}`)
          .then(response => response.json())
          .then(data => {
            setPerformedFetch(true);

            data.forEach((c, index) => {
              if (c.desc.includes(slug)) {                    
                setCard(c)
                setCardLocation(index + 1)
              } else if (!publishDate && index > cardLocation) { 
                console.log(publishDate); // why is this always null?? also runs multiple times

                const name = c.name;
                const frontHalf = name.split("/")[0].split(" ");
                const month = frontHalf[frontHalf.length - 1];
                const day = name.split("/")[1].split(")")[0];
                setPublishDate(`${month}/${day}`);
              }
            });
        });
    }
  }, [slug, performedFetch])
}

3 Comments

Sorry but this isn't correct. My code runs fine and the slug is pulled from the URL bar so the slug has nothing to do with anything async. I get into the else block and it continues to run through the cards I fetched. I'd expect it to stop after the first time I call setPublishedDate. Logging immediately after I set it still returns null.
@ZackShapiro , It's because React will batch all of your setPublishDate calls and will update state after you're checking the publish date
So how can I fix? Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.