Mobx not updating react native element - reactjs

I'm new to react and mobx.
I'm trying to use mobx to update a simple counter and display the count number.
When I click on the button "Add" I can see in the logs that counterStore.count is increasing but counter shown in the <Text></Text> remains equal to 0.
Can you please me tell me what is wrong?
index.tsx
import { observer } from "mobx-react";
import React from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import CounterStore from './stores/CounterStore';
export function App() {
const counterStore = new CounterStore(0);
return (
<View style={styles.container}>
<View style={styles.wrapper}>
<Text>{counterStore.count}</Text>
<Button
title="Add"
onPress={() => {
counterStore.addToCount();
console.log("count = ", counterStore.count);
}}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
height: "100%"
},
wrapper: {
backgroundColor: "#F5FCFF",
width: "100%",
maxWidth: 425
}
});
export default observer(App);
CounterStore.ts
import { action, makeObservable, observable } from "mobx";
class CounterStore {
#observable count: number;
constructor(count: number){
this.count = count
makeObservable(this);
}
#action
addToCount(){
this.count++;
}
}
export default CounterStore;
Output & Logs
output

I tried to improve my code based on Jay Vaghasiya's answer, but still got the same behaviour.
CounterStore.ts
import { action, makeObservable, observable } from "mobx";
import React from "react";
class CounterStore {
count = 0;
constructor(){
makeObservable(this), {count: observable};
}
#action
addToCount(){
this.count++;
}
}
export const CounterStoreContext = React.createContext(new CounterStore());
index.tsx
import { observer } from "mobx-react";
import React from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import { CounterStoreContext } from './stores/CounterStore';
const App = observer(() => {
const counterStore = React.useContext(CounterStoreContext);
return (
<View style={styles.container}>
<Text>{counterStore.count}</Text>
<Button
title="Add"
onPress={() => {
counterStore.addToCount();
console.log("count = ", counterStore.count);
}}
/>
</View>
);
});
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
height: "100%",
},
wrapper: {
backgroundColor: "#F5FCFF",
width: "100%",
maxWidth: 425
}
});
export default App;

Related

React Native & MobX: useContext doesn't re-render the change into the screen

I have been trying to use mobX to apply on React Native Functional Component.
So I use these 2 libraries - mobx & mobx-react-lite.
I made a simple counter app and I also useContext hook along with this.
After increasing the value, it doesn't apply on the screen. However, it appeared on my console.
The change got displayed on after I had refreshed my code by saving it (I didn't change the code)
How do I solve this issue?
App.js
import { StyleSheet, Text, View,Button } from 'react-native';
import { CounterStoreContext } from './components/CounterStore';
import { observer } from "mobx-react-lite";
import React, { useContext, useState } from "react";
const App = observer(() => {
// const [count, setCount] = useState(0);
const counterStore = useContext(CounterStoreContext)
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome</Text>
<Text style={styles.text}>Just Press the damn button</Text>
{/* <Text style={styles.text}>{count}</Text> */}
{/* <Button title="Increase" onPress={()=>{setCount(count+1)}}/> */}
<Text style={styles.text}>{counterStore.count}</Text>
<Button title="Increase" onPress={()=>{
counterStore.count++;
console.log(counterStore.count)
// setCount(counterStore.count)
}}/>
</View>
);
})
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
welcome:{
fontSize: 20,
fontWeight: 'bold',
textAlign: 'center',
},
text:{
fontSize: 14,
textAlign: 'center',
},
});
export default App;
CounterStore.js
import { observable, observe } from "mobx";
import { createContext } from "react";
class CounterStore {
#observable count = 0;
}
export const CounterStoreContext = createContext(new CounterStore())
Since MobX 6 the #observable decorator is not enough. You need to use makeObservable / makeAutoObservable in the constructor as well.
class CounterStore {
#observable count = 0;
constructor() {
makeAutoObservable(this);
}
}

React Native Expo: Why isn't/can't my image can't be found and isn't loading?

