Chrisissorry
Chrisissorry

Reputation: 1514

Child component not rerendered on prop change

In the following code, I expect OfferList to rerender when I add an offer item to the store. OfferList itself is not an observable, but the offer array is passed as a prop.

export const MerchantScreen: FC = observer(() => {
  const { merchantStore } = useStores()

  return (
    <View>
      <OfferList data={merchantStore.offers} />
      <View>
        <Button title={"New Offer"} onPress={() => merchantStore.addOffer()}/>
      </View>
    </View>
  )
})

export const OfferList: FC<OfferListProps> = ({ data }: OfferListProps) => {
  const renderItem = (offer: ListRenderItemInfo<any>) => {
    return (
      <Text>{offer.name}</Text>
    )
  }

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
    />
  )
}

I use Mobx State Tree. All merchantStore.addOffer() does for now is push another offer item into the array.

What I tried / findings:

When I read from the store in my MerchantScreen, e.g. by adding

<Text>{ merchantStore.offers.toString() }</Text>

, the OfferList will also update. I suspect that reading from the store directly in the parent component will force a rerender of the child component as well.

I stumbled upon some answers here that would indicate that a missing key attribute within the FlatList renderItems could be the issue. Tried using key={item.id} to no avail. Also, as you can see I use the keyExtractor prop of FlatList.

Another answers suggested introducing local state to the component like this:

export const OfferList: FC<OfferListProps> = ({ data }: OfferListProps) => {
  const [offers, setOfferss] = useState()

  useEffect(() => {
    setOffers(data)
  }, [data])
  
  const renderItem = (offer: ListRenderItemInfo<any>) => {
    return (
      <Text>{offer.name}</Text>
    )
  }

  return (
    <FlatList
      data={offers}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
    />
  )
}

This does not work and my gutfeeling is that this is not how it's done.

As you see my MerchantScreen parent component is an observable while my child component OfferList is not. From my expectation, the observer on my parent component should be enough. The parent component should already detect the change in the store and rerender. The child component in itself does not even use stores.

Overall, the problem at hand seems quite trivial so I guess I am just missing out on an important detail.

Upvotes: 2

Views: 1034

Answers (1)

Danila
Danila

Reputation: 18516

MobX only tracks data accessed for observer components if they are directly accessed by render, so if you want to react to each of the offers you need to access them somewhere. You sort of did when you tried merchantStore.offers.toString(), that's why it worked.

So first of all you need to make OfferList an observer.

But then you have FlatList which is native component and you can't make it an observer. What you can do is to access each offers item inside OfferList (just to subscribe for updates basically) like that data={offers.slice()} or even better with MobX helper method toJS data={toJS(offers)}

Depending on your use case you might also want to use <Observer> inside renderItem callback:

  const renderItem = (offer: ListRenderItemInfo<any>) => {
    return (
      <Observer>{() => <Text>{offer.name}</Text>}</Observer>
    )
  }

Upvotes: 4

Related Questions