React Native Props passed to Child View won't behave as expected - reactjs

I'm new to RN, trying to get around it by trial and erroring a lot. I'm currently stuck with this :
I have one parent view which is like this :
import React, { Component } from 'react';
import { View, Image, Text, Button } from 'react-native';
class ParentView extends Component {
render() {
return(
<View style={{flex:1}}>
<View style={{flex:0.14, flexDirection:'row', alignItems:'center'}}>
<View style={{flex:1}}>
<Image
source = {require('./assets/image1.png')}
resizeMode= 'contain'
style={{flex:1, height:null, width:null}}
/>
</View>
<View style={{flex:3}}>
<Button title='dummytitle' onPress={() => this.props.navigation.navigate('Child', {
dbpath: 'db.category.subcategory',
})}
/>
</View>
etc...
This part works OK. In child view, I'm trying to import JSON data from a file like so :
import React, { Component } from 'react';
import { Text, View, TouchableOpacity, Image, StyleSheet } from 'react-native';
import AwesomeAlert from 'react-native-awesome-alerts';
import db from './db/quizDB';
class Quiz extends Component {
constructor(props) {
super(props);
this.qno = 0
this.score = 0
quiz = this.props.navigation.getParam('dbpath');
arrnew = Object.keys(quiz).map(function(k) {return quiz[k]});
this.state = {
question: arrnew[this.qno].question,
}
};
render() {
return(
<View style={{flex:1}}>
<View style={{flex:2}}>
<View style={styles.Question}>
<Text>{this.state.question}</Text>
</View>
etc..
{this.state.question} returns nothing, it's just empty. But if I hardcode quiz as quiz = db.category.subcategory, it does work, {this.state.question} displays the expected content.
What am I missing there ? It seems like props aren't processed as I'd like them to...

You need to either declare quiz and arrnew with let or var, or attach them to the state.
In addition, in React, standard practice is not to directly attach properties to the class instance, like you've done here:
this.qno = 0
this.score = 0
These should probably be local variables, but if you need to, these could be attached to the state instead.

Solved this, I was being stupid. I just imported db in the Parent view and set the quiz prop directly from there. My mistake was to set it as a string...

Related

How to re-render a react-native components when its prop changes?

Sorry if this is a bit of a noob question. I'm trying to set up a react-native multiplayer game with a lobby which shows who your opponents are. The problem is that even when the prop representing the opponents updates, the component does not re-render, even though a console log shows me the prop has changed (and that render() was called?). Some snippets from my code are below:
In Lobby.js:
export default class Lobby extends Component {
render() {
console.log('In Lobby, opponents = ' + this.props.opponents);
return(
<View style={{ flex: 1, justifyContent: "center", alignItems: 'center' }}>
<Text>Welcome to the lobby { this.props.username }!</Text>
<Text>Opponents: { this.props.opponents }</Text>
... more text and buttons ...
</View>
);
}
}
As I said, I can see in the console that this.props.opponents does change but the screen doesn't seem to re-render that <Text> component. I initially thought this may be because this.props.opponents is set to the opponents value of my Main component's state, but the console log seems to debunk this.
I've looked here on SO and found suggestions to add shouldComponentUpdate() and componentDidUpdate() to my component but the nextProps parameter would always actually be the same as this.props. I.e. if I add:
shouldComponentUpdate(nextProps) {
console.log('this.props.opponents=' + this.props.opponents + '; ' + 'nextProps.opponents=' + nextProps.opponents);
return this.props.opponents != nextProps.opponents;
}
This never actually returns True for me, even though the prop is definitely changing. (I also don't know what code I would actually want in componentDidUpdate() either, since I just want it to re-render). It just shows that, yes the prop is different but that both nextProps.opponents and this.props.opponents have changed to that same new value (i.e. 'TestUser').
I'm really at a loss here. Thanks in advance.
EDIT: Added simplified version of my code.
App.js:
import React, { Component } from 'react';
import Main from './components/Main';
export default function App() {
return (
<Main/>
)
}
In Main.js:
import React, { Component } from 'react';
import { View } from 'react-native';
import Lobby from './Lobby';
export default class Main extends Component {
constructor(props) {
super(props);
this.state = {
opponents: [], // array of strings of other users' usernames
in_lobby: true // whether user is in lobby or not
};
this.testClientJoin = this.testClientJoin.bind(this);
}
testClientJoin() {
console.log('Called testClientJoin().');
let currentOpponents = this.state.opponents;
currentOpponents.push('TestUser');
this.setState({
opponents: currentOpponents
});
}
render() {
return(
<View style={{ flex: 1}}>
{
this.state.in_lobby &&
<Lobby
opponents={this.state.opponents}
testClientJoin={this.testClientJoin}
/>
}
</View>
);
}
}
In Lobby.js:
import React, { Component } from 'react';
import { View, Button, Text } from 'react-native';
export default class Lobby extends Component {
render() {
console.log('Opponents prop = ' + this.props.opponents);
return(
<View style={{ flex: 1, justifyContent: "center", alignItems: 'center' }}>
<Text>Welcome to the lobby!</Text>
<Text>Your opponents are {this.props.opponents}.</Text>
<Button
title='Add test opponent'
onPress={this.props.testClientJoin}
/>
</View>
);
}
}
So. When I tap the button to Add test opponent, the console logs
Called testClientJoin().
Opponents prop = TestUser
which shows the prop has indeed updated. However, the doesn't actually reflect this change, until I force some update (i.e. since I'm using expo, I just save the file again, and I finally see the text appear on my phone. I'm sure I'm just being an idiot but would love to know how to get the desired behavior of the text component updating.
Well I found an answer to my question which I'll leave here if anyone finds this. What I had to do was the following:
Main.js:
Instead of passing this.state.opponents directly as a prop to <Lobby>, pass a copy of this array instead.
render() {
<Lobby
testClientJoin={this.testClientJoin}
opponents={ [...this.state.opponents] }
/>
And this has the desired result. So the moral of the story is, don't try to directly pass an array state from a parent to a child component.

Initialize react-native application with class components

I have just installed create-react-native-app and created one. I can see that app is a function there
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
</View>
);
}
I know that both Functional and Class-Components in available in React. Now I want to create app with Class Components. Can somebody suggest how to do it?
For people who come from heavy object-oriented background, the class-based component will let them jump on reactjs relatively quickly compared to a functional-based component.
Here is some basic skeleton of the class component:
import * as React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
export default class App extends React.PureComponent {
constructor() {
// Where you initialize some data
}
componentDidMount() {
// Lifecycle method when your component is mounted
}
componentWillUnmount() {
// Lifecycle method when your component is unmounted
}
_handleOnButtonPress = () => {
// Handler when your button is pressed
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this._handleOnButtonPress}>
<Text>Open up App.tsx to start working on your app!</Text>
</TouchableOpacity>
</View>
);
}
}
And here is a further reference to compare class and functional component. Cheers!
Here is code for you :
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet
} from 'react-native';
class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount = () => {
// initial method
};
render() {
return (
<View>
<Text>Test</Text>
</View>
);
}
}
const styles = StyleSheet.create({
});
export default App;
You don't need to make it a class unless you are using state in the class

Passing React Navigation to Child of Child Component

I'm dynamically building my "screen" with the use of child "row" and "button" components. I'm only using this method because I can't find a flex-flow property available for react-native.
So basically I'm mapping through an array of arrays to build each row, and within the row, mapping through each array to build each button. Because the onPress needs to be set in the button, I'm passing the URL for each
onPress{() => this.props.navigation.navigate({navigationURL})
as a prop, first to the row, and then to the button. The problem is I keep getting the error 'Cannot read property 'navigation' of undefined. I'm sure this is because only the actual "screens" within the navigator have access to the navigation props. I've also tried passing
navigation={this.props.navigation}
but had no success. I've looked through all of the documentation and can't seem to find anything helpful. Anyone else encountered a similar situation?
If you want to access the navigation object from a component which is not part of navigator, then wrap that component in withNavigation HOC. Within the wrapped component you can access navigation using this.props.navigation. Take a look at the official document
Sample
import { withNavigation } from 'react-navigation';
...
class CustomButton extends React.Component {
render() {
return <Button title="Back" onPress={() => {
this.props.navigation.goBack() }} />;
}
}
export default withNavigation(CustomButton);
Hope this will help!
Ahhh, silly mistake. I wasn't setting up Props in the constructor. Thank you Prasun Pal for the help! Here's my code if someone else has an issue.
import React, { Component } from 'react'
import { Image, Text, TouchableOpacity, View } from 'react-native'
import { withNavigation } from 'react-navigation'
class ButtonName extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<TouchableOpacity
onPress={() => this.props.navigation.navigate('PageName')}
>
</TouchableOpacity>
)
}
}
export default withNavigation(ButtonName);