I'm building a React Native App with expo and I have an image I want to display on my login screen that should cover the whole image but for some reason it's not loading and I have a blank screen. My terminal says "Could not find image file:///Users/BlahBlah/Library/Developer/CoreSimulator/Devices/3FE078A1-2C0A-4308-B256-BFBF1B246A85/data/Containers/Bundle/Application/08978CAA-74FC-476D-939C-9CBDF1E4B9D9/Exponent-2.13.0.app/./assets/Images/TemplatePic.jpg
- node_modules/react-native/Libraries/BatchedBridge/NativeModules.js:104:55 in
- node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:414:4 in __invokeCallback
- ... 4 more stack frames from framework internals"
LoginScreen
import React from 'react';
import { View, Image, Dimensions, SafeAreaView, StyleSheet, Text } from 'react-native';
import { AppLoading } from 'expo';
import { Asset } from 'expo-asset';
import { Tab, Tabs, Header } from 'native-base';
import { commonStyles } from './styles/styles';
import SignInScreen from './SignInScreen';
import SignUp from './SignUp';
const { width, height } = Dimensions.get('window');
class LoginScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: false
};
}
render() {
return (
<View style={styles.background}>
<View style={StyleSheet.absoluteFill}>
<Image
source={('../assets/Image/TemplatePic.jpg')}
style={{ flex: 1, height: null, width: null }}
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
background: {
flex: 1,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
}
});
export default LoginScreen;
App.js
import React from 'react';
import { View, Image, Dimensions, SafeAreaView, StyleSheet, Text } from 'react-native';
import { AppLoading } from 'expo';
import { Asset } from 'expo-asset';
import * as firebase from 'firebase';
import { firebaseConfig } from './config.js';
import { Provider, connect } from 'react-redux';
import RootStack from './RootStack';
import LoginScreen from './App/screens/LoginScreen';
import configureStore from './App/reducers/configureStore';
firebase.initializeApp(firebaseConfig);
// create store from redux
const store = configureStore();
function cacheImages(images) {
return images.map(image => {
if (typeof image === 'string') {
return Image.prefetch(image);
}
return Asset.fromModule(image).downloadAsync();
});
}
const { width, height } = Dimensions.get('window');
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: false
};
}
async _loadAssetsAsync() {
const imageAssets = cacheImages([
('./assets/Images/TemplatePic.jpg'),
]);
await Promise.all([...imageAssets]);
}
render() {
//If the state is not ready then display the apploading oterwise display the app
if (!this.state.isReady) {
return (
<AppLoading
startAsync={this._loadAssetsAsync}
onFinish={() => this.setState({ isReady: true })}
onError={console.warn}
/>
);
}
return (
<View style={styles.background}>
<LoginScreen />
</View>
);
}
}
const styles = StyleSheet.create({
background: {
flex: 1,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',
fontSize: 16
},
textStyle: {
}
});
Try this:
<Image
source={require('../assets/Image/TemplatePic.jpg')}
style={{ flex: 1 }}
resizeMode="cover"
/>

Redux thunk with rendering using App class

