0
votes

I have a favorite button on the 'tweet' card that I show on the FeedScreen.js.

~~~~~~~~~ IMPORTS SNIP ~~~~~~~~~

function FeedScreen(props) {
  const [feed, setFeed] = useState([]);
  const [favorites, setFavorite] = useState([]);
  const [refreshing, setRefreshing] = useState(false);
  useEffect(() => {
    loadFeed(0, 4);
  }, []);

  const loadFeed = async (last_id = 0, limit = 1) => {
    setRefreshing(true);
    const response = await tweetsApi.getTweets(last_id, limit);
    if (response.ok) {
      setFeed(response.data["data"].concat(feed));
    } else {
      console.log(response.problem);
    }
    setRefreshing(false);
  };

  const handleBookmark = async (item_id) => {
    const response = await tweetsApi.toggleBookmark(item_id);
    if (response.ok) {
      console.log("ok response");
      setFavorite(favorites.concat(item_id));
// I've tried this as well
// setFavorite([...favorites].concat(item_id));
// but in vain
      console.log(favorites);
    }
  };

  return (
    <Screen style={styles.screen}>
      <FlatList
        data={feed}
        keyExtractor={(tweet) => {
          return tweet.id.toString();
        }}
        renderItem={({ item }) => (
~~~~~~~~~ SNIP ~~~~~~~~~
            <CardFooter
              style={{ marginLeft: 20 }}
              item={item}
              onPress={handleBookmark}
            />
          </View>
        )}
        ItemSeparatorComponent={ListItemSeparator}
        refreshing={refreshing}
        onRefresh={() => {
          loadFeed(feed[0]["id"], 2);
        }}
      />
    </Screen>
  );
}

~~~~~~~~~ SNIP ~~~~~~~~~

And here's the CardFooter.js :

~~~~~~~~~ SNIP ~~~~~~~~~

function CardFooter({ item, onPress }) {
  return (
      <View style={styles.bookmark}>
        <TouchableOpacity
          onPress={() => {
            return onPress(item.id);
          }}
        >
          {item.bookmarked && (
            <FontAwesome name="bookmark" size={24} color="red" />
          )}
          {!item.bookmarked && (
            <FontAwesome5 name="bookmark" size={24} color="black" />
          )}
        </TouchableOpacity>
      </View>
    </View>
  );
}
export default CardFooter;
~~~~~~~~~ SNIP ~~~~~~~~~

However the component doesn't seem to re render. I've looked at these :

  1. react-component-not-re-rendering-after-using-usestate-hook
  2. Similar here
  3. Another one 17 days back - why-usestate-is-not-re-rendering
  4. usestate-not-re-rendering-when-updating-nested-object

All of these and similar other ones, each one of them point to the fact that the a new array should be created so that react re-renders it.

Update

console.log output

yes the console.log is printing the array, although one value previous. That's because useState is async so it isn't printing the realtime array. So, when the second time this is called, it would show one item_id ( the previous one ) added to favorites

1
Did you console.log() your response to see if data was indeed returned?Mosia Thabo
what is the console.log(favorites) showing?Ross
@Ross updated the question,with console.log outputtemporarya

1 Answers

0
votes

I finally solved this by managing the state in the component itself.

Not sure if this is 'the proper way' to do this, but read here (how-to-add-a-simple-toggle-function-in-react-native) that this is how you can do this.

So, now the bookmark component gets its response from the top level component ( FeedScreen.js ) :

const handleBookmark = async (item_id) => {
    const response = await tweetsApi.toggleBookmark(item_id);
    if (response.ok) {
      return true;
    } else {
      return false;
    }
  };

And changing the CardFooter.js i.e. where the bookmark component resides.

function CardFooter({ item, onPress }) {
  const [favorite, setFavorite] = useState(item.bookmarked);
  return (
      <View style={styles.bookmark}>
        <TouchableOpacity
          onPress={async () => {
            let response = await onPress(item.id);
            if (response) {
              setFavorite(!favorite);
            } else {
              alert("Some error occurred");
            }
          }}
        >
          {favorite && <FontAwesome name="bookmark" size={24} color="red" />}
          {!favorite && (
            <FontAwesome5 name="bookmark" size={24} color="black" />
          )}
        </TouchableOpacity>
      </View>
    </View>
  );
}

Concerns

I am a bit concerned about handling the response in this component.

Should I handle the async operation in the bottom component ?