I have a real doozy here and I can't wrap my head around it.
I have this component:
<TouchableOpacity
onPress={() => {
if (myContext.value) {
alert('true');
//changes myContext.value to false
} else {
alert('false');
//changes myContext.value to true
}
}}>
<Text>{myContext.value ? 'working' : 'not working'}</Text>
</TouchableOpacity>
The weird thing that I don't understand is that the context IS changing the onPress function. So if I click the button it will alert true at first and then false and back and forth as expected. What's not changing is the text that should be going between "working" and "not working".
This component is definitely within the scope of my Provider. I'm using useContext to get access to myContext.
Does anyone know why this might be happening?
The point to be noted here is that just changing myContext.value = false or true Will not trigger a re-render.
I am not sure how are you handling the value change, But I've created a replica of what you want
Check out this Snack to see the working example.
Create a Context as shown below
import { createContext } from 'react';
const MyContext = createContext();
export default MyContext;
An example App.js component would look like
import * as React from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
import MyContext from './MyContext';
import MyComponent from './MyComponent';
export default function App() {
const [value, setValue] = React.useState(true);
return (
<MyContext.Provider value={{ value, setValue }}>
<MyComponent />
</MyContext.Provider>
);
}
And your component where you want to perform this operation should look like this
import * as React from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
import MyContext from './MyContext';
export default function MyComponent() {
const myContext = React.useContext(MyContext);
return (
<TouchableOpacity
onPress={() => {
if (myContext.value) {
alert('true');
myContext.setValue(false);
//changes myContext.value to false
} else {
alert('false');
myContext.setValue(true);
//changes myContext.value to true
}
}}>
<Text>{myContext.value ? 'working' : 'not working'}</Text>
</TouchableOpacity>
);
}
Related
this is very basic but I'm new to JS and new to React, I need a few pointers on what I'm doing wrong. I have a Slider components I would like to change, and I would like to reflect/store the new value of the Slider in the class state. How should I be writing this?
I get this error:
TypeError: Cannot read properties of undefined (reading 'value')
Code example below:
import React from 'react';
import { Text, View, Button } from 'react-native';
import Slider from '#react-native-community/slider';
import { TextInput } from 'react-native-paper';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value1: 1
};
}
render() {
return(
<View>
<Text>Slider</Text>
<Slider
value={this.state.value1}
onValueChange={(e) => this.setState({value1: e.target.value})}
minimumValue={1}
maximumValue={5}
step={1}/>
</View>
);
}
}
export default App;
there is no mood property in your code, I highly recommend you to work with functional components instead of class components to be able to use hooks and make your code more clean and readable.
for more information check React Functional Components VS Class Components
I revised your code, now it updates the state of value1 whenever the slider moves:
import React from 'react';
import { Text, View, Button } from 'react-native';
import Slider from '#react-native-community/slider';
import { TextInput } from 'react-native-paper';
const App = (props) =>{
const [value1 , setValue1] = React.useState(1)
return(
<View>
<Text>The value is : {value1}</Text>
<Slider
value={value1}
onValueChange={setValue1}
minimumValue={1}
maximumValue={5}
step={1}/>
</View>
);
}
export default App;
demo
No mood property in your code. So it is undefined.
In my React-native project in the HomeScreen, I get some values from AsyncStorage. After getting this value I compare it and take decision in which screen it will go next.
If the getValue is null then it will go the WelcomeScreen and if it is not null then it will go the HomeDrawer Screen.
Here I have provided the code-
import React from 'react';
import { StyleSheet, Text, View, AsyncStorage } from 'react-native';
import {StackNavigator} from 'react-navigation';
import WelcomeScreen from './WelcomeScreen';
import LoginScreen from './components/LoginScreen';
import NoteMeHome from './components/NoteMeHome';
import HomeDrawer from './HomeDrawer/HomeDrawer';
import SettingsScreen from './components/SettingsScreen';
class HomeScreen extends React.Component {
state = {
getValue: '',
}
async componentDidMount() {
const token = await AsyncStorage.getItem('toke');
this.setState({ getValue: token });
}
render() {
console.log('#ZZZ:', this.state.getValue);
if(this.state.getValue !== null) {
return (
<AppStackNavigator/>
);
} else {
return (
<AppStackNavigator2/>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStackNavigator = new StackNavigator({
HomeDrawer: {screen:HomeDrawer},
WelcomeScreen: {screen:WelcomeScreen},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
})
const AppStackNavigator2 = new StackNavigator({
WelcomeScreen: {screen:WelcomeScreen},
HomeDrawer: {screen:HomeDrawer},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
})
export default HomeScreen;
Now, after running this, if I get null value in the variable getValue , then it is showing the following warning-
Warning: Can't call setState(or forceUpdate) on an unmounted
component. this is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscription and asynchronous tasks in
the componentWillUnmount method.
So, how can I solve this warning issue?
I don't know whether it's a good practice or not. The problem was- my component was initializing with empty string and I was checking for null in render function. Initializing getvalue with null or checking for empty string in render would solve this issue.
So, the change I made in my code is -
state = {
getValue: ''
}
And it removes the warning.
A better solution would be to use the SwitchNavigator from react-navigation since your navigation stacks are identical and you only want to route to the first screen based on that token.
see example
import React from 'react';
import { StyleSheet, Text, View, AsyncStorage } from 'react-native';
import {StackNavigator, createSwitchNavigator} from 'react-navigation';
import WelcomeScreen from './WelcomeScreen';
import LoginScreen from './components/LoginScreen';
import NoteMeHome from './components/NoteMeHome';
import HomeDrawer from './HomeDrawer/HomeDrawer';
import SettingsScreen from './components/SettingsScreen';
const AppStackNavigator = new StackNavigator({
HomeDrawer: {screen:HomeDrawer},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
});
export default createAppContainer(createSwitchNavigator(
{
LaunchScreen,
WelcomeScreen,
AppStackNavigator,
},
{
initialRouteName: 'LaunchScreen',
}
));
class LaunchScreen extends React.Component {
constructor(props) {
super(props);
this._getToken();
}
// Fetch the token from storage then navigate to the appropriate place
_getToken = async () => {
const tok = await AsyncStorage.getItem('toke');
// This will switch to the Welcome screen or main AppStack. Then this launch
// screen will be unmounted and thrown away.
this.props.navigation.navigate(tok ? 'AppStackNavigator' : 'WelcomeScreen');
};
// Render any loading content that you like here
render() {
return (
<View>
{/*...*/}
</View>
);
}
}
So if i have alot of different props for one component I wish i could do something like
const { ...props } = props;
instead of
const { prop1, prop2, prop3, ... } = props;
why isn't statement 1 valid? or am I doing the syntax wrong?
Edit: statement 1 is valid i see that was me being stupid now.
what im looking for is a programatic way to achieve statement 2 so i dont have to write out loads of props
option one is useful if you are using HOCs, for example in React native, if you are making animated components you can go:
import React from 'react';
import { Animated, Text} from 'react-native';
class ComponentToBeAnimated extends React.Component {
render() {
const {...other} = this.props;
return (
<Text {...other}>
hello world
</Text>
)
}
}
export const AnimatedText = Animated.createAnimatedComponent(ComponentToBeAnimated )
Then when you instantiate AnimatedText, you can apply styles as if you were instantiating Text
import React from 'react';
import { StyleSheet, View} from 'react-native';
import { AnimatedText } from './AnimatedText';
const styles = StyleSheet.create({
title: {
fontSize: 19,
fontWeight: 'bold',
},
});
export const Message = () => {
return (
<View>
<AnimatedText style={styles.title}/>
</View>
)
}
See you can apply all the Text props you want from the HOC
I'm not sure how to describe what I'm trying to do with words so please take a look at the following code:
This is what causing me issues: this.fetchMessages()
import React, { Component } from 'react';
import { PushNotificationIOS, FlatList, TextInput, ActivityIndicator, ListView, Text, View, Image, TouchableWithoutFeedback, AsyncStorage } from 'react-native';
import { Actions } from 'react-native-router-flux';
import ConversationsItem from './ConversationsItem';
import { conversationFetch } from '../actions';
import { connect } from 'react-redux';
import { Divider } from 'react-native-elements'
import PushNotification from 'react-native-push-notification';
class Conversations extends Component {
componentDidMount() {
this.props.conversationFetch()
}
fetchMessages() {
this.props.conversationFetch()
}
render() {
PushNotification.configure({
onNotification: function(notification) {
PushNotification.getApplicationIconBadgeNumber((response) => {
PushNotification.setApplicationIconBadgeNumber(response + 1)
})
console.log( 'NOTIFICATION:', notification )
notification.finish(PushNotificationIOS.FetchResult.NoData);
this.fetchMessages()
}
});
if (!this.props.email) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
console.log(this.props.conversations)
return (
<View style={{flex: 1, backgroundColor: 'white'}}>
...
</View>
);
}
}
const mapStateToProps = (state) => {
console.log(state)
const { conversations } = state.conversation;
const { email } = state.conversation;
return { conversations, email };
};
export default connect(mapStateToProps, { conversationFetch })(Conversations);
When I call this.fetchMessages() inside PushNotification.configure({}), I get the following error message:
this.fetchMessages is not a function
I'm not sure if what I'm doing is possible but if so I'd really like to know how to make this work.
I tried adding .bind(this) and other ways around but got same error anyways.
Thanks for you help.
Functions declared with function keyword has their own this, so this inside onNotification function does not refer to the class.
Therefore use arrow function syntax, which will lexically resolve this and value of this inside will refer to class itself. So convert
onNotification: function(notification) {
to
onNotification: (notification) => {
So you have in fact tried binding the fetchMessages function in the constructor? Like such:
constructor(props) {
super(props)
this.fetchMessages = this.fetchMessages.bind(this);
}
You can also use an arrow function to bind your method to the class without calling the constructor like such:
() => this.fetchMessages()
I don't understand how I'm getting this error (pic below). In my LoginForm.js file, the onEmailChange(text) is giving me an unresolved function or method call to onEmailChange() error when I hover over it in my WebStorm IDE. In my index.js file, no error is being thrown anywhere.
I've looked around SO for this issue but it doesn't fully pertain to my problem.
I've tried File > Invalidate Caches/Restart but that didn't work.
Here's App.js:
import React, { Component } from 'react';
import {StyleSheet} from 'react-native';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import firebase from 'firebase';
import reducers from './reducers';
import LoginForm from './components/common/LoginForm';
class App extends Component {
render() {
return(
<Provider style={styles.c} store={createStore(reducers)}>
<LoginForm/>
</Provider>
);
}
}
const styles = StyleSheet.create({
c: {
flex: 1
}
});
export default App;
Here's LoginForm.js:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {emailChanged} from 'TorusTeensApp/src/actions';
import {Text, StyleSheet, KeyboardAvoidingView, TextInput, TouchableOpacity} from 'react-native';
class LoginForm extends Component {
render() {
onEmailChange(text)
{
this.props.emailChanged(text);
}
return(
<KeyboardAvoidingView style={styles.container}>
<TextInput
style={styles.userInput}
onsubmitediting={() => this.passwordInput.focus()}
returnKeyType={"next"}
placeholder={"Email"}
label={"Email"}
keyboardType={"email-address"}
autoCorrect={false}
onChangeText={this.onEmailChange.bind(this)}
value={this.props.email}
/>
<TextInput
style={styles.userInput}
ref={(userInput) => this.passwordInput = userInput}
returnKeyType={"go"}
placeholder={"Password"}
label={"Password"}
secureTextEntry
/>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>Create Account</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
);
}
}
const styles = StyleSheet.create({
container: {
padding: 20 // creates a gap from the bottom
},
userInput: {
marginBottom: 20,
backgroundColor: '#9b42f4',
height: 40
},
buttonContainer: {
backgroundColor: '#41bbf4',
paddingVertical: 10,
marginBottom: 20
},
buttonText: {
textAlign: 'center',
color: '#FFFFFF'
}
});
const mapStateToProps = state => {
return {
email: state.auth.email
};
};
export default connect(mapStateToProps, null, {emailChanged}) (LoginForm);
Here's index.js:
import {EMAIL_CHANGED} from './types';
export const emailChanged = (text) => {
return {
type: 'EMAIL_CHANGED',
payload: text
};
};
export default emailChanged();
Your connect is miswired
connect(mapStateToProps, null, {emailChanged}) (LoginForm);
It should be something like:
connect(mapStateToProps,
(dispatch) => ({emailChanged: (text) => dispatch(emailChanged(text))})
)(LoginForm);
so that your action actually gets dispatched
and as spotted by emed in comment:
export default emailChanged;
without parentheses.
You defined your callback inside your render() method and not inside the class body. Do it like this:
class LoginForm extends Component {
onEmailChange(text) {
this.props.emailChanged(text);
}
render() {
return(...);
}
}
Also you shouldn't bind methods inside your render() method. Do it in the constructor of your Component:
class LoginForm extends Component {
constructor(props) {
super(props);
this.onEmailChange.bind(this);
}
onEmailChange(text) {
// do something
}
// other methods
}
Or if you use babel and ES6, you can define your callback with an arrow function, then it will be automatically bound:
class LoginForm extends Component {
onEmailChange = text => {
// do something
};
// other methods
}
See also the react docs about autobinding.
Also your call to connect seems incorrect. If you want to dispatch the action emailChanged it has to look like this:
const mapStateToProps = state => {
return {
email: state.auth.email
};
};
const mapDispatchToProps = dispatch => {
// this put a function emailChanged into your props that will dispatch the correct action
emailChanged: text => dispatch(emailChanged(text))
};
const LoginFormContainer = connect(mapStateToProps, mapDispatchToProps)(LoginForm);
export default LoginFormContainer;
The third argument to connect needs to be a function that knows how to merge the output of mapStateToProps, mapDispatchToProps, and ownProps all into one object that is then used as props for your connected component. I think you're trying to pass that action to the mapDispatchToProps argument, which is the second argument not the third. So, based on what I think you're doing, you probably wanna change your connect line to look like this.
export default connect(mapStateToProps, {emailChanged}) (LoginForm);
Then, export the function from your actions file not the output of calling that function.
export default emailChanged;
Notice I removed the parentheses so it's not being called.
Then make the callback function a method on your class and bind it in the constructor.
constuctor(props) {
super(props);
this.onEmailChange = this.onEmailChange.bind(this);
}
onEmailChange(text) {
this.props.emailChanged(text);
}
Then update onChangeText on that element.
onChangeText={this.onEmailChange}