Pass TextInput props between different screens in React Navigation

I am totally lost on how to grab two parameters from a form screen and pass them via React Navigator and display them on the previous screen.
The app section in question works like this:
1. touchablehighlight to form screen.
2. input title and description and press submit onpress
3. the onpress runs a function that dispatches the parameters to the previous page via a key.
4. then returns back to the origin page, with the props on display.
I am having multiple issues with the process:
1. if I am understanding the docs correctly, each page has a unique key and i tried to find it via this.props.navigation.state.key, however unknown to me, on refresh the id number would change.
2. that leads to problem 2 where the function will run, but it will not redirect back to the original page.
3. i have tried .navigate line after .dispatch but it would open a new copy of the original page and not display the new props that supposively were passed down.
import React from 'react';
import styles from '../styling/style.js';
import { Text, View, Image, TouchableHighlight, TextInput, Alert } from 'react-native';
import { StackNavigator, NavigationActions } from 'react-navigation';
import Forms from './formGenerator';
export default class Workout extends React.Component {
constructor(props) {
super(props);
this.state = {
programTitle: '',
programDescription: ''
}
}
render() {
const {navigate} = this.props.navigation;
return (
<Image style={styles.workoutContainer, { flex: 1}} source={require("../images/HomepageBackground.jpg")}>
<View style={styles.workoutBody}>
<Text style={styles.workoutTextBody}> {this.state.programTitle}</Text>
<Text style={styles.workoutTextBody}>{this.state.programDescription}</Text>
</View>
<View style={styles.createButton}>
<TouchableHighlight onPress={Alert.alert(this.props.navigation.state.key)} style={styles.addButtonTouch} title='test' >
<Text style={styles.addButtonText}>+</Text>
</TouchableHighlight>
</View>
</Image>
);
}
// End of the render
}
import React from 'react';
import styles from '../styling/style.js';
import { Text, View, Image, TouchableHighlight, TextInput, Button, Alert } from 'react-native';
import Workout from './workouts';
import { NavigationActions } from 'react-navigation';
export default class Forms extends React.Component {
constructor(props) {
super(props);
this.state = {
programTitle: '',
programDescription: ''
}
}
render() {
const {goBack} = this.props.navigation;
const {params} = this.props.navigation.state;
return (
<Image style={styles.workoutContainer, { flex: 1}} source={require("../images/HomepageBackground.jpg")}>
<View style={styles.workoutBody}>
<Text style={styles.formHeader}>Program Title</Text>
<TextInput
autoFocus={true}
style={styles.formBody}
onChangeText={(programTitle) => this.setState({programTitle})}
placeholder='Title'
value={this.state.programTitle} />
<Text style={styles.formHeader}>Description (Ex. 4sets x 10reps)</Text>
<TextInput
autoFocus={true}
style={styles.formBody}
placeholder='Description'
onChangeText={(programDescription) => this.setState({programDescription})}
value={this.state.programDescription} />
<TouchableHighlight onPress={this.addProgram} style={styles.buttonBody} title="Add Program" >
<Text>Add Program</Text>
</TouchableHighlight>
</View>
</Image>
);
}
addProgram = () => {
Alert.alert(this.props.navigation.state.key);
this.setState({programTitle: ''});
this.setState({programDescription: ''});
const setParamsAction = NavigationActions.setParams({
params: { programTitle: this.state.programTitle, programDescription: this.state.programDescription },
key: ,
})
this.props.navigation.dispatch(setParamsAction)
};
}
If you are trying to get parameter from "Next Page", you could have two approaches.
1, save the params in AsyncStorage (suggested)
2, using navigation setParams function with the params
const setParamsAction = NavigationActions.setParams({
params: { title: 'Hello' },
key: 'screen-123',
})
this.props.navigation.dispatch(setParamsAction)
https://reactnavigation.org/docs/navigators/navigation-actions
You are just trying to display information from the Forms class on the Workout class, correct??
From your Workout class, create a function that update's it's state.
updateWorkoutState = (programTitle,programDescription) => this.setState(programTitle,programDescription)
Pass that function through to the Forms class when you push that route.
this.props.navigation.navigate('Forms',{updateWorkoutState: this.updateWorkoutState}
Once your conditions are met on the Forms class and you want to update the Workout component, call it with this.props.navigation.state.params.updateWorkoutState(val1,val2)
Do not use AsyncStorage for this.

Export and import observable MobX React Native

I am new to React Native and very new to MobX, only realising i needed it when my first project demanded dynamically updating and changing props/store between files.
Here is the github of the project: https://github.com/Kovah101/GenreGeneratorv1
I am trying to build an app that generates the name of random genre of music.
My main file renders all the components and has a small console.log to check that random numbers are being generated. I don't get any errors here
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import
GenreSelector
from './genreSelector';
import GenerateButton from './generateButton';
import styles from '../styles/styles';
import randomStore from './generateButton';
var MainOriginal = React.createClass ({
getInitialState() {
return {
chosenRandoms: [ , , ],
};
},
//used to check Generate button is working
changeRandoms(newRandoms) {
this.chosenRandoms = newRandoms;
console.log('the console log works!');
console.log(this.state.chosenRandoms);
} ,
render(){
return (
<View style={styles.container}>
<Text style= {styles.title}>Genre Generator</Text>
<Text style={styles.h2}>I am listening to</Text>
<View style={styles.genreContainer}>
<GenreSelector store={randomStore}/> {//passing randomStore as a prop to selector
}
</View>
<Text style={styles.h2}>You filthy casual</Text>
<GenerateButton onPress={this.changeRandoms}/>
</View>
);
}
});
module.exports = MainOriginal;
Next, GenerateButton renders a button with an onClickevent that generates an array of random numbers, these get checked by mainOriginaland work correctly. I also use MobX to make randomNumbers observable as it is constantly updated and will be passed to the final file genreSelector.
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
TouchableHighlight,
Text,
View
} from 'react-native';
import styles from '../styles/styles';
import {observable} from 'mobx';
var GenerateButton = React.createClass({
generateRandoms: function() {
#observable randomNumbers = [Math.random(), Math.random(), Math.random()];
this.props.onPress(randomNumbers);
},
render: function (){
return (
<TouchableHighlight
onPress={this.generateRandoms}
style={styles.generateButton}>
<Text style={styles.generateButton}>Generate!</Text>
</TouchableHighlight>
);
}
});
const randomStore = new GenerateButton ();
export default randomStore;
module.exports = GenerateButton;
genreSelector should use the array of random numbers map them to the size of the 3 different genre arrays, then render 3 boxes, each with one of the random genres from each array. However I get unexpected tokens at 'chosenRandoms' if i set it to be a 'var' and the same again at 'get randomGenres`, my understanding is they need to be something.
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import{
genre1,
genre2,
genre3
} from './genres.js';
import styles from '../styles/styles';
import {observer, computed} from 'mobx-react/native';
import randomStore from './generateButton';
const size = [genre1.length, genre2.length, genre3.length];
#observer
class GenreSelector extends Component {
render() {
var chosenrandom = this.props.randomStore.randomNumbers; //accessing the passed state
console.log({chosenrandom});
let randomGenres = [Math.floor(this.chosenrandom[0] * size[0], Math.floor(this.chosenrandom[1] * size[1], Math.floor(this.chosenrandom[2] * size[2]],
//manipulating the passed array -- ERROR AT END OF THIS LINE
return (
<View style={styles.genreContainer}>
<Text style={styles.genreText} >{genre1[randomGenres[0]]}</Text>
<Text style={styles.genreText} >{genre2[randomGenres[1]]}</Text>
<Text style={styles.genreText} >{genre3[randomGenres[2]]}</Text>
</View>
);
}
}
module.exports = GenreSelector;
Does anybody have any ideas on what i'm doing wrong? If i take the var and get out then i get an error at the end of the math manipulation line. I must be misusing something. Thanks for any help, I can provide more of my code but i dont think the problem is in the stylesheet or index.
So i decided to put all the calculations and genre picking to the generatebuttonand just send the array as a prop from the button to the mainOriginal then back to genreselector.
Didn't have to use MobX or anything overly complicated.
Here is the final code: github.com/Kovah101/GenreGeneratorv2

Resources