How do I reset the selected value of a Picker in React Native - react-native

I have a custom component class that displays a select list using React Native Picker in an app. This component is imported by one of the screens that I have created using createBottomTabNavigator() of react navigation.
The screen that uses this class has a form. The user fills the form and on submission the user is directed to another screen. What I am struggling at is that when the user returns to the same form screen instead of Picker list been resetted to its default value I am seeing the list with the previous selected value. How do I reset it when the form is submitted from the class
the Picker list component is as follows
import React from "react";
import {View, Text, StyleSheet, Picker} from "react-native";
import PropTypes from "prop-types";
import {connect} from "react-redux";
import {savePostJobType} from "../reducer.js";
//import store from "../store.js";
class SelectList extends React.Component {
static propTypes = {
selectionMesg : PropTypes.string,
data : PropTypes.array,
}
state = {
pickerColorFlag : "#C7C7CD",
val : "",
}
componentDidMount() {
console.log("List Mounted")
}
render(){
return(
<Picker
selectedValue={this.state.val}
style = {styles.picker}
onValueChange={(itemValue, itemIndex) => {
//this.removeErrorMsg()
if(itemValue !== "0")
{
/*POTENTIAL BUG:The this.state.val shows the value of previous selected value in here*/
this.setState({
val: itemValue,
pickerColorFlag : "black"})
/*SAVE THE SELECTED VALUE IN STORE
**THIS IS WHAT YOU NEED TO EDIT IF REUSING THIS COMPONENT** */
this.props.saveJobType(itemValue)
}
else
{
this.setState({pickerColorFlag : "#C7C7CD"})
}
}
}>
<Picker.Item label={this.props.selectionMesg} value="0" color = "#C7C7CD" />
{
this.props.data.map((value, key) =>(
<Picker.Item key = {key} label={value} value= {value} />
))
}
</Picker>
)
}
}
const styles = StyleSheet.create({
container : {
flex : 1,
justifyContent : "center",
alignItems : "center",
backgroundColor : "blue",
},
picker : {
width: "60%",
marginTop : "5%",
},
})
// const test = (state) => ({
// selection : state.postJobData.type
// })
export default connect(null, {saveJobType : savePostJobType})(SelectList)
I am importing this component to a screen class as follows
import SelectList from "./customComponent/selectList.js";
class ScrrenTab extends React.Component {
render(){
<SelectList
selectionMesg = "Select Job Type"
data = {["electrician", "plumber", "carpenter", "painter"]}/>
<Button title = "Submit"/>
}
}

I was trying to update the list component from the screen component but all I had to do was to send the navigation prop to the list component from the screen component and add a listener to it and make changes when the list was in focus
in screen component
<SelectList
navigation={this.props.navigation}
selectionMesg = "Select Job Type"
data = {["electrician", "plumber", "carpenter", "painter"]}/>
and in the SelectList i had to just add these methods
componentDidMount() {
this._unsubscribe = this.props.navigation.addListener('focus', () => {
console.log("List Mounted")
this.setState({val : ""})
});
}
componentWillUnmount() {
this._unsubscribe();
}

Related

React call function and setstate when go back to a previous screen

