React Native passing array between components - arrays

I am trying to pass an array of numbers from one child component to another. I believe the way to do this is through their parent file.
First child- generateButton simply generates an array of 3 random numbers onPress
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
TouchableHighlight,
Text,
View
} from 'react-native';
import styles from '../styles/styles';
var GenerateButton = React.createClass({
generateRandoms: function() {
let 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>
);
}
});
module.exports = GenerateButton;
Parent- main uses this to change the value of the state randomGenres to the value returned by generateButton, this state is then passed to the 2nd child Genre
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import {
Genre
} from './genreSelector';
import GenerateButton from './generateButton';
import styles from '../styles/styles';
var Main = React.createClass ({
propTypes: {
randomGenres: React.PropTypes.array,
newRandoms: React.PropTypes.array
},
getInitialState: function () {
return { randomGenres: [4,7,19]};
},
changeRandoms: function (newRandoms) {
this.setState({
randomGenres: newRandoms
});
},
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}>
<Genre randomGenres={this.state.randomGenres}/>
</View>
<Text style={styles.h2}>You filthy casual</Text>
<GenerateButton onPress={this.changeRandoms}/>
</View>
);
}
});
module.exports = Main;
Second child- Genre, at this point all i want it to do is receive randomGenres as a prop and render the three values
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';
var Genre = React.createClass({
render: function (){
var randomGenres = this.props.randomGenres;
return (
<View styles={styles.genreContainer}>
<Text >{randomGenres[0]}</Text>
<Text >{randomGenres[1]}</Text>
<Text >{randomGenres[2]}</Text>
</View>
);
}
});
module.exports = Genre;
Eventually it will convert the random numbers into random genres by finding a random element of an array of genres and displaying them instead of just the numbers. However i cannot seem to pass the array between the components
My Error: Element type is invalid: expected a string or a class/function, check the render method of main
This only occurs when this line is uncommented:
<Genre randomGenres={this.state.randomGenres}/>
I believe my error lies in how Genre receives the array stored in randomGenres
I have tried to set the data type of the prop using propTypes but no luck, any help would be greatly appreciated!

EDIT:
Within the render method of your Genre component you use the attribute styles instead of style Removing it should fix
your error:
import React, { Component } from 'react';
...
var Genre = React.createClass({
render: function (){
var randomGenres = this.props.randomGenres;
return (
<View style={styles.genreContainer}>
...
</View>
);
}
});
module.exports = Genre;
As NiFi's comment below details: the setState method accepts a callback as an optional second argument which is triggered once setState has completed. Grabbing the randomNumbers value here should yield the correct result

Related

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.

Navigate to completely different screen on React Native

On my React Native app I am initially loading SceneA, which simply displays a text "Click me". When this button is clicked I would like to load a completely different screen (SceneB) that has completely different components.
All the examples that I found online (like the example below) load a scene with exactly the same components, but different values. In my case it is a completely different layout. How can I navigate to that new screen (SceneB)?
It is basically the equivalent to a new Activity on Android, passing some data at the same time. Any pointers will be appreciated.
index.android.js
import React, { Component } from 'react';
import { AppRegistry, Navigator } from 'react-native';
import SceneA from './SceneA';
class ReactNativeTest extends Component {
render() {
return (
<Navigator
initialRoute={{ title: 'My Initial Scene', index: 0 }}
renderScene={(route, navigator) =>
<SceneA
title={route.title}
// Function to call when a new scene should be displayed
loadNewScene={() => {
const nextIndex = route.index + 1;
navigator.push({
title: 'Scene B',
index: nextIndex,
});
}}
/>
}
/>
)
}
}
AppRegistry.registerComponent('ReactNativeTest', () => ReactNativeTest);
SceneA.js
import React, { Component, PropTypes } from 'react';
import { View, Text, TouchableHighlight } from 'react-native';
export default class SceneA extends Component {
render() {
return (
<View>
<Text>Scene A</Text>
<TouchableHighlight onPress={this.props.loadNewScene}>
<Text>Click Me</Text>
</TouchableHighlight>
</View>
)
}
}
SceneA.propTypes = {
loadNewScene: PropTypes.func.isRequired,
};
You handle which components should render in the renderScene function, each scene will have a route.title so you can decide which to render based on that.
renderScene={(route, navigator) =>
if (route.title === 'Scene A') {
return <SceneA navigator={navigator} />
}
if (route.title === 'Scene B') {
return <SceneB navigator={navigator} />
}
}
Then inside your components you're gonna have a function that handles the navigation:
navigateToSceneB = () => {
this.props.navigator.push({
title: 'Scene B'
})
}

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

MobX passing simple array from observable to observer