Redux thunk with rendering using App class - I am using react-native and redux thunk to call the dispatcher via componentDidMount of App class and receiving errors "props is not defined" and "Unable to find module for EventDispatcher".
Would request for further help on this from experts.
index.js
import React from 'react';
import {AppRegistry} from 'react-native';
import {Provider} from 'react-redux';
import configureStore from './configureStore';
import App from './App';
import {name as appName} from './app.json';
const store = configureStore();
const rnredux = () => (
<Provider store={store}>
<App />
</Provider>
)
AppRegistry.registerComponent(appName, () => rnredux);
app.js
import React from 'react';
import {Platform, TouchableHighlight, StyleSheet, Text, View} from 'react-native';
import {connect} from 'react-redux'
import {fetchPeopleFromAPI} from './actions'
class App extends React.Component {
componentDidMount() {
this.props.getPeople();
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native! & Redux</Text>
<Text style={styles.instructions}>To get started, edit App.js</Text>
<TouchableHighlight onPress={props.getPeople} style={styles.buttonText}>
<Text>Fetch Data</Text>
</TouchableHighlight>
{
isFetching && <Text>Loading</Text>
}
{
people.length? (
people.map((person,index) => {
return (
<View key={index}>
<Text>Name: {person.breedName}</Text>
</View>
)
})
) : null
}
</View>
);
}
}
const {people, isFetching} = props.people
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
buttonText: {
backgroundColor: 'red',
height:60,
justifyContent: 'center',
alignItems: 'center',
}
});
function mapStateToProps (state) {
return {
people: state.people
}
}
function mapDispatchToProps (dispatch) {
return {
getPeople: () => dispatch(fetchPeopleFromAPI())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
Remove this part from your code completely:
function mapDispatchToProps (dispatch) {
return {
getPeople: () => dispatch(fetchPeopleFromAPI())
}
}
in your export statement amend into:
export default connect(
mapStateToProps,
{ fetchPeopleFromAPI }
)(App)
and finally in your componentDidMount:
componentDidMount() {
this.props.fetchPeopleFromAPI();
}

cant find variable props

I am trying to show the detail view when user taps on one of the list item. When the user taps on the listitem, I get an error saying Undefined is not an object
evaluating 'this.props.services.ser. Below is the screen shot of the error:
My list item page code is below:
import React, { Component } from 'react';
import { Text, View, StyleSheet, ListView } from 'react-native';
import { Provider, connect } from 'react-redux';
import { createStore } from 'redux'
import reducers from '../reducers/ServiceReducer';
import ServiceItem from './ServiceItem';
import Icon from 'react-native-vector-icons/EvilIcons';
import ServiceDetail from './ServiceDetail';
const styles = StyleSheet.create({
container: {
flex: 1,
width: 353,
flexWrap: 'wrap',
paddingTop: 20,
paddingLeft: 20,
},
});
const store = createStore(reducers);
class AutoCompActivity extends Component {
renderInitialView() {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
});
this.dataSource = ds.cloneWithRows(this.props.services);
if (this.props.detailView === true) {
return (
<ServiceDetail />
);
} else {
return (
<ListView
enableEmptySections={true}
dataSource={this.dataSource}
renderRow={(rowData) =>
<ServiceItem services={rowData} />
}
/>
);
}
}
render() {
return (
<View style={styles.container}>
{this.renderInitialView()}
</View>
);
}
}
const mapStateToProps = state => {
return {
services: state.services,
detailView: state.detailView,
};
};
const ConnectedAutoCompActivity = connect(mapStateToProps)(AutoCompActivity);
const app1 = () => (
<Provider store={store}>
<ConnectedAutoCompActivity />
</Provider>
)
export default app1;
Each item that I display to the user is defined in js class serviceItem.js. Below is the code for serviceItem.js. I put the entire code inside TouchableWithoutFeedback. This is when I am getting an error when user taps on one of the listItem. I looked at this code several times, but could not figure out where I am doing wrong
import React from 'react';
import { Text, View, StyleSheet, Image, TouchableWithoutFeedback } from 'react-native';
import { connect } from 'react-redux';
import { getTheme } from 'react-native-material-kit';
import Icon from 'react-native-vector-icons/EvilIcons';
import * as actions from '../actions';
const theme = getTheme();
const styles = StyleSheet.create({
card: {
marginTop: 20,
},
title: {
top: 20,
left: 80,
fontSize: 24,
},
image: {
height: 100,
},
action: {
backgroundColor: 'black',
color: 'white',
},
icon: {
position: 'absolute',
top: 15,
left: 0,
color: 'white',
backgroundColor: 'rgba(255,255,255,0)',
},
});
const ServiceItem = (props) => {
return (
<TouchableWithoutFeedback
onPress={() => props.selectServices(props.services)}
>
<View style={[theme.cardStyle, styles.card]}>
<Text >{props.services.ser} </Text>
</View>
</TouchableWithoutFeedback>
);
};
const mapStateToProps = state => {
return {
selectServices: state.selectServices,
services: state.services
};
};
export default connect(mapStateToProps, actions)(ServiceItem);
When the user taps on each listitem. I am calling the class ServiceDetail.js. Below is the code for serviceDetail.js class:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import React, { Component } from 'react';
import { Text, View, StyleSheet, Image, ScrollView, TouchableOpacity, Linking } from 'react-native';
import { connect } from 'react-redux';
import { getTheme } from 'react-native-material-kit';
import EvilIcon from 'react-native-vector-icons/EvilIcons';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import SimpleIcon from 'react-native-vector-icons/SimpleLineIcons';
import * as actions from '../actions';
const theme = getTheme();
const styles = StyleSheet.create({
card: {
marginTop: 10,
paddingBottom: 20,
marginBottom: 20,
borderColor: 'lightgrey',
borderWidth: 0.5,
},
title1: {
top: 10,
left: 80,
fontSize: 24,
},
title2: {
top: 35,
left: 82,
fontSize: 18,
},
image: {
flex: 0,
height: 100,
width: 333,
backgroundColor: 'transparent',
justifyContent: 'center',
alignItems: 'center',
},
closeIcon: {
position: 'absolute',
top: 5,
left: 295,
color: 'rgba(233,166,154,0.8)',
backgroundColor: 'rgba(255,255,255,0)',
},
icon: {
position: 'absolute',
top: 15,
left: 0,
color: 'white',
backgroundColor: 'rgba(255,255,255,0)',
},
textArea: {
flexDirection: 'row',
paddingLeft: 20,
paddingTop: 10,
width: 260,
},
textIcons: {
color: '#26a69a',
},
actionArea: {
paddingTop: 10,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
},
});
class ServiceDetail extends Component {
handleClick = (link) => {
Linking.canOpenURL(link).then(suppported => {
if (supported) {
Linking.openURL(link);
} else {
console.log('Don\'t know how to open URI: ' + link);
}
});
};
render() {
return (
<ScrollView showsVerticalScrollIndicator={false}>
<View style={[theme.cardStyle, styles.card]}>
<Image
source={require('../images/background.jpg')}
style={[theme.cardImageStyle, styles.image]}
/>
<EvilIcon name={'user'} size={100} style={styles.icon}/>
<SimpleIcon name={'close'} size={30} style={styles.closeIcon}
onPress={() => this.props.noneSelected()} />
<Text style={[theme.cardTitleStyle, styles.title1]}>{this.props.services.ser}</Text>
<Text style={[theme.cardTitleStyle, styles.title2]}>from {this.props.services.Location}</Text>
<Text style={[theme.cardTitleStyle, styles.title2]}>from {this.props.services.SecondLoc}</Text>
<View style={styles.textArea}>
<MaterialIcon name={'phone'} size={40} style={styles.textIcons}/>
<Text style={theme.cardContentStyle}>{this.props.services.Phone}</Text>
</View>
<View style={styles.textArea}>
<MaterialIcon name={'email'} size={40} style={styles.textIcons}/>
<Text style={theme.cardContentStyle}>{this.props.services.email}</Text>
</View>
<View style={styles.actionArea}>
<Text>Call</Text>
<Text>Email</Text>
</View>
</View>
</ScrollView>
);
}
}
const mapStateToProps = state => {
return {
service: state.serviceSelected,
};
};
export default connect(mapStateToProps, actions)(ServiceDetail);
My index.js class has the following code under actions folder:
export const selectServices = (serviceId) => {
return {
type: 'SELECTED_SERVICE',
payload: serviceId,
};
};
export const noneSelected = () => {
return {
type: 'NONE_SELECTED',
};
};
My serviceReducer has the following code:
import services from './services.json';
const initialState = {
services,
detailView: false,
serviceSelected: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case 'SELECTED_SERVICE':
return {
...state,
detailView: true,
serviceSelected: action.payload
}
case 'NONE_SELECTED':
return {
...state,
detailView: false,
serviceSelected: null,
}
default:
return state;
}
}
I am developing this application on windows machine on android emulator. I know I am missing something. I looked at each and every line, but could not figure out what am I doing wrong. I am new to react native and trying to follow the example to make this app. I tried to debug the application using chrome, but keep getting an error that could not connect to remote debugging.
any help will be highly appreciated.
I see you pass props and use it to your const ServiceItem. You need to use mapStateToProps also then pass to your connect at serviceItem.js.
const mapStateToProps = state => {
return {
selectServices: state.selectServices,
services: state.services
};
};
export default connect(mapStateToProps, actions)(ServiceItem);
Your ServiceDetail.js also should be:
const mapStateToProps = state => {
return {
services: state.services
};
};
export default connect(mapStateToProps, actions)(ServiceDetail);
Your ServiceItem component is just a presentational component, in my opinion, it need not to be connected to redux store, instead, pass the data and event hook from parent to ServiceItem component and that will work just fine. That will solve your props problem as well.
Point made by "DennisFrea" still holds good. i am just suggesting another of achieving the same thing.