I'm new in React's world
I have 2 screens : Stock and Barcode.
In Stock, i navigate to Barcode's screen.
When i scan a barcode, i go back to the previous screen I would like to set the input text with the barcode and call a function. In my example joinData();
The problem is to set the input text and call a function.
I tried examples and answers but i don't find or don't understand how to to that.
I tried something in componentDidUpdate() but it fails
Invariant Violation:Maximum update depth exceeded
Stock.js
import React, {useState} from "react";
import { ScrollView, TouchableWithoutFeedback, Dimensions, StyleSheet, FlatList, View, Alert, TouchableOpacity, TextInput } from 'react-native';
//galio
import { Block, Text, theme } from "galio-framework";
import { Button, Icon, Input } from "../components/";
export default class Stock extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.array = [];
this.state = {
arrayHolder: [],
Input_ITMREF: ''
};
}
// I tried this but it fails
componentDidUpdate() {
if (this.props.navigation.getParam('itmref') != 'undefined') {
this.setState({ Input_ITMREF: this.props.navigation.getParam('itmref')});
}
}
componentDidMount() {
this.setState({ arrayHolder: [...this.array] }) // RafraƮchit la liste
}
joinData = () => {
vxml = this.state.Input_ITMREF+" I do something";
}
Render() {
return (
<Block flex>
<Block row space="evenly">
<Block center>
<Input
placeholder='Code article'
onChangeText={data => this.setState({ Input_ITMREF: data })}
ref={this.myRef}
/>
</Block>
</Block>
<Block center>
<Button style={styles.button} onPress={() => this.props.navigation.navigate('Barcode')}>Barcode</Button>
<Text style={{ margin: 10 }}>Post: {this.props.navigation.getParam('itmref')}</Text>
</Block>
</Block>
);
}
}
And Barcode.js
import React, {} from 'react';
import { ScrollView, TouchableWithoutFeedback, Dimensions, StyleSheet, FlatList, View, Alert, TouchableOpacity, TextInput } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
import { Button } from "../components/";
export default class Barcode extends React.Component {
static navigationOptions = {
header: null //hide the header bar
};
handleBarCodeScanned = ({ type, data }) => {
this.props.navigation.navigate("Stock", {
itmref: data
});
};
render() {
return (
<BarCodeScanner
onBarCodeScanned={this.handleBarCodeScanned}
style={styles.barcodeScanner}
/>
);
}
}
You can pass a state handler function as prop to Barcode screen and use that to set value for textInput in state.
in Stock(in state)
state = {
inputValue: ''
}
....
const setInputTextValue= (newValue) => {
this.setState({
inputValue: newValue
})
you pass this function as prop to Barcode scene and call it whenever you wanna set a new value(considering Stock scene is still mounted).
UPDATE: What is the proper way to update a previous StackNavigator screen?
also another solution i just saw: Updating State of Another Screen in React Navigation
You need to use WillFocus method(Included in react-navigation) when you comeback from Barcodepage to stockPage
componentDidMount(){
console.log("willFocus runs") initial start
const {navigation} = this.props;
navigation.addListener ('willFocus', async () =>{
console.log("willFocus runs") // calling it here to make sure it is logged at every time screen is focused after initial start
});
}
For More Information read this document
https://reactnavigation.org/docs/function-after-focusing-screen/

Change one button color when onPress in list

So i want to make a list with a heart button
when i tap on one heart button.
i want only one button changed color.
and save the current button color state.
My Problem is
When i tap on one heart button, all button changed color.
Screenshot:
this is my code
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
} from 'react-native';
import { ListItem } from 'react-native-elements'
import { Icon } from 'react-native-elements'
import Data from './src/data/sampledata.json';
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = {
buttonColor: '#979797', // default button color goes here, grey is default
};
}
onButtonPress = () => {
if(this.state.buttonColor=='#ff002b')
{
this.setState({ buttonColor: '#979797' }) // grey
}
else {
this.setState({ buttonColor: '#ff002b' }) // red
}
}
render() {
return (
<View style={styles.container}>
{
Data.map((item, i) => (
<ListItem
key={item.index}
title={item.dataItem}
rightIcon={
<Icon
raised
name='heart'
type='font-awesome'
color={this.state.buttonColor}
onPress={this.onButtonPress}
/>
}
/>
))
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
});
One way to achieve this is as follows:
What you can do is keep track of the selected items in an array. Using getUpdatedSelectedItemsArray function you can get an array that contains all the selected item like so
const getUpdatedSelectedItemsArray = (selectedItems, id) => {
const forDeletion = [id]; //can also be used for multiple ids
if (selectedItems.includes(id)) {
//if id already exists delete it
return selectedItems.filter(item => !forDeletion.includes(item));
}
//otherwise push it
selectedItems.push(id);
return selectedItems;
};
you can call the above function from this.onButtonPress(item) which takes item as an argument.
//Creating state
this.state = {
buttonColor: '#979797',
selectedItems: []
};
Calling getUpdatedSelectedItemsArray() will give you updated array which you can set in your selectedItems state.
Lastly you will have check while giving the color to icon(item), that if item.id exists in this.state.selectedItems then show red otherwise grey, like so
Data.map((item, i) => (
<ListItem
key={item.index}
title={item.dataItem}
rightIcon={
<Icon
raised
name='heart'
type='font-awesome'
color={this.checkIfIDExists(item.id) ? 'red' : 'grey'}
onPress={this.onButtonPress}
/>
}
/>
))
P.S this.checkIfIDExists(item.id) is a function the returns true if id exists in selectedItems array.

React-Native, ActionSheetIOS display instead of Picker

I would like to use the ActionSheetIOS on iOS, instead of the native picker wheel.
My app crashes out, how can I display my component?
Here is my Picker component:
// Picker.ios.js
import React, { Component } from "react";
import { StyleSheet, ActionSheetIOS, View } from "react-native";
const PickerList = props => {
const { label, options, selectedValue, name, onChange, identifier } = props;
return ActionSheetIOS.showActionSheetWithOptions(
{
options: options,
cancelButtonIndex: 1,
destructiveButtonIndex: 2
},
optionIndex => {
console.log('clicked')
}
);
};
export default PickerList;
I'm using conditional rendering to display my pickers, and a platform specific import:
import Picker from "./common/Picker";
{setYear
? <Picker
selectedValue={setGroup}
label="Year group"
onChange={this.onGroupChange}
options={
categories.find(category => {
return category.id == setYear;
}).options
}
/>
: null}
ActionSheetIOS only shows the options, you have to have some view component to replace the picker in the view. I used TouchableOpacity ja ActionSheetIOS to replace Picker on IOS like this:
<TouchableOpacity onPress={this.onSelectCategory.bind(this)}>
<Text style={styles.textInputStyle}>
{this.state.category}
</Text>
</TouchableOpacity>
onSelectCategory() {
ActionSheetIOS.showActionSheetWithOptions(
{ options: FEEDBACK_CATEGORIES},
(buttonIndex) => this.setState({ category: FEEDBACK_CATEGORIES[buttonIndex] }));
}
I use this.state.category to show my selection in TouchableOpacity
When user press the TouchableOpacity the ActionSheetIOS is shown
When user select option the callback function is called and I update the
selected index to the this.state.category

React Native Modal crashes app on load

I'm trying to create a small request box that pops up from the bottom of the screen to let a user confirm a reservation. A mockup of what that could look like is below. After some research, a modal seemed like the way to go.
The Problem
Starting the app with a <Modal /> freezes the app on the home screen. Loading the modal when needed (in the code below) crashes the app with no error.
Code
Component rendering the <Model /> (called as <RequestTeacherPopup />)
// #flow
import React from 'react'
import { ScrollView, View } from 'react-native'
...
export default class RequestScreen extends React.Component<Props, {nextSeminar: DateTime, teachers: []}> {
constructor (props: Props) {
super(props)
...
this.state = {
nextSeminar: nextSeminar,
teachers: null,
requestVisibility: false,
requestedTeacher: null
}
}
...
render () {
var teacherList = []
if (this.state.teachers) {
for (let teacherItem of this.state.teachers) {
if (teacherItem.key !== this.props.profile.defaultSeminar) {
let teacher: Teacher = teacherItem.value
let teacherPic: {uri: string} = ('picture' in teacher) ? { uri: teacher.picture } : null
teacherList.push(
<ListItem
roundAvatar
avatar={teacherPic}
onPressRightIcon={
function () {
this.setState({
requestVisibility: true,
requestedTeacher: teacher
})
}.bind(this)
}
key={teacherItem.key}
title={`${teacher.firstName} ${teacher.lastName}`}
subtitle={`${teacher.taughtCourses} | Room ${teacher.room}`} />
)
}
}
}
return (
<View style={styles.mainContainer}>
<ScrollView>
<List>
{teacherList}
</List>
{
(this.state.requestVisibility)
? (<RequestTeacherPopup
requestedTeacher={this.state.requestedTeacher}
onFinish={() => this.setState({ requestVisibility: false })} />)
: null
}
</ScrollView>
</View>
)
}
}
RequestTeacherPopup Component
// #flow
import React, { Component } from 'react'
import { View, Image, Text } from 'react-native'
import { Divider, Overlay } from 'react-native-elements'
import Modal from 'react-native-modal'
...
class RequestTeacherPopup extends Component<{isVisible: boolean, requestedTeacher: Teacher, onFinish: () => void}> {
state = {
markedDates: this.getDaysInMonth(DateTime.local().month, DateTime.local().year, DISABLED_DAYS),
calVisiblity: false,
requestedDate: null,
requestedDay: null
}
...
render () {
return (
<Modal
style={Styles.bottomModal}
isVisible
onSwipe={this.handleRequest}
swipeDirection='up' >
...
</Modal>
)
}
}
The problem still occurs when the <Modal /> is not passed any children, and I have tried both the built in modal and the react-native-modal package from npm.
Thanks for any help!
Hello while showing or closing any modal,
you have use setTimeout(()=>{},miliseconds)
modal is an external window of the screen that covers the whole screen.

React StackNavigator with lifted state howto

I work with StackNavigator for navigation between a couple of screens. In these screens i will collect user information. I would like this information to be stored in one state so each view will have the same information.
I got this working but i think it is not the correct or ideal because i keep track of 2 states. One in the starting view and one in the current view.
Hence my question is, is it possible to work with only one state?
Here is my code:
My root class which creates the StackNavigator:
//#flow
import React from 'react';
import {StackNavigator} from 'react-navigation';
import Cart from './Cart';
import DeliveryAddress from './DeliveryAddress';
import Queue from './Queue';
export type State = {
text: string,
user: string,
onChange: Function
}
const Screens = StackNavigator({
Cart: {
screen: Cart
},
Queue: {
screen: Queue
},
DeliveryAddress: {
screen: DeliveryAddress
}
});
const AppNavigation = () => (<Screens/>);
export default class Root extends React.Component < Object,
State > {
render() {
return <AppNavigation/>;
}
}
Here my first view the Cart:
// #flow
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Text, View, Button} from 'react-native';
import DeliveryAddress from './DeliveryAddress';
import type {State}
from './Root';
export default class Cart extends Component < Object,
State > {
static navigationOptions = {
title: 'Welcome'
};
handleChange: Function;
getState: Function;
constructor(props : Object) {
super(props);
console.log("Construction Cart with state");
console.log(this);
console.log("props");
console.log(this.props);
this.handleChange = this.handleChange.bind(this);
this.state = {
user: "Lucy",
text: "Vul in",
onChange: this.handleChange,
getState: this.getState
};
}
handleChange(data : Object) {
this.setState(...data);
}
render() {
const {navigate} = this.props.navigation;
return (<View>
<Text>This is your cart.</Text>
<Button onPress={() => navigate('DeliveryAddress', this.state)} title="Go to delivery address"/>
</View>);
}
}
and here is my second view:
// #flow
import React, {Component} from 'react';
import {Text, View, Button, TextInput} from 'react-native';
import type {NavigationScreenProp}
from 'react-navigation/src/TypeDefinition';
import type {State}
from './Root';
type NavigationType = {
navigation: NavigationScreenProp < NavigationState >
}
type NavigationState = {
params: {
user: "bar"
}
}
export default class DeliveryAddress extends Component < any,
State > {
// Nav options can be defined as a function of the screen's props:
static navigationOptions = ({navigation} : NavigationType) => ({title: `Delivery address of ${navigation.state.params.user}`});
handleChange: Function;
constructor(props : Object) {
super(props);
this.state = props.navigation.state.params;
console.log(props.navigation.state);
this.handleChange = this.handleChange.bind(this);
}
handleChange(data : Object) {
this.setState(data);
this.state.onChange(data);
}
render() {
console.log("welkom in delivery address");
// The screen's current route is passed in to `props.navigation.state`:
const {navigate} = this.props.navigation;
return (<View>
<Text>User name: {this.state.user}</Text>
<TextInput style={{
height: 40,
borderColor: 'gray',
borderWidth: 1
}} onChangeText={(text) => this.handleChange({text})} value={this.state.text}/>
<Button onPress={() => navigate('Queue', this.state)} title={this.state.text}/>
</View>);
}
}
The thing is that i need to set both states like this:
handleChange(data : Object) {
this.setState(data);
this.state.onChange(data);
}
Is this the best way of doing this?
it seems that there is a nice way to solve this: Using Redux with a Provider.
with the provider it is possible do add information to a 'context':
import {Provider, connect} from "react-redux";
return (<Provider store={store}>
<AppNavigation/>
</Provider>);
the provider will add the store to the context. Any child (and grandchild) can use this store like this:
console.log("this.context");
console.log(this.context);
const {store} = this.context;
above will only work if you add this into the component:
Cart.contextTypes = {
store: PropTypes.object
}
here is more info:
https://egghead.io/lessons/react-redux-passing-the-store-down-implicitly-via-context