React Native Update picker value except a component with ref - reactjs

I'm new to React/Native, I've being breaking my head with this issue. I'm using a library called react-native-image-picker-form which is so good, it let's use your phone camera or you can choose a picture from your album.
Let me show you the code
import React, {Component} from 'react'
import {Text, View, TouchableOpacity, Picker} from 'react-native'
import t from 'tcomb-form-native'
import ImageFactory from 'react-native-image-picker-form';
const Form = t.form.Form;
const DocumentFormStruct = t.struct({
image: t.String
});
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
form: null,
value: null,
language: 'PERMISO ESPECIAL',
options: {
fields: {
image: {
config: {
title: 'Select image',
options: ['Open camera', 'Select from gallery', 'Cancel'],
// Used on Android to style BottomSheet
style: {
titleFontFamily: 'Roboto'
}
},
error: 'No image provided',
factory: ImageFactory
}
}
},
changed: false
};
}
onPress = () =>{
if (this.form == undefined){
console.log("UNDEFINED");
}else {
console.log(this.form.getValue());
}
}
componentDidMount() {
// var value = this.form.getValue();
// console.log(value);
}
shouldComponentUpdate(nextProps, nextState) {
return false;
}
render() {
return (
<View>
<Form
ref={(ref: any) => {
this.form = ref
}}
type={DocumentFormStruct}
value={this.state.value}
options={this.state.options}
/>
<Picker
selectedValue={this.state.language}
onValueChange={(itemValue, itemIndex) => this.setState({language: itemValue})}>
<Picker.Item label="PERMISO ESPECIAL"
value="PERMISO ESPECIAL"/>
<Picker.Item label="PERMISO HOJA CLARO" value="HOJA CLARO"/>
<Picker.Item label="COORDINAR CON PROPIETARIO"
value="COORDINA CON PROPIETARIO"/>
</Picker>
{this.state.language == 'HOJA CLARO'
?
<View>
<Text>Hello World!</Text>
</View>
:
null
}
<TouchableOpacity
onPress={this.onPress}
>
<Text> Touch Here </Text>
</TouchableOpacity>
</View>
)
}
}
I know that with shouldcomponentupdate, you can prevent unnecessary re renders. The thing is that this library has to use this ref property that when on state change of any other value the selected picture gets a null. Is there a way or workaround for this. If I use the shouldcomponentupdate then the pickers value wont change but at least I'm getting the current value of the image-picker.

An workaround would be to take advantage of onChange props of Form. (See docs)
<Form
ref={(ref: any) => {
this.form = ref
}}
type={DocumentFormStruct}
value={this.state.value}
options={this.state.options}
onChange={this.onChange}
/>
In your onChange method, save the value in local state.
onChange = (value) => {
this.setState({value});
}
Now, even if it reinitialized as you said, it will get the value from local state and will not become null.

Related

React Native jest testing: "Cannot read properties of undefined"

I am struggling to get my jest test going.
My Component:
//Login.js
import Dialog from "react-native-dialog";
import { View, TextInput } from "react-native";
export default class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
};
}
render() {
return (
<View>
<View>
<TextInput
testID="email"
value={this.state.email}
onChangeText={(text) => this.setState({ email: text })}
/>
</View>
<View>
<TextInput
testID="password"
value={this.state.password}
onChangeText={(text) => this.setState({ password: text })}
/>
</View>
<View>
<Dialog.Container>
<Dialog.Button label="hi" />
</Dialog.Container>
</View>
</View>
);
}
}
My Test file
//Login.test.js
import React from "react";
import { render, fireEvent } from "react-native-testing-library";
import Login from "./Login";
const mockedModule_dialog = jest.mock("react-native-dialog");
module.exports = mockedModule_dialog;
jest.mock("react-native-dialog", () => mockedModule_dialog);
describe("Login", () => {
describe("change text login", () => {
it("change text username and password", () => {
const { getByTestId } = render(<Login />);
// use fireEvent change value TextInput
fireEvent.changeText(getByTestId("email"), "admin");
fireEvent.changeText(getByTestId("password"), "admin#123");
// use toEqual check value TextInput
expect(getByTestId("email").props.value).toEqual("admin");
expect(getByTestId("password").props.value).toEqual(
"admin#123"
);
});
});
});
But every time I run yarn test, I am getting the following error Cannot read properties of undefined (Reading 'Container')
When I remove the <Dialog.Container> .... </Dialog.Container> then test passed.
What am I doing wrong? Mocking the library react-native-dialog is not done correctly?

Can I subscribe to this.props.navigation.state.params?