I am new to MobX and was recommended it here.
I have a simple array which I am trying to pass to another file.
I have tried many things but I am unable to receive the array in the form of this.props.store
If you could shine some light on my improper use of mobX it would be greatly appreciated. Thank you.
import React, { Component } from 'react';
import {
Alert,
AppRegistry,
StyleSheet,
Text,
TouchableHighlight,
View
} from 'react-native';
import styles from '../styles/styles.js';
import {observable, extendObservable} from 'mobx';
//unsure why below code wont work, when trying to export it throws error: object is not a constructor (evaluating 'new rNumStore()')
// const rNumStore = observable([1,2,3]);
// a couple of other options both i am unsure why they do not work
// class rNumStore {
// extendObservable(this, {
// rNumbers: [1,2,3]});
// }
// class rNumStore {
// observable({rNumbers: [1,2,3]});
// }
//Current best working solution below
var rNumStore = function(){
extendObservable(this, {
rNumbers: [1,2,3]
});
}
var Button = React.createClass({
rNumberGen: function(){
store.rNumbers = [Math.random(), Math.random(), Math.random()];
var a = store.rNumbers[0].toString();
var d =store.rNumbers.toString();
Alert.alert(a, d);
//this alert gives mobxArray of random numbers
},
render: function(){
return(
<TouchableHighlight onPress={this.rNumberGen} style= {styles.center}>
<Text style={styles.button}>Generate!</Text>
</TouchableHighlight>
);
}
});
var store = new rNumStore;
export default store;
export {Button};
Then the second file needs to observe the array
import React, { Component } from 'react';
import {
Alert,
AppRegistry,
StyleSheet,
TouchableHighlight,
Text,
View
} from 'react-native';
import{
genre1,
genre2,
genre3
} from './genres.js';
import styles from '../styles/styles.js';
import {observer} from 'mobx-react/native';
var Genre = observer(React.createClass({
alert: function(){
//below does not cause an error.
var a = this.props.store;
//undefined ins not an object(evaluating 'this.props.store.rNumbers')
// have tried making 'a' a state : this.props.store.rNumbers same error occurs at that point
var b = this.props.store.rNumbers[1];
var c = b.toString();
Alert.alert('this is', c);
},
render: function(){
let genre = this.props.selected;
return(
<TouchableHighlight onPress={this.alert}>
<View style={styles.genre}>
<Text style={styles.center}>{genre}</Text>
</View>
</TouchableHighlight>
);
},
}));
module.exports = Genre;
Where is your main app code? That should create the store, then in it's render it should have and tags that you pass the store to as a property
Edit:
The main store needs a class:
class rNumStore {
rNumbers = observable([1,2,3])
rNumGen() {
this.rNumbers = [Math.random(), Math.random(), Math.random()]
//Alert.alert('this is', rNumbers.toString())
}
}
class Main extends React.Component {
render(){
return(
<View style={styles.container}>
<Text style={styles.title}>Genre Genrerator</Text>
<Text style={[styles.h2, styles.h21]}>I am listening to</Text>
<View style={styles.genreContainer}>
<Genre store={store} selected='{genre[1]}'/>
<Genre store={store} selected='{genre[2]}'/>
<Genre store={store} selected='{genre[3]}'/>
</View>
<Text style={styles.h2}>You filthy casual</Text>
<Button store={store}/>
</View>
);
}
}
var store = new rNumStore;
export default store;
module.exports = Main;
which is exported as above.
Then as Peter says the store then needs to be passed into the props of the observer classes.
Thanks for the help.

React Native: this.state of reusable components not expected

I'm a newbie of React native and honestly I have just a very basic knowledge of React. I'm developing a sample application in which I make use of reusable components and ES6 sintax.
I'am experiencing unexpected results when reusing the same component multiple times in the same Scene (I also make use of Navigator). More precisely I can't understand why differents components (of the same type) are apparently conditions each others states.
I'm posting my code for a better understanding.
This is my main page , in which I make use 2 times of the same custom defined component < TopCategories /> :
HomeScene.js
import React from 'react';
import {View} from 'react-native';
import BaseScene from './BaseScene'
import SearchSuggest from '../components/SearchSuggest';
import TopCategories from '../components/TopCategories'
import styles from '../styles'
export default class HomeScene extends BaseScene {
render() {
return(
<View style={styles.container}>
<SearchSuggest
navigator={this.props.navigator}
/>
<TopCategories/> //first
<TopCategories/> //second
</View>
)
}
}
These are the details of the inner components used:
TopCategories.js
import React, { Component } from 'react';
import {
Text,
View,
StyleSheet
} from 'react-native';
import styles from '../styles'
import utility from '../utility'
import serverConnector from '../serverConnector'
import routes from '../routes'
import MenuItemComplex from './MenuItemComplex'
export default class TopCategories extends Component {
constructor(props) {
super(props);
this.state = {categories: []};
this._fetchContent();
}
_updateCategoriesList(rawCategories){
//store in state.categories array a simplied version of
//the contents received from the server
let simplifiedCategories = [];
for(i=0; i<rawCategories.length; i++){
var simpleCat = {};
simpleCat.id = rawCategories[i].uniqueID;
simpleCat.name = rawCategories[i].name;
simplifiedCategories.push(simpleCat);
}
this.setState({categories: simplifiedCategories });
}
_fetchContent(){
//fetch content from server in JSON format
_topCategories = this;
serverConnector.call(
"CATEGORY",
"FindTopCategories",
{},
function(err, json){
if(err!=null) utility.log("e", err);
else {
try{
_topCategories._updateCategoriesList(json.res.header.body.CatalogGroupView);
}catch(err){
utility.log("e", err);
}
}
}
)
}
openCategoryScene(id, name){
//push on Navigator stack the next route with additional data
let nextRoute = routes.get("categoriesListFirst");
nextRoute.passProps = {
categoryId: id,
categoryName: name
};
this.props.navigate(nextRoute)
}
render(){
console.log(this.state)
return (
<MenuItemComplex key="categories" name="Catalogo" icon="list-b" onItemSelected={this.openCategoryScene.bind(this)} subItems={this.state.categories} />
)
}
}
and finally
MenuItemComplex.js
import React, { Component } from 'react';
import { View, Text, Image, TouchableHighlight, TouchableWithoutFeedback } from 'react-native';
import styles from '../styles'
export default class MenuItemComplex extends Component{
static propTypes = {
name : React.PropTypes.string.isRequired,
icon : React.PropTypes.string.isRequired,
subItems: React.PropTypes.array.isRequired,
onItemSelected: React.PropTypes.func.isRequired
};
render(){
let subItems = [];
for(i=0; i<this.props.subItems.length; i++){
let subItem = this.props.subItems[i];
subItems.push(
<TouchableHighlight
key={subItem.id}
underlayColor={"#d00"}
activeOpacity={1}
onPress={() => this.props.onItemSelected(subItem.id, subItem.name)}
>
<View style={styles.menuSubItem}>
<Text style={[styles.mmText, styles.menuSubItemText]} >
{subItem.name}
</Text>
</View>
</TouchableHighlight>
)
}
return(
<View>
<TouchableWithoutFeedback disabled={true}>
<View style={styles.menuItem}>
<Image style={styles.menuItemImage} source={{uri: this.props.icon}} />
<Text style={[styles.mmTextBold, styles.menuItemText]}>{this.props.name}</Text>
</View>
</TouchableWithoutFeedback>
{subItems}
</View>
)
}
}
I can't understand why the state.simplifiedCategories of the first < TopCategories > component used in my HomeScene seems to be an empty array after the second < TopCategories > component has rendered. So far I thought that the two components were completely isolated, with their own "private" state. But in this case seems that this is shared somehow.
Can someone explain what is happening here ? And then how can I fix that ?
Thanks
EDIT 2016/09/05
As suggested by user V-SHY I tried to give every component a randomic string as key, but this does not solve the problem.
What I find very strange is that I can see only an instance of < TopCategories > in the global window object, the last one.
The screenshot here refers to a test made with
<TopCategories key="tc_first" {...this.props}/>
<TopCategories key="tc_second" {...this.props}/>
in the HomeScene.js file
As suggested by Daniel there was an issue during the fetch of data from server. In particular I was wrong when creating a _topCategories=this object inside TopCategories.js file
_fetchContent(){
_topCategories = this; // ISSUE HERE!
serverConnector.call(
"CATEGORY",
"FindTopCategories",
{},
function(err, json){
if(err!=null) utility.log("e", err);
else {
try{
_topCategories._updateCategoriesList(json.res.header.body.CatalogGroupView);
}catch(err){
utility.log("e", err);
}
}
}
)
}
I solved passing the fetchContent method a reference of the component :
constructor(props) {
super(props);
this.state = {categories: []};
this._fetchContent(this); // passing THIS reference
}
_updateCategoriesList(rawCategories){
let simplifiedCategories = [];
for(i=0; i<rawCategories.length; i++){
var simpleCat = {};
simpleCat.id = rawCategories[i].uniqueID;
simpleCat.name = rawCategories[i].name;
simplifiedCategories.push(simpleCat);
}
this.setState({categories: simplifiedCategories});
}
_fetchContent(instanceRef){
motifConnector.call(
"CATEGORY",
"FindTopCategories",
{},
function(err, json){
if(err!=null) utility.log("e", err);
else {
try{
instanceRef._updateCategoriesList(json.res.header.body.CatalogGroupView);
}catch(err){
utility.log("e", err);
}
}
}
)
}

Resources