I have the following setup:
import {getNewImage} from '...'
export default class FirstClass extends Component {
constructor() {
super();
this.state = {
imageURL: 'www.test.com/new.jpg',
}
}
update = () => {
this.setState({
imageURL: 'www.test.com/updated.jpg',
})
}
render() {
return (
<View>
<Image
source={{ uri: this.state.imageURL }}
/>
</View>
);
}
}
import Class1 from '...'
export default class SecondClass extends Component {
render() {
return (
<TouchableOpacity onPress={() => new FirstClass().update()}>
<Class1></Class1>
</TouchableOpacity>
);
}
}
The problem is: it doesn't update the imageURL. I tried multiple things. Logging this inside of update gave back the right object. But trying to log the this.setState() gives an undefinded back.
I suppose by Class1 you mean FirstClass.
you should use the reference of the component using ref, not creating new instance of FirstClass class
checkout this code
export default class SecondClass extends Component {
private firstClass = null;
render() {
return (
<TouchableOpacity onPress={() => this.firstClass.update()}>
<Class1 ref={ref => this.firstClass = ref} />
</TouchableOpacity>
);
}
}
You can try to update state in Class1 similar to what do you want by the react reference.
But on my opinion this is non a good case and predictable behavior to change child component state from parent.
As another options you can add additional state to the SecondClass and pass it via props to child component. And inside Class1 in getDerivedStateFromProps based on that prop change state.
export default class FirstClass extends Component {
constructor() {
super();
this.state = {
isNeedToUdpate: false,
imageURL: "www.test.com/new.jpg"
};
}
static getDerivedStateFromProps(props, state) {
if (props.isNeedToUdpate !== state.isNeedToUdpate) {
return {
isNeedToUdpate: props.isNeedToUdpate,
imageURL: "www.test.com/updated.jpg"
};
}
return null;
}
render() {
return (
<View>
<Image source={{ uri: this.state.imageURL }} />
</View>
);
}
}
export default class SecondClass extends Component {
constructor() {
super();
this.state = {
isNeedToUpdateClass1: false
};
}
render() {
return (
<TouchableOpacity
onPress={() => {
this.setState({ isNeedToUpdateClass1: true });
}}
>
<Class1 isNeedToUpdate={this.state.isNeedToUpdateClass1}></Class1>
</TouchableOpacity>
);
}
}
Related
I can't solve this issue by myself. I have a Class
class WebViewScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
url: 'https://example.org',
};
}
someMethod = () => {
return 'something';
}
render() {
return (
<View style={styles.mainContainer}>
<WebView
source={{uri: this.state.url}}
/>
<StatusBar backgroundColor="white" barStyle="dark-content" />
</View>
);
}
static navigationOptions = ({navigation}) => {
return {
headerTitle: () => (
<AddressBar navigation={navigation} />
),
};
};
}
I want to access someMethod from static navigationOptions
I've tried the following without the success:
run this.someMethod()
run WebViewScreen.someMethod()
let _this outside a class, then _this = this from a WebViewScreen Class constructor
then _this.someMethod() from static navigationOptions
My question is, how can I achieve this?
You cannot access the component's state from within a static method. What you can do is keeping a navigation's parameter in sync with your local state and/or pass functions to it with
this.props.navigation.setParams({
someMethod: this.someMethod.bind(this)
})
which you can retrieve with:
static navigationOptions = ({ navigation }) => {
const someMethod = navigation.getParam('someMethod', () => null)
// ... someMethod()
There is a build-in function in React Navigation 4 called: setParams. This function could be used to set params in static navigationOptions, however in React Navigation 5 there will be a dynamic way to change the navigation. Until then you should use setParams. In your WebViewScreen component you should use it like this.
class WebViewScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
url: 'https://example.org'
};
}
componentDidMount() {
const { navigation } = this.props;
navigation.setParams({ logOut: this.someMethod });
}
someMethod = () => {
return 'log out';
};
render() {
return (
<View style={styles.mainContainer}>
<WebView source={{ uri: this.state.url }} />
<StatusBar backgroundColor="white" barStyle="dark-content" />
</View>
);
}
static navigationOptions = ({
navigation,
navigation: {
state: { params }
}
}) => {
return {
headerTitle: () => <AddressBar navigation={navigation} />,
headerRight: (
<TouchableOpacity onPress={() => params.someMethod()}>
Log out
</TouchableOpacity>
)
};
};
}
Important to note: you can only add params to the static nav, when the component is mounted!
i'm trying to pass an image path down to one component via react-navigation params and from that component to a third one as a prop.
This is a chat application and the structure of the components is:
ChatsMain.js --> (click on SingleChat, passing parameters when navigating) --> SingleConversation.js --> Message
I can pass 'name' as a parameter, but not the uri of an image, not even by assigning it to variables.
The name that i see on the SingleChat is passed without problems to the correspondent conversation, but when i pass the image i get the error:
'Failed prop type: Invalid prop source supplied to Image.'
I hope it's clear enough, i don't think it's the correct way to do this, but i am new to react native!
ChatsMain.js
const imageNames = {
juf: require('../assets/juf.png'),
meester: require('../assets/meester.png'),
groep: require('../assets/groep.png'),
onderwerpen: require('../assets/onderwerpen.png')
}
export default class ChatsMain extends React.Component {
render() {
return (
<SingleChat
image={imageNames.juf}
backgroundColor={yellow}
borderColor={blue}
name='Juf Elsa'
onPress={() => this.props.navigation.navigate('SingleConversation', {name: 'Juf Elsa', image: imageNames.juf})}
/>
)
SingleConversation.js
export default class SingleConversation extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('name', 'Chat'),
headerStyle: [titleBar, { backgroundColor: yellow }],
headerTitleStyle: [title, { color: blue }]
}
}
render() {
return (
<View style={chatMainContainer}>
<ScrollView style={chatsContent}>
<Message image={({navigation}) => navigation.getParam('image', 'image')} />
</ScrollView>
</View>
);
}
}
Message.js
export default class ReceivedFirst extends React.Component {
render(
){
return(
<View style={textWithPicture}>
<View style={[chatPhotoContainer, { borderColor: yellow }]}>
<Image source={this.props.image} style={{ width: 70, height: 70}} />
.....
.....
)
You can use this.props.navigation.state.params instead this.props.navigation.getParam
export default class SingleConversation extends React.Component {
constructor(props) {
super(props);
this.state = {
image: this.props.navigation.state.params.image
};
}
...
Message image={this.state.image} />
OR
You need to get a Navigation from the props.
render() {
const { navigation } = this.props
return (
<View style={chatMainContainer}>
<ScrollView style={chatsContent}>
<Message image={() => navigation.getParam('image', 'image')} />
</ScrollView>
</View>
);
}
I want to change the theme of the app(say background colors) by selecting from a list of colors. I've tried saving the state in a reducer and applying the state to a new page.
LoginView.js
import styles from "./styles";
class LoginView extends Component {
constructor(props) {
super(props);
this.state = {
primaryColor: theme.PRIMARY_COLOR
};
}
navigate = () => {
this.props.navigation.navigate("Home");
};
onPressGreen = () => {
theme.PRIMARY_COLOR = "green";
this.setState({ primaryColor: theme.PRIMARY_COLOR });
this.props.onPressGreenButton(theme.PRIMARY_COLOR);
};
onPressRed = () => {
theme.PRIMARY_COLOR = "red";
this.setState({ primaryColor: theme.PRIMARY_COLOR });
};
render() {
return (
<View
style={[styles.container, { backgroundColor: this.state.primaryColor }]}
>
<TouchableOpacity
style={styles.greenButton}
onPress={this.onPressGreen}
>
<Text>Green</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.redButton} onPress={this.onPressRed}>
<Text>Red</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.navigate}>
<Text>Home</Text>
</TouchableOpacity>
</View>
);
}
}
function mapDispatchToProps(dispatch) {
return {
onPressGreenButton: color => dispatch(loginActions.saveColor(color))
};
}
export default connect(
null,
mapDispatchToProps
)(LoginView);
HomeView.js
class HomeView extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={[styles.container, { backgroundColor: this.props.color }]}>
<Text>Home</Text>
</View>
);
}
}
function mapStateToProps(state) {
return {
color: state.loginReducer.color //color saved in reducer
};
}
export default connect(
mapStateToProps,
null
)(HomeView);
In HomeView.js file instead of applying backgroundcolor in the way I did, how to include it inside styles.container. Is there any way to access function mapStateToProps inside styles.js I've imported. I've used redux to save the state permanently.
you can use redux outside of react component like styles.js
by store.getState() API in redux
but I think the best way to handle this is to create a wrapper component and apply them to it then use it everywhere you want
class MasterView extends Component {
render() {
return (
<View style={[styles.container, { backgroundColor: this.props.color }]}>
{this.props.children}
</View>
);
}
}
function mapStateToProps(state) {
return {
color: state.loginReducer.color //color saved in reducer
};
}
export default connect(
mapStateToProps,
null
)(MasterScreen);
then on each screen, you want you can use it for example
class HomeView extends Component {
constructor(props) {
super(props);
}
render() {
return (
<MasterView>
<Text>Home</Text>
</MasterView>
);
}
}
another way is in styles.js
import store from '../store'
const containerColor=store.getState().loginReducer.color
const styles = StyleSheet.create({
container: {
backgroundColor:containerColor
},
}
export default styles
I have the following problem.
I am creating a React Native appliation and I want to pass a click handler to a child component. But when I try to call the click handler in the child component I keep getting a _this.props.onItemPress is not a function Exception.
When I try to pass the function with a .bind(this) inside the parent, it says the function is undefined.
Here's my code:
Parent
constructor(props) {
super(props)
this.handleTodoPress = this.handleTodoPress.bind(this)
}
...
handleTodoPress(event) {
console.warn('Press handled')
}
renderItem ({section, item}) {
return <TodoItem onItemPress={this.handleTodoPress} title={item.title} description={item.description} completed={item.completed} />
}
...
render () {
return (
<View style={styles.container}>
<SectionList
renderSectionHeader={this.renderSectionHeader}
sections={this.state.data}
contentContainerStyle={styles.listContent}
data={this.state.dataObjects}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
initialNumToRender={this.oneScreensWorth}
ListHeaderComponent={this.renderHeader}
SectionSeparatorComponent={this.renderSectionSeparator}
ListEmptyComponent={this.renderEmpty}
ItemSeparatorComponent={this.renderSeparator}
renderSectionFooter={this.renderSectionFooter}
/>
</View>
)
}
}
Child
import React, { Component } from 'react';
import { TouchableOpacity, View, Text, } from 'react-native';
import styles from './Styles/TodoItemStyles'
export default class TodoItem extends Component {
constructor(props) {
super(props)
this.state = {completed: 'Todo'}
this.setCompletedState = this.setCompletedState.bind(this)
}
itemPressed = (e) => {
console.warn(this.props);
this.props.onItemPress(e)
}
setCompletedState() {
if (this.props.completed == true) {
this.setState({completed: 'Completed'})
}
}
componentWillMount() {
this.setCompletedState()
}
render() {
return (
<TouchableOpacity onPress={this.itemPressed}>
<View style={styles.todoContainer}>
<Text style={styles.itemTitle}>{this.props.title}</Text>
<Text style={styles.itemDescription}>{this.props.description}</Text>
<Text style={[styles.itemLabel, this.props.completed ? styles.itemLabelCompleted : styles.itemLabelNotCompleted]}>{this.state.completed}</Text>
</View>
</TouchableOpacity>
);
}
}
TRY:
export default class TodoItem extends Component {
constructor(props) {
super(props)
this.state = {completed: 'Todo'}
this.setCompletedState = this.setCompletedState.bind(this)
}
itemPressed(e){
console.warn(this.props);
this.props.onItemPress(e)
}
setCompletedState() {
if (this.props.completed == true) {
this.setState({completed: 'Completed'})
}
}
componentWillMount() {
this.setCompletedState()
}
render() {
return (
<TouchableOpacity onPress={this.itemPressed}>
<View style={styles.todoContainer}>
<Text style={styles.itemTitle}>{this.props.title}</Text>
<Text style={styles.itemDescription}>{this.props.description}</Text>
<Text style={[styles.itemLabel, this.props.completed ? styles.itemLabelCompleted : styles.itemLabelNotCompleted]}>{this.state.completed}</Text>
</View>
</TouchableOpacity>
);
}
}
when you use
itemPressed = (e) => {
console.warn(this.props);
this.props.onItemPress(e)
}
that notations binds the current context inside the function
I think your problem is that how you are using arrow function for itemPressed. Try rewriting it and binding this for itemPressed the same as you did for setCompletedState.
I have a problem with refs on React Native. This is a simplified version of my code:
class Main extends React.Component {
constructor(props) {
...
this.refs = {};
}
render() {
if(this.state.page=="index") {
return(
<View>
<FlatList ref={flatlist => this.refs.flatlist = flatlist}> ... </FlatList>
<MyActionButton flatlist={this.refs.flatlist}/>
</View>
)
} else if (this.state.page="text"=){
return(
<Text> ... </Text>
)
}
}
}
class MyActionButton extends React.Component {
render(
return(
<ActionButton>
<ActionButtonItem onPress={() => {
console.log("AB props", this.props)
}} />
</ActionButton>
)
)
}
The app starts with this.state.page = "index" so when I press MyActionButton I see the log as expected, and things seem to work:
'AB props', {flatlist: {A LOT OF STUFF HERE}}
However If I change the state.page to "text" and then come back to "index" again, when I press MyActionButton I get:
'AB props', {flatlist: undefined}
I'm not sure why that prop gets undefined and how to fix it to make it point to the actual FlatList.
I don't like very much, but I managed to get it working by changing the reference to a getter funcion
class Main extends React.Component {
constructor(props) {
...
this.refs = {};
}
getFlatList() {
return this.refs.flatlist;
}
render() {
if(this.state.page=="index") {
return(
<View>
<FlatList ref={flatlist => this.refs.flatlist = flatlist}> ... </FlatList>
<MyActionButton flatlist={this.getFlatList.bind(this)}/>
</View>
)
} else if (this.state.page="text"=){
return(
<Text> ... </Text>
)
}
}
}