I am wonder if in screenA I have an object data = {} that will be changed dynamically, can I receive changes in screenB by just sending this props from screenA through this.props.navigation.navigate('screenB', {data})?
And in screenB to have a componentWillReceiveProps(nextProps) to get this changes through something like nextProps.navigation.state.param.data
Or there is a way to achieve this?
You can use onWillFocus of NavigationEvents, which fires whenever the screen is navigated to.
_willFocus = () => {
const { navigation } = this.props
const data = navigation.getParam('data', null)
if (data !== null) {
/* do something */
}
}
/* ... */
render () {
return (
<View>
<NavigationEvents onWillFocus={_willFocus()}
</View>
)
}
It is easy, just as you said: send some data navigation.navigate('screenB', { data }) and receive it in the screenB as navigation.state.params.data.
I agree with #FurkanO you probably show use Redux instead to control all the state of your app, but for simple stuff I think isn't necessary!
I made a simple snack demo to show you: snack.expo.io/#abranhe/stackoverflow-56671202
Here some code to follow up:
Home Screen
class HomeScreen extends Component {
state = {
colors: ['red', 'blue', 'green'],
};
render() {
return (
<View>
{this.state.colors.map(color => {
return <Text>{color}</Text>;
})}
<View>
<Text>Details Screen</Text>
<Button
title="Go to Details"
onPress={() => this.props.navigation.navigate('Details', { colors: this.state.colors })}
/>
</View>
</View>
);
}
}
Details Screen
class DetailsScreen extends Component {
state = {
colors: [],
};
componentWillMount() {
this.setState({ colors: this.props.navigation.state.params.colors });
}
render() {
return (
<View>
{this.state.colors.map(color => {
return <Text>{color}</Text>;
})}
<Text>Details Screen</Text>
</View>
);
}
}
Update
The question's author requested an update to add a setTimeout() to see the exact moment when the data is on the other screen, so it will look like this:
componentWillMount() {
setTimeout(() => {
this.setState({ colors: this.props.navigation.state.params.colors });
}, 3000);
}

Button not displaying fetch results to the component?

I am creating a currency converter app and it will retrieve currency value from the API and multiply with the text input for the result. Both the API result and Text input are stored in State and passing as props to the Component
import React from 'react';
import { StyleSheet, Text, View,TextInput,Button } from 'react-native';
import DisplayResult from './src/DisplayResult'
export default class App extends React.Component {
state = {
currency:'',
pokeList: '',
}
placeNameChangeHandler=(val)=>{
this.setState({currency:val});
}
// console.log(this.state.currency);
async findCurrency () {
try {
//Assign the promise unresolved first then get the data using the json method.
const pokemonApiCall = await fetch('https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}');
const pokemon = await pokemonApiCall.json();
this.setState({pokeList: pokemon['KWD_INR']});
// console.log(pokemon);
} catch(err) {
console.log("Error fetching data-----------", err);
};
<DisplayResult convert={this.state.pokeList} result={this.state.currency} />
}
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value = {this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button
title="Search"
onPress={this.findCurrency.bind(this)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
DisplayResult
const DisplayResult =(props)=>{
const {convert,result} = props
console.log(convert);
return (
<View>
<Text>{result*convert}</Text>
</View>
)
}
export default DisplayResult;
I am trying to pass the API result and text input to the display component and this will multiply the values and will give the result.
Now this is not functioning or giving result
why this is not showing and where it's going wrong?
In your findCurrency method you just "call" the DisplayResult without returning it, but I don't think this is the good method to display your result.
You can use your component directly within the render method by testing your state variables, like this :
findCurrency = async () => {
try {
const pokemonApiCall = await fetch(
"https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}"
);
const pokemon = await pokemonApiCall.json();
this.setState({ pokeList: pokemon["KWD_INR"] }); // You set your "pokeList" variable up
} catch (err) {
console.log("Error fetching data-----------", err);
}
}
Note that you remove the DisplayResult call here and the function became an arrowed function, then in your render method use the test to make your result appear only if pokeList isn't empty :
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value={this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button title="Search" onPress={this.findCurrency.bind(this)} />
{this.state.pokeList !== "" && (
<DisplayResult
convert={this.state.pokeList}
result={this.state.currency}
/>
)}
</View>
);
}
Then, you don't have to bind your function in the onPress method like this, JavaScript immediately calls the function if you do this, instead, use arrow functions, you can access this by doing so in your function AND the onPress method doesn't call it if you don't click on the button, you just have to specify which function to execute when clicked :
<Button title="Search" onPress={this.findCurrency} />
If you have parameters in your function, use an arrow function instead :
<Button title="Search" onPress={() => yourFunction(param)} />
This should do the trick.
Try writing your function like that :
const findCurrency = async() => {
// ...
};
and call it like that
<Button
title="Search"
onPress={() => this.findCurrency()}
/>
I personnaly never use .bind because I think this is very unclear.
try using conditional rendering,
if data fetched, then only render.
import React from 'react';
import { StyleSheet, Text, View,TextInput,Button } from 'react-native';
import DisplayResult from './src/DisplayResult'
export default class App extends React.Component {
state = {
currency: '',
pokeList: '',
}
placeNameChangeHandler=(val)=>{
this.setState({currency:val});
}
// console.log(this.state.currency);
this.findCurrency.bind(this);
async findCurrency () {
try {
//Assign the promise unresolved first then get the data using the json method.
const pokemonApiCall = await fetch('https://free.currconv.com/api/v7/convert?q=KWD_INR&compact=ultra&apiKey={my_api_Key}');
const pokemon = await pokemonApiCall.json();
this.setState({pokeList: pokemon['KWD_INR']});
// console.log(pokemon);
} catch(err) {
console.log("Error fetching data-----------", err);
};
}
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Currency"
value = {this.state.currency}
onChangeText={this.placeNameChangeHandler}
/>
<Button
title="Search"
onPress={this.findCurrency()}
/>
</View>
{
if(this.state.pokeList !== '' || this.state.currency !== '') ?
<DisplayResult convert={this.state.pokeList} result={this.state.currency} /> : <div></div>
}
);
}
}

React Component Props are receiving late. (Meteor JS)

I am working on a react-native and meteor js project.
My problem is that the props received from withTracker() function are only received in componentDidUpdate(prevProps) I don't get them in constructor or componentWillMount.
Another issue is when i pass props directly from parent to child. it receives them late due to which my component does not update
iconGroups prop comes from withTracker() method
and openSection props which i am using in this showGroupIcons()
is passed directly from parent to this component.
I want to open Accordian section that is passed to it via parent. but problem is in componentDidUpdate(prevProps) I am changing state due to which component re-renders.
openSection variable by default value is Zero. when props arrvies it value changes which i required But Accordian does not update.
Below is my code
import React, { Component } from 'react';
import Meteor, { withTracker } from 'react-native-meteor';
import {
View, Image, ScrollView, TouchableOpacity,
} from 'react-native';
import PopupDialog from 'react-native-popup-dialog';
import {Text, Icon, Input, Item, List,} from 'native-base';
import Accordion from 'react-native-collapsible/Accordion';
import { Col, Row, Grid } from 'react-native-easy-grid';
import styles from './styles';
import CONFIG from '../../config/constant';
import {MO} from "../../index";
const staticUrl = '../../assets/img/icons/';
class IconPickerComponent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
itemName: 'apple1',
activeSections: 0,
showAccordian: true,
accordianData: []
};
}
componentDidUpdate(prevProps) {
if(prevProps.iconGroups !== this.props.iconGroups) {
let images = this.props.iconGroups.map(icon => icon.images);
let flatten = [].concat.apply([], images).map(img => { return {name: img, icon: CONFIG.ICON_URL+img+'.png'} })
this.setState({ filteredItems: flatten, dataSource: flatten, accordianData: this.props.iconGroups });
}
}
componentDidMount() {
this.props.onRef(this);
}
componentWillUnmount() {
this.props.onRef(null);
}
method() {
// this.setState(...this.state,{
// searchText: ''
// })
this.iconPicker.show(); // show icon picker
}
onSearchChange(text) {
this.setState({
showAccordian: !(text.length > 0)
});
const searchText = text.toLowerCase();
const filteredItems = this.state.dataSource.filter((item) => {
const itemText = item.name.toLowerCase();
return itemText.indexOf(searchText) !== -1;
});
this.setState({ filteredItems });
}
onIconSelect(item) {
this.setState({
itemName: item,
});
this.iconPicker.dismiss();
if (this.props.onIconChanged) {
this.props.onIconChanged(item);
}
}
_renderSectionTitle = section => {
return (
<View style={styles.content}>
<Text></Text>
</View>
);
};
_renderHeader = section => {
return (
<View style={styles.accordHeader}>
<Text style={{color: 'white'}}>{this.state.showAccordian} - {section.group}</Text>
<Text>
<Icon style={styles.downArrow} name="ios-arrow-down" />
</Text>
</View>
);
};
_renderContent = section => {
return (
<View style={styles.accordContent}>
{
section.images.map((img, key) => (
<TouchableOpacity onPress={() => this.onIconSelect(img)} key={key}>
<View style={styles.iconsGrid}>
<Image style={styles.image} source={{uri: CONFIG.ICON_URL+ img + '.png'}}/>
</View>
</TouchableOpacity>
))
}
</View>
);
};
_updateSections = activeSections => {
this.setState({ activeSections });
};
hasGroupIcons() {
return this.props.iconGroups.length > 0;
};
showGroupIcons() {
if(this.state.showAccordian){
let openSection;
if(!!this.props.openSection) {
let groupIndex = this.state.accordianData.findIndex(icon => icon.group === this.props.openSection);
if(groupIndex !== -1) {
openSection = groupIndex;
} else {
openSection = 0;
}
} else {
openSection = 0;
}
return(<Accordion
sections={this.state.accordianData}
activeSections={this.state.activeSections}
renderSectionTitle={this._renderSectionTitle}
renderHeader={this._renderHeader}
renderContent={this._renderContent}
onChange={this._updateSections}
initiallyActiveSection={openSection} />);
} else {
return(<View style={{flexWrap: 'wrap', flexDirection: 'row'}}>
{
this.state.filteredItems.map((item, key) => (
<TouchableOpacity onPress={() => this.onIconSelect(item.name)} key={key}>
<View style={styles.iconsGrid}>
<Image style={styles.image} source={{uri: item.icon}}/>
</View>
</TouchableOpacity>
))
}
</View>)
}
};
render() {
return (
<PopupDialog
overlayOpacity={0.8}
overlayBackgroundColor="#414141"
dialogStyle={styles.dialogBox}
containerStyle={styles.dialogContainer}
ref={(popupDialog) => { this.iconPicker = popupDialog; }}
>
<ScrollView>
<View style={styles.dialogInner}>
<Item searchBar rounded style={styles.searchbar}>
<Icon style={styles.searchIcon} name="search" />
<Input onChangeText={this.onSearchChange.bind(this)} style={styles.inputSearch} placeholder="Search" />
</Item>
{
this.hasGroupIcons() && this.showGroupIcons()
}
</View>
</ScrollView>
</PopupDialog>
);
}
}
export default withTracker(params => {
MO.subscribe('ipSubsId3', 'IconGroups');
return {
iconGroups: MO.collection('IconGroups', 'ipSubsId3').find({}),
};
})(IconPickerComponent);
I am new to react. I am assuming when props change component re-renders.
Use this life cycle method
static getDerivedStateFromProps(prevProps, prevState) {
if(prevProps.iconGroups !== this.props.iconGroups) {
let images = this.props.iconGroups.map(icon => icon.images);
let flatten = [].concat.apply([], images).map(img => { return {name: img, icon: CONFIG.ICON_URL+img+'.png'} })
this.setState({ filteredItems: flatten, dataSource: flatten, accordianData: this.props.iconGroups });
}
}
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
Read more about this lifecycle method here
I have fixed this issue. Actually my concepts were not right. I thought props are first received in constructor and componentWillMount. But I get all props in render() and everything works fine i dont have to use any lifecycle method to use props now

