I am using mobX for my react native project. Please consider this store class:
class Birds {
@observable listOne = [];
@observable fetchingListOne = false;
@observable fetchErrorOne = '';
@action setListOne = () => {
this.fetchingListOne = true;
api.getList()
.then((data) => {
this.listOne.replace(data);
this.fetchingListOne = false;
})
.catch((error) => {
this.fetchingListOne = false;
this.fetchErrorOne = error;
});
};
}
And this the react component:
@inject('BirdStore') @observer
export default class Flat extends React.Component {
componentDidMount() {
this.props.BirdStore.setListOne();
}
_renderHeader = () => {
return <Text style={styles.listHeaderText}>
Hello {this.props.BirdStore.listOne.length} is {this.props.BirdStore.fetchingListOne.toString()}
</Text>;
};
_renderItem = ({item}) => {
return <Text style={styles.item}>{item.name}</Text>
};
_renderFooter = () => {
if (this.props.BirdStore.fetchingListOne) {
return <ActivityIndicator/>
}
else {
return null
}
};
render() {
const dataSource = this.props.BirdStore.listOne.slice();
return (
<View style={styles.container}>
<FlatList
style={styles.listContainer}
ListHeaderComponent={this._renderHeader}
data={dataSource}
renderItem={this._renderItem}
keyExtractor={(item, i) => item.id}
ListFooterComponent={this._renderFooter}
/>
</View>
)
}
}
From above it looks to me that:
When the Flat component mounts, it calls the method of the store setListOne().
setListOne() sets fetchingListOne to true and makes an API call.
On the component side, when the fetchingListOne is true, the ActivityIndicator displays, and in the ListHeaderComponent it should display true.
On the store side, after a successful/unsuccessful response, it sets fetchingListOne to false.
Finally on the component side, because fetchingListOne is set to false, ActivityIndicator should not display and in the ListHeaderComponent it should display false.
However, this is not what's happening. Here when the setListOne() method is called, after it sets the fetchingListOne to true, the component does not react to the changes made after API call. And the ActivityIndicator keeps displaying and in ListHeaderComponent it's displaying true.
So to test out, I have added a Text component before the FlatList. And adding a Text component or console logging inside the component class's render method does make the FlatList react to the changes.
....
....
<View style={styles.container}>
<Text>Fetching: {this.props.BirdStore.fetchingListOne.toString()}</Text>
<FlatList
style={styles.listContainer}
ListHeaderComponent={this._renderHeader}
data={dataSource}
renderItem={this._renderItem}
keyExtractor={(item, i) => item.id}
ListFooterComponent={this._renderFooter}
/>
</View>
....
....
I am using:
Note that the render callbacks passed to FlatList are not called from @observer, but from the FlatList, which isn't an observer. Thefore the observables accessed inside these callbacks are not tracked. To solve this try to wrap them in <Observer> like this:
_renderHeader = () => {
return <Observer>{() => <Text style={styles.listHeaderText}>
Hello {this.props.BirdStore.listOne.length} is {this.props.BirdStore.fetchingListOne.toString()}
</Text>}</Observer>;
};
closing as answer has been provided
@urugator very helpful, solve my problem, Thanks!
let me provide bit more code for future reference
I am using FlatList and React Native 0.51 here
(omit some code)
import { observer } from 'mobx-react';
import { Observer } from 'mobx-react/native';
(omit some code)
_renderItem = ({item}) => {
return <Observer>{() => (
<View style={{ padding: 5, flexDirection: 'row', paddingTop: 10 }}>
<View style={{ marginLeft: 30, marginRight: 22 }}>
<Image
style={{ width: 80, height: 80 }}
source={{ uri: item.base64 }}
/>
{this.imageOverLimit(item.response.fileSize, item.response.width, item.response.height) &&
<View style={styles.overlay}>
<Text style={{ backgroundColor: 'red' }}>Exceed Limit!</Text>
</View>}
</View>
<View style={{ flexDirection: 'column', justifyContent: 'space-between' }}>
<Text>Progress%:{item.progress}</Text>
<View style={{ flexDirection: 'row' }}>
<Text>{this.properFileSize(item.response.fileSize)} | </Text>
<Text>{item.response.width} x</Text>
<Text> {item.response.height} px</Text>
</View>
</View>
</View>
)}</Observer>;
};
(omit some code)
@mweststrate @1c7 @urugator
I still cannot get this to work. See code below:
import { inject, observer, Observer } from 'mobx-react/native'
@inject('store')
@observer
export default class Home extends React.Component<IExploreScreenProps> {
public componentDidMount() {
const { store: { offers } } = this.props
if (offers.explore.length === 0) {
console.log('Trigger fetchExploreOffers()')
offers.fetchExploreOffers()
}
}
public render() {
// const { store: { offers } } = this.props
return (
<G.View paddingHorizontal={10} backgroundColor="white">
<G.View justifyContent="center" height="100%" alignContent="center">
<FlatList
data={this.props.store.offers.explore}
initialNumToRender={8}
renderItem={this.renderExploreItem}
getItemLayout={this.getCardHeight}
keyExtractor={this.extractItemKey}
style={{ height: '100%' }}
/>
</G.View>
</G.View>
)
}
private renderExploreItem = ({ item: offer }: { item: OfferModel }) => {
console.log(offer)
return (
<Observer inject={['store']}>
{() => <ExploreCard uri={DUMMY_URI} offer={offer} />}
</Observer>
)
}
private extractItemKey = (item: OfferModel) => item._id
private getCardHeight = (data: any, index: number) => ({
length: STANDARD_CARD_HEIGHT,
offset: STANDARD_CARD_HEIGHT * index,
index
})
}
Store is updating, but nothing is re-rendered
@duro Firstly don't use inject on Observer it's broken.
{() => <ExploreCard uri={DUMMY_URI} offer={offer} />} - there is nothing to react to, you're not accessing any observables there, therefore <Observer> is kinda useless...
However your problem is probably here:
data={this.props.store.offers.explore}
try to change it to:
data={this.props.store.offers.explore.slice()}
EDIT: for detailed explanation consult https://mobx.js.org/best/react.html
@urugator That did it! So the reason this fixed this is because I tapped into the Observable Array on the first render? I'm still a mob-x newb, so any brief explanation on this would help me better grok the underlying concepts.
@duro Yes, you didn't use anything from the array inside the observer, therefore array modifications shouldn't affect the rendering result from the perspective of observer. The array is accessed later in FlatList, but FlatList isn't observer, therefore no subscription for array occurs. By copying array in observer, you "artifically" subscribe for any array modification. Sometimes you may need to create deep copy Mobx.toJS(thing), causing subscription not only to thing itself, but also to anything inside.
@urugator Did I also read correctly in the doc you sent that restructuring is also not a good idea?
Like if I did:
const { store: { offers }} = this.props
const exploreOffers = offers.explore.slice()
Would this also bork the subscription?
Why don't you try?
Destructuring dereferences the object properties, therefore invokes property getters (which hides the subscription logic), therefore causes subscription...
const { store: { offers }} = this.props
is a sugar for
const offers = this.props.store.offers; // subscribes for a change like store.offers = [];
Array can be destructured too:
const exploreOffers = [...offers.explore] // invokes array iterator causing the subscription for a changes like explore[3] = 0; explore.push(0); etc
@urugator just a quick comment, thanks for the splice() hint, saved my day ;-)
Same issue here.. Seems like FlatList is not updating its items even when I pass separate component instead of the function to the renderItem
@SourceCipher please refrain from commenting on old issues if you want actual help; probably nobody will notice your comments.
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs or questions.
Most helpful comment
Note that the render callbacks passed to FlatList are not called from
@observer, but from the FlatList, which isn't an observer. Thefore the observables accessed inside these callbacks are not tracked. To solve this try to wrap them in<Observer>like this: