Hogan
Hogan

Reputation: 165

useEffect not running when state changes

I have a screen (MyPics.js) that shows a collection of images that the user selects from another screen. I get the selected images from asyncstorage.

All works fine if I select images and then reboot the simulator. When I navigate to the MyPics.js screen, I see all of the images stored in AsyncStorage from previous image selections.

  const MyPicsScreen = props => {
    const [picList, setpicList] = useState([]);

    useEffect(() => {
      console.log("I'm in!! picList data is: " +  picList);
      AsyncStorage.getItem("mypics").then((response) => {
      const currentData = JSON.parse(response);
      setpicList(currentData);
      console.log("Current picList data will be: " + currentData);
    });
   }, []);


  return (
<View style={styles.wrapper}>
  <ScrollView>
    <View style={{flexDirection: "column", justifyContent: "center", alignItems: "center"}}>

      {picList ? picList.map((item, index) => {
        return (
            <View key={index} style={styles.viewItem}>
              <Image style={{width: 200, height: 131}} source={Images[PicList[item][3]]} />
            </View>
          );
        }
      ) : <View><Text>You haven't selected any pics yet</Text></View>}

    </View>
  </ScrollView>
</View>
   );
 };

Terminal shows:

  I'm in!! picList data is:  //No data, as expected. Nothing is retrieved yet, then...
  Current picList data: pic001,pic007,pic099 // That shows, correctly, what's stored and screen shows those images 

But if I add images, the new images don't show up unless I reboot the simulator. Plus, no read out in the console, so useEffect is not running to get the data and reset the state.

I'm assuming that going back to the MyPics screen causes a re-render, but I may be wrong.

I made 3 buttons on the MyPics.js screen that manually runs either removeItem or setItem or getItem on asyncstorage which then removes or sets or gets images then updates the picList state.

That works.

When I use the buttons it gets or removes previously set images or sets a new image and the screen re-renders to show the state change.

So, useEffect is not running when I navigate to the MyPics.js screen. It only runs on the first render.

When I add in the picList state as a dependency,

  }, [picList]);

it works but the console is running in an infinite loop.

Is that normal? Should I just ignore the infinitely scrolling console or am I missing a step? Should I be using a focus listener to re-render the page on a re-visit as I only need the new data when I come back to the MyPics screen.

Upvotes: 4

Views: 11744

Answers (3)

Agent
Agent

Reputation: 1395

Thank you @Kelvin Aitken and @Danny Buonocore for support

I wondered why useEffect hooks only work during initial loading and failed whenever I tried to paginate more items.

I later found that I should pass any dependency/state in [] to tell hooks to render anytime state changes.


const [fetchItemInput, SetFetchItemInput] = useState(input);

const fetchMoreItemBtn = (nextCursor) => {
    const newInput = { ...fetchItemInput };
    newInput.after = nextCursor;
    return SetFetchItemInput(newInput);
  };

  useEffect(() => {

    handleFetchItem(fetchItemInput)
      .then((res) => {
            ...
      })
      .catch((error) => {
            ...
      });
    return () => {};
  }, [fetchItemInput]);       // Any changes run the effect

Thank you again for your support.

Upvotes: 1

Kelvin Aitken
Kelvin Aitken

Reputation: 443

After a lot of pain and wasted time, this works:

 useEffect(() => {
  AsyncStorage.getItem("mypics").then((response) => {
  setpicList(JSON.parse(response);
});
}, []);

const getList = useCallback(() => {
  AsyncStorage.getItem("mypics").then((value) =>{
  setpicList(JSON.parse(value));
});
}, [picList]);

Some very practical examples for functional components can be found in this thread:

React navigation didfocus event listener works differently between class component and functional component

Focus detection is needed when coming back to the screen so that the useEffect function runs again.

Upvotes: 2

Danny Buonocore
Danny Buonocore

Reputation: 3777

From the docs

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.

So passing an empty array to your useEffect is roughly equivalent to (except useEffect fires after the initial paint) the componentDidMount and componentWillUnmount functions of class components.

Upvotes: 2

Related Questions