React-Redux FlatList Updation

I have an AuditionsList component which displays a FlatList. In a different reducer I'm changing Settings called location and roleType. Depending on these new settings I want to refresh the AuditionList.
Here is my code:
import React from 'react';
import { Text, View, FlatList, ActivityIndicator } from 'react-native';
import { connect } from 'react-redux';
import AuditionItem from './AuditionItem';
import Auditions from './../data/Auditions';
class AuditionsList extends React.Component {
constructor(props) {
super(props);
this.state = { isLoading: true, data: [], refresh: false }
}
componentDidMount() {
this._refreshData();
}
_onRefresh() {
this.setState({ isLoading: true }, this._refreshData() );
}
_refreshData = () => {
Auditions.fetchAuditions(this.props.productionType, this.props.location, this.props.roleType).then(auditions => {
this.setState({ isLoading: false, data: this._addKeysToAuditions(auditions) });
});
}
_addKeysToAuditions = auditions => {
return auditions.map(audition => {
return Object.assign(audition, { key: audition.Role});
});
}
_renderItem = ({ item }) => {
return (
<AuditionItem
auditionId={item.objectId}
role={item.Role}
project={item.Project.Name}
productionType={item.Project.ProductionType.Type}
auditionDate={JSON.stringify(item.Date.iso)}
productionHouse={item.Project.ProductionHouse.Name}
/>
);
}
render() {
console.log("Here...");
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList onRefresh={() => this._onRefresh()} refreshing={this.state.isLoading} data={this.state.data} renderItem={this._renderItem} />
</View>
);
}
}
const mapStateToProps = state => {
return {
location: state.settings.location,
roleType: state.settings.roleType,
};
}
export default connect(mapStateToProps)(AuditionsList);
I need to call the _onRefresh() or _refreshData() function AFTER the callback to mapStateToProps (which runs successfully) but BEFORE the FlatList re-renders (which also happens successfully, but with old data). So where do I call the _onRefresh() or _refreshData() functions? Putting them in render() causes an infinite loop.
Have you tried using the ComponentDidUpdate react lifecycle method? This will fire after you receive new props, and you could make a call to this._refreshData there.
https://reactjs.org/docs/react-component.html#componentdidupdate

Resources