'Component not updating/rerendering when redux toolkit state changes

I'm using redux toolkit to manage state and am finding that the components are not updating when changes are made to the state.

I do see though, that when I listen to changes in my redux state within a useEffect that it will trigger the callback, meaning it is aware that the redux state has changed but the component is not rerendering.

Reading other questions it seems like a lot of the issues were about mutating the state, but I do know that using redux tool kit allows you to write "mutable" code and this is handled by Immer. Not sure if this is an issue that applies here...

My Slice:

const initialState = {
  watchlists: []
}

export const watchlistsSlice = createSlice({
  name: 'watchlists',
  initialState,
  reducers: {
    updateWatchlists: (state, { payload }) => {
      state.watchlists = payload;
    },
  },
})

export const { updateWatchlists } = watchlistsSlice.actions;

export default watchlistsSlice.reducer;

This is the component that calls the function that changes the state:

import React from 'react';
import { StyleSheet, View, Image } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';

import Text from '../core/Text';
import Pressable from '../core/Pressable';
import { AddIcon } from '../core/Icons';

import { formatPrice, formatPercent } from '../../utils/formatNumber';
import { shortenLongText } from '../../utils/formatText';

import { updateWatchlists } from '../../redux/watchlistsSlice';

import { addToWatchlist } from '../../storage/watchlists';

export default function ListItem({data, theme, navigation, watchlistID }) {

  const dispatch = useDispatch();
  const { currency } = useSelector(state => state.userPreference)

  const addCryptoToWatchlist = async () => {
    if (watchlistID) {
      addToWatchlist(watchlistID, {
        slug: data.slug,
      })
      .then(result => dispatch(updateWatchlists(result)))
      .catch(err => console.log(err))
    } else {
      console.log('not ready yet')
    }
  }

  return (
    <Pressable 
      onPress={() => navigation.navigate('CoinDetails', { 
        data,
      })}
    >
      <View style={styles.searchResult}>
        <View style={styles.nameContainer}>
          <Image style={styles.image} source={{uri: data.logo}} />
          <View style={{marginLeft: 15}}>
            <Text type={"big"} size={18} theme={theme.text}>{data.symbol}</Text>
            <Text type={"regular"} size={14} theme={theme.text} style={{paddingTop: 1}}>{shortenLongText(data.name,25)}</Text>
          </View>
        </View>
        <View style={styles.rightContainer}>
          <View style={styles.priceContainer}>
            <Text type={"big"} theme={theme.text}>{formatPrice(data.price, currency)}</Text>
            <Text type={"big"} theme={data.direction === 'up' ? theme.percent.up : theme.percent.down}>{formatPercent(data.percent_change_24h)}</Text>
          </View>
          <Pressable onPress={() => addCryptoToWatchlist()}>
            <AddIcon 
              size={30} 
            />
          </Pressable>
        </View>
      </View>
    </Pressable>
  )
}

const styles = StyleSheet.create({
  searchResult: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 30,
  },
  nameContainer : {
    flexDirection: 'row',
    alignItems: 'center',
  },
  rightContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  priceContainer: {
    alignItems: 'flex-end',
    marginRight: 20
  },
  image: {
    width: 28, 
    height: 28, 
  }
})

This is one the components that I expect to rerender when the state changes:

The useEffect does get trigged so the component is recognizing a change in state, but the component does not rerender.

I dont know if this is insightful, but the data in the state is an array of objects and in this case a single property one of the objects is getting changed.

import React, { useState, useEffect } from 'react';
import { View, StyleSheet } from 'react-native';
import { useSelector } from 'react-redux';

import GetStarted from './GetStarted';
import AddCryptoCard from './AddCryptoCard';
import CardList from './CardList';
import Text from '../core/Text';

export default function WatchlistCardsSection({ setModalVisible, navigation }) {

  const { theme } = useSelector(state => state.userPreference);
  const { watchlists } = useSelector(state => state.watchlists)
  const { cryptoData } = useSelector(state => state.cryptoData);

  const [ watchlistCryptoDataLoaded, setWatchlistCryptoDataLoaded ] = useState(false);

  const checkIfWatchlistDataLoaded = () => {
    const watchlistNames = watchlists.map(watchlist => watchlist.name);
    const checkIfLoaded = cryptoData.map(data => watchlistNames.some(name => data.tags.includes(name))).includes(true);
    setWatchlistCryptoDataLoaded(checkIfLoaded);
  }

  useEffect(() => {
    checkIfWatchlistDataLoaded();
  },[cryptoData])

  useEffect(() => console.log("watchlist updated"), [watchlists])

  return (
    watchlists && 
      watchlists.length === 0 ? 
        <GetStarted 
          setModalVisible={setModalVisible}
        /> 
      : 
        watchlists.filter(item => item.viewOnHome).map(watchlist => (
          watchlist.data.length === 0 ?
            <AddCryptoCard
              key={watchlist.id}
              id={watchlist.id}
              name={watchlist.name} 
              navigation={navigation}
            />
          :
            watchlistCryptoDataLoaded ?
              <View 
                key={watchlist.id}
                style={styles.sectionContainer}
              >
                <Text style={{paddingLeft: 20}} type={'big'} size={24} theme={theme.text}>{watchlist.name}</Text>
                <CardList
                  name={watchlist.name}
                  config={{type: 'price'}}
                  navigation={navigation}
                /> 
              </View> 
            : null
        ))     
  )
}

const styles = StyleSheet.create({
  sectionContainer: {
    flex: 1,
    marginTop: 25,
  }
})


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source