React Native custom event

Just starting out with React Native, and was curious on how best to achieve passing events to a parent component from children buttons. So for example:
import React, { Component } from 'react';
import ViewContainer from '../components/ViewContainer';
import SectionHeader from '../components/SectionHeader';
import {
View
} from 'react-native';
class App extends Component {
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {() => console.log("SUCCESS")} ></SectionHeader>
</ViewContainer>
);
}
}
module.exports = ProjectListScreen;
and here is my section header. I was attempting to use an Event Emitter, but not working the way I'd hoped, unless I missed something.
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/FontAwesome';
import EventEmitter from "EventEmitter"
import {
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react-native';
class SectionHeader extends Component {
componentWillMount() {
console.log("mounted");
this.eventEmitter = new EventEmitter();
this.eventEmitter.addListener('onCreateNew', function(){
console.log('myEventName has been triggered');
});
}
_navigateAdd() {
this.eventEmitter.emit('onCreateNew', { someArg: 'argValue' });
}
_navigateBack() {
console.log("Back");
}
render() {
return (
<View style={[styles.header, this.props.style || {}]}>
<TouchableOpacity style={{position:"absolute", left:20}} onPress={() => this._navigateBack()}>
<Icon name="chevron-left" size={20} style={{color:"white"}} />
</TouchableOpacity>
<TouchableOpacity style={{position:"absolute", right:20}} onPress={() => this._navigateAdd()}>
<Icon name="plus" size={20} style={{color:"white"}} />
</TouchableOpacity>
<Text style={styles.headerText}>Section Header</Text>
</View>
);
}
}
const styles = StyleSheet.create({
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00B9E6',
flexDirection: 'column',
//paddingTop: 25
},
headerText: {
fontWeight: 'bold',
fontSize: 20,
color: 'white'
},
});
module.exports = SectionHeader;
Thanks for your help!
Not sure if this is the right way to go about this, but using this.props.onCustomEvent from the nested Button's onPress event seems to work fine. Please let me know if there is a better way to go about doing this.
App:
import React, { Component } from 'react';
import ViewContainer from '../components/ViewContainer';
import SectionHeader from '../components/SectionHeader';
import {
View
} from 'react-native';
class App extends Component {
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {() => console.log("SUCCESS")} ></SectionHeader>
</ViewContainer>
);
}
}
module.exports = ProjectListScreen;
Section Header
import React, { Component } from 'react';
import Icon from 'react-native-vector-icons/FontAwesome';
import {
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react-native';
class SectionHeader extends Component {
render() {
return (
<View style={[styles.header, this.props.style || {}]}>
<TouchableOpacity style={{position:"absolute", left:20}} onPress={() => this.props.onCreateNew()}>
<Icon name="chevron-left" size={20} style={{color:"white"}} />
</TouchableOpacity>
<Text style={styles.headerText}>Section Header</Text>
</View>
);
}
}
const styles = StyleSheet.create({
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00B9E6',
flexDirection: 'column',
//paddingTop: 25
},
headerText: {
fontWeight: 'bold',
fontSize: 20,
color: 'white'
},
});
module.exports = SectionHeader;
In addition to #arbitez's answer, if you want to maintain scope then you should make a method and bind to it, eg:
Parent:
constructor(props) {
super(props)
// ........
this.onCreateNew=this.onCreateNew.bind(this);
}
onCreateNew() {
console.log('this: ', this);
}
render() {
return (
<ViewContainer>
<SectionHeader onCreateNew = {this.onCreateNew} ></SectionHeader>
</ViewContainer>
);

Resources