Export and import observable MobX React Native - arrays

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

Related

How to create a sort of Context Template for different parents components in react native?

I wanted to Internationalize my react native app only using redux, but for the sake of brevity I'm going to elude the part relative to redux since the whole app is working perfectly fine. for each screen component of the app, I create a context to wrap the screen component and pass down all the strings related to the screen
So i created the context in a separate file that I'm exporting as Language:
import React, { useContext, createContext } from "react";
import { useSelector } from "react-redux";
import { getTranslation } from "../../../redux/reducers/translation/selector/selector";
import {language} from "./Util";
export const Context = createContext();
export const useLanguageContext = () => {
return useContext(Context);
};
export default function Language({ children }) {
const translation = useSelector(getTranslation);
const lang = language(translation.lang);
return <Context.Provider value={lang}>{children}</Context.Provider>;
}
translation.lang is the current language grabbed from redux that I pass to the language function to get the strings related to the screen accordingly. in this case it's going to be the Entrance screen. Here is the logic for the language function:
import french from "../../../i18n/french/auth/entrance/i18n";
import english from "../../../i18n/english/auth/entrance/i18n";
import spanish from "../../../i18n/spanish/auth/entrance/i18n";
import wolof from "../../../i18n/wolof/auth/entrance/i18n";
export const language = (translation) => {
switch (translation) {
case "french":
return french;
case "english":
return english;
case "spanish":
return spanish;
case "wolof":
return wolof;
default:
return french;
}
};
and I wrap the Entrance screen with the context like this
import React from "react";
import { View } from "react-native";
import { Slider, Button } from "../../../components/organisms/auth/Entrance";
import Language from "../../../utils/language/context";
import { styles } from "./Style";
export default function Entrance() {
return (
<Language>
<View style={styles.container}>
<Slider />
<Button />
</View>
</Language>
);
}
And finally, from a child component like Button, I can retrieve the language object passed through like this
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import { useNavigation } from "#react-navigation/native";
import { useLanguageContext } from "../../../../../../utils/language/context";
import { styles } from "./Style";
export default function Button() {
const lang = useLanguageContext();
const navigation = useNavigation();
return (
<TouchableOpacity
style={styles.container}
onPress={() => {
navigation.navigate("LoginNavigator");
}}
>
<Text style={styles.text}>{lang.login}</Text>
</TouchableOpacity>
);
}
Now all of this is working perfectly fine but the main problem is I'm going to have a lot of screens and don't want to be repeating this process of creating a context for each and every screen. Isn't there a neater way to create a sort of Context template that I can use for the different screens instead of creating one for each screen?
If the purpose of this implementation is to separate the translations for each screen, then you could pass a property to the context provider that works on only grabbing the translations for the screen you want.
A cool tip that makes it easier to import the languages instead of importing each one individually is to create an index file that exports it from one entry, for example:
Instead of having
import french from "../../../i18n/french/auth/entrance/i18n";
import english from "../../../i18n/english/auth/entrance/i18n";
import spanish from "../../../i18n/spanish/auth/entrance/i18n";
import wolof from "../../../i18n/wolof/auth/entrance/i18n";
Create an index file inside ../../../i18n/french/auth that exports the translations for a specific screen like so
export * as entrance from "./entrance/i18n"
export * as login from "./login/i18n"
// etc
Then, from each language folder, an index file to export the related translations from one place
export * from "./auth"
export * from "./home"
// etc
That way it can be accessed like so, where the screen is the name of the screen to grab the translations for, and the translation is the language
import french from "../../../i18n/french"
// other languages
export const language = (screen, translation) => {
switch (translation) {
case "french":
return french[screen];
// other languages
default:
return french[screen];
}
};
Hope you find this helpful!
I try something quite simple and didn't expect it to work
Now in the Context file, I'm passing the language function as a props to each screen the Context will wrap
import React, { useContext, createContext } from "react";
import { useSelector } from "react-redux";
import { getTranslation } from "../../../redux/reducers/translation/selector/selector";
export const Context = createContext();
export const useLanguageContext = () => {
return useContext(Context);
};
export default function Language({ children, language }) {
const translation = useSelector(getTranslation);
const lang = language(translation.lang);
return <Context.Provider value={lang}>{children}</Context.Provider>;
}
and then inside each Screen, I'll import the language function and wrap it with the Context like this:
import React from "react";
import { View } from "react-native";
import { Slider, Buttons } from "../../../components/organisms/auth/Entrance";
import Language from "../../../utils/language/context";
import { styles } from "./Style";
import { language } from "./Utils";
export default function Entrance() {
return (
<Language language={language}>
<View style={styles.container}>
<Slider />
<Buttons />
</View>
</Language>
);
}
And it worked.
#Ibrahim thanks for your answer i'll try to structure the translations folder as you mentioned

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

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...

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.

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 passing array between components

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

Resources