Reputation: 1514
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
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