How to properly update a react native swiper component when state changes? - reactjs

I have a react native component that uses react-native-swiper module. One of the slides in the swiper contains text which is set in the component's state. In the component I also have a modal with form that changes the state's text when user tries to save the input data from the modal.
The question is: in my current implementation, every time I saves a new data, the swiper gets dragged to the last slide, and re-render the slides (the process is laggy). So I wonder what's the best way to update the slides more smoothly?
Below is my component:
'use strict';
import React from 'react';
import {
Dimensions,
StyleSheet,
View,
Text,
ScrollView,
AlertIOS,
AsyncStorage
} from 'react-native';
import { StackNavigator } from 'react-navigation';
import Swiper from 'react-native-swiper';
import Button from 'react-native-button';
import { saveName, getName } from '../Utils/dataService';
import { showAlert } from '../Utils/alert';
import HeaderSection from './HeaderSection';
import { styles } from '../Styles/Styles';
import { renderPagination } from './renderPagination';
class MainView extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
currIndex: 0
};
}
componentDidMount() {
getName(val => this.setState({'name': val}));
}
showInputModal() {
AlertIOS.prompt(
'Enter New Doctor Name', null,
[
{
text: 'Save',
onPress: name => saveName(name, val => this.setState({'name': val}))
},
{ text: 'Cancel', style: 'cancel' }
]
);
}
render() {
return (
<View style={{flex: 1}}>
<Swiper ref='swiper' onIndexChanged={(index) => this.setState({'currIndex': index})}>
<View style={styles.slide}>
<Text style={styles.text}>Hello {this.state.name}</Text>
</View>
</Swiper>
<Button onPress={this.showInputModal.bind(this)}>
Customize
</Button>
</View>
);
}
}
export default MainView;

I had the similar problem. Then I tried rendering the Swiper (in your case) from the state and it optimizes the performance. I hope it will solve your problem too.
Just replace your class MainView with this one:
class MainView extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
currIndex: 0,
swiper: this.renderSwpier
};
}
componentDidMount() {
getName(val => this.setState({'name': val}));
}
renderSwpier(){
return(
<Swiper ref='swiper' onIndexChanged={(index) => this.setState({'currIndex': index})}>
<View style={styles.slide}>
<Text style={styles.text}>Hello {this.state.name}</Text>
</View>
</Swiper>
)
}
showInputModal() {
AlertIOS.prompt(
'Enter New Doctor Name', null,
[
{
text: 'Save',
onPress: name => saveName(name, val => this.setState({'name': val}))
},
{ text: 'Cancel', style: 'cancel' }
]
);
}
render() {
return (
<View style={{flex: 1}}>
{this.state.swiper.call(this)}
<Button onPress={this.showInputModal.bind(this)}>
Customize
</Button>
</View>
);
}
}

Related

set Multiple state Id for custom component in React Native

I have implemented custom inputBox component. So When I am using this component first time then it is working fine and when I am using multiple time in one page then data is prepopulate to next component.
Custom component:
import React, { createRef } from 'react';
import {
View,
TextInput,
Alert,
Text,
StyleSheet
} from "react-native";
class BoxInput extends React.Component {
constructor(props) {
super(props)
this.state = {
digit1: '',
digit2: '',
digit3: '',
...props
}
this.digit1Ref = createRef()
this.digit2Ref = createRef()
this.digit3Ref = createRef()
}
componentDidMount() {
this.digit1Ref.current.focus()
}
componentDidUpdate(prevProps) {
if (this.state.digit1 && this.state.digit2 &&
this.state.digit3
}
saveText(text, key) {
this.setState({ ...this.state, [key]: text }, () => {
if (text) {
key == 'digit1' ? this.digit2Ref.current.focus() : null
key == 'digit2' ? this.digit3Ref.current.focus() : null
key == 'digit3'
}
const boxInputValue = this.state.digit1 + this.state.digit2 + this.state.digit3
this.props.onBoxInput(boxInputValue)
});
}
render() {
return (
<>
<TextInput maxLength={1} keyboardType={'numeric'} ref={this.digit1Ref} style={styles.boxStyle} value={this.state.digit1} onChangeText={(text) => this.saveText(text, 'digit1')} />
<TextInput maxLength={1} keyboardType={'numeric'} ref={this.digit2Ref} style={styles.boxStyle} value={this.state.digit2} onChangeText={(text) => this.saveText(text, 'digit2')} />
<TextInput maxLength={1} keyboardType={'numeric'} ref={this.digit3Ref} style={styles.boxStyle} value={this.state.digit3} onChangeText={(text) => this.saveText(text, 'digit3')} />
</>
)
}
}
const styles = StyleSheet.create({
boxStyle: {
marginTop: 20,
height: 57,
width: 50,
borderRadius: 10,
borderWidth: 1,
borderColor: '#F1F5F9',
backgroundColor: '#F1F5F9',
fontSize: 20,
lineHeight: 40,
paddingHorizontal: 15,
textAlign: 'center'
}
})
export default BoxInput;
import React, { createRef } from 'react';
import styles from './style';
import {
View,
TextInput,
Alert
} from "react-native";
import { connect } from "react-redux";
import * as Animatable from 'react-native-animatable';
import BoxInput from "../../../../md-components/atoms/boxinput"
class MPINScreen extends React.Component {
constructor(props) {
super(props)
this.state = {
confirmMpinEnable: true,
...props
}
this.codes = [{
value: '+91',
}]
}
componentDidUpdate(prevProps) {
if (this.state.mpinValue.split("").length == 3 &&
prevProps.success_msg != this.props.success_msg && this.props.success_msg == 'verified') {
NavigationService.navigate(this.props.navigation, 'MPINVerifyOnboarding')
}
}
handleSubmit = () => {
if (this.state.mpinValue != this.state.confirmMpinValue) {
Alert.alert(
"Error",
"MPIN is not machted",
[
{ text: "OK" }
],
{ cancelable: false }
);
} else {
this.props.verifyMpin({
"mpin": this.state.mpinValue,
phoneNumber: this.props.mobileNumber
})
}
}
mpinConfirmation = () => {
if (this.state.mpinValue.split("").length != 6) {
Alert.alert(
"Error",
"Please insert 6 digit mpin",
[
{ text: "OK" }
],
{ cancelable: false }
);
}else{
this.setState({
confirmMpinEnable: false,
});
}
}
mpinText = (args) => {
this.setState({
mpinValue: args,
});
}
confirmMpinText = (args) => {
this.setState({
confirmMpinValue: args,
});
}
render() {
return (
<>
<HeaderComponent backgroundColor="#E5E5E5" showLeftIcon={true} showRightIcon={false} />
<View style={styles.container}>
<Text style={[styles.textInfo, styles.textTitle]}>We are almost there!</Text>
<View style={styles.imageWrapper}>
<Animatable.View animation="slideInDown" iterationCount={1} style={styles.centerIconWrap}>
<Image style={styles.centerIcon} source={mpin_card} />
</Animatable.View>
</View>
{this.state.confirmMpinEnable ?
<Text style={[styles.textInfo]}>Setup your MPIN</Text> : <Text style={[styles.textInfo]}>Confirm your MPIN</Text>
}
{this.state.confirmMpinEnable ?
<View style={styles.rowWrap}>
<BoxInput id="catFood1" onBoxInput={this.mpinText} />
</View>:
<View style={styles.rowWrap}>
<BoxInput id="catFood2" onBoxInput={this.confirmMpinText} />
</View>
}
<View style={styles.marginBottom}>
<Text style={[styles.mpinNote]}>M-PIN is a short 6-digit PIN that you have to set for</Text>
<Text style={[styles.mpinNote]}>our mandatory Two-Factor Authentication</Text>
</View>
<View style={styles.bottomBtnSyle}>
<View style={styles.multipleBtnStyle}>
<Button onPress={this.handleBack}>{"Back"}</Button>
</View>
{this.state.confirmMpinEnable ?
<View style={styles.multipleBtnStyle}>
<Button onPress={this.mpinConfirmation} >{"Confirm"}</Button>
</View> :
<View style={styles.multipleBtnStyle}>
<Button onPress={this.handleSubmit} >{"Save & Continue"}</Button>
</View>
}
</View>
</View>
</>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MPINScreen);
when I am click on confirm button then hide and display . But in second component data is prepopulating which i was inserted.
in this screen shot data is prepopulate but i want this empty, Because user has to insert again. but it is taking same value from previous state. how we can use multiple time same component in one page.
General idea:
Create a property in MPINScreen state that is changing (incrementing) every attempt (you can call it attempt) and pass it as prop to BoxInput.
In BoxInput create a reset function (that will clean the values of the text inputs and focus the first input). On componentDidUpdate check if attempt prop changed. If true - save the new value in BoxInput state and call "reset".

FlatList not updated if data updated

I have a FlatList with data fetched from an API. There's a button on the screen that fetches data which is changed and sets the state, but the flat list doesn't refresh. I tried setting the extraData as per docs, but it didn't help. Here are the full code and snack.
If you click the Toggle List button, the alert correctly shows the new data, but the list isn't updated.
import React, {useState} from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, Button } from 'react-native';
import Constants from 'expo-constants';
const DATA2 = [
{
id: 0,
title: 'D2-0'
},
{
id: 1,
title: 'D2-1'
},
{
id: 2,
title: 'D2-2'
},
];
const DATA1 = [
{
id: 0,
title: 'D1-0'
},
{
id: 1,
title: 'D1-1'
},
{
id: 2,
title: 'D1-2'
},
];
export default function App(props) {
const [data, setData]=useState(DATA1);
const [dataUsed, setDataUsed]=useState(1);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => <MyComponent data={item} /> }
keyExtractor={item => item.id}
extraData={data}
/>
<Button title="Toggle Data" onPress={() => {
let newData = dataUsed === 1 ? DATA2 : DATA1;
setDataUsed(dataUsed === 1 ? 2: 1);
alert(JSON.stringify(newData));
setData(newData);
}} />
</SafeAreaView>
);
}
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {data: props.data};
}
render() {
return <Text>{this.state.data.title}</Text>;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: Constants.statusBarHeight,
padding: 50
}
});
<div data-snack-id="SkDYPf4wH" data-snack-platform="web" data-snack-preview="true" data-snack-theme="light" style="overflow:hidden;background:#fafafa;border:1px solid rgba(0,0,0,.08);border-radius:4px;height:505px;width:100%"></div>
<script async src="https://snack.expo.io/embed.js"></script>
I think you missed the reflection of the state.
Once you set the state, it could be reflected next time.
Do I think you need to use the Hook.
Please try to use it.
import React, {useState, useEffect} from 'react';
... ... ...
export default function App(props) {
const [data, setData]=useState(DATA1);
const [dataUsed, setDataUsed]=useState(1);
useEffect(()=>{
let newData = dataUsed === 1 ? DATA2 : DATA1;
setData(newData);
},[setData, dataUsed]);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => <MyComponent data={item} /> }
keyExtractor={item => item.id}
extraData={data}
/>
<Button title="Toggle Data" onPress={() => {
setDataUsed(dataUsed === 1 ? 2: 1);
alert(JSON.stringify(newData));
}} />
</SafeAreaView>
);
}
And for the component.
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {data: props.data};
}
componentDidUpdate(prevProps){
if( prevProps.data !== this.props.data ){
this.setData();
}
}
setData = ()=>{
this.setState({
data: this.props.data,
});
}
Render () {
return <Text>{this.state.data.title}</Text>;
}
Change MyComponent code like this
class MyComponent extends React.Component {
render() {
return <Text>{this.props.data.title}</Text>;
}
}
Your constructor code is actually useless.

React Native setState not causing rendering

I'm a complete beginner at react native and now I'm stuck with an update problem. I'm using react-native-paper and typescript.
In my app, I want to press a button and then the text field should change its text.
The problem is somehow at the button, or the called function because in the console log its always "before: true after:true" or "before: false after:false",
but what I expected is "before: true after: false" or vice-versa
I've also got a second Text View which is not shown at all.
Maybe someone can tell me what I am doing wrong?
My index.js
import * as React from 'react';
import { AppRegistry } from 'react-native';
import { Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
export default function Main() {
return (
<PaperProvider>
<App />
</PaperProvider>
);
}
AppRegistry.registerComponent('main', () => Main);
My MyNavigation.tsx (which contains currently my whole app).
import * as React from 'react';
import { BottomNavigation, Text, Avatar, Button, Card, Title, Paragraph, Banner } from 'react-native-paper';
import { View, Image, WebView } from 'react-native';
export default class MyNavi extends React.Component {
constructor(props, context) {
super(props, context);
this.setUnConnected = this.setUnConnected.bind(this);
}
state = {
index: 0,
routes: [
{ key: 'viewcamera', title: 'View', icon: 'remove-red-eye' },
{ key: 'viewsettings', title: 'Settings', icon: 'settings' },
{ key: 'viewhelp', title: 'How-To', icon: 'help' },
],
visible: true,
connected: false,
};
_handleIndexChange = index => { this.setState({ index }); }
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
ViewRoute = () =>
<View style={{ flex: 1, marginTop: 40 }}>
{/* --------- This text field does not get updated -------------*/}
<Text>connected: {this.state.connected ? 'true' : 'false'}</Text>
{/* --------- This text field is not shown at all ------------*/}
<Text>
{this.state.connected}
</Text>
<Button icon="camera" mode="contained" onPress={this.setUnConnected}>
Press me
</Button>
<View style={{ height: 400, width: 400 }}>
<WebView
source={{ uri: 'https://stackoverflow.com/' }}
style={{ marginTop: 40 }}
// onLoad={() => this.setState({ connected: true })}
/>
</View>
</View>
SettingsRoute = () => <Text>Settings</Text>;
HelpRoute = () => <View></View>
_renderScene = BottomNavigation.SceneMap({
viewcamera: this.ViewRoute,
viewsettings: this.SettingsRoute,
viewhelp: this.HelpRoute,
});
render() {
return (
<BottomNavigation
navigationState={this.state}
onIndexChange={this._handleIndexChange}
renderScene={this._renderScene}
/>
);
}
}
State Updates May Be Asynchronous React Documentation
So You cannot test your console.log in this way. Use the callback function of setState method as follows,
this.setState({ connected: !this.state.connected }, () => {
console.log("after: " + this.state.connected);
console.log("--------------");
});
Hope this will help you.
Your issue is here,
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
setState is async function and it takes some time to update the state. It does not block execution of next statements. So you will always get the previous state only for both the console.log.
To get the actual updated value, you should use callback in setState.
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected }, () => console.log("after: " + this.state.connected); ); //Now you will get updated value.
console.log("--------------");
};
For this,
{/* --------- This text field is not shown at all ------------*/}
<Text>
{this.state.connected}
</Text>
this.state.connected is either true or false (Boolean) which will never be shown on screen. If you still want to see the value on screen, then you can use this hack.
<Text>
{this.state.connected.toString()}
</Text>
Update
From the docs,
Pages are lazily rendered, which means that a page will be rendered the first time you navigate to it. After initial render, all the pages stay rendered to preserve their state.
Instead of this,
_renderScene = BottomNavigation.SceneMap({
viewcamera: this.ViewRoute,
viewsettings: this.SettingsRoute,
viewhelp: this.HelpRoute,
});
You should use this version of renderScene,
_renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'viewcamera':
return <ViewRoute jumpTo={jumpTo} connected={this.state.connected} setUnConnected={this.setUnConnected}/>; //Here you can pass data from state and function to your component
case 'viewsettings':
return <SettingsRoute jumpTo={jumpTo} />;
case 'viewhelp':
return <HelpRoute jumpTo={jumpTo} />;
}
}
Your complete code should look like this,
import * as React from 'react';
import { BottomNavigation, Text, Avatar, Button, Card, Title, Paragraph, Banner } from 'react-native-paper';
import { View, Image, WebView } from 'react-native';
const ViewRoute = (props) =>
<View style={{ flex: 1, marginTop: 40 }}>
{/* --------- This text field does not get updated -------------*/}
<Text>connected: {props.connected ? 'true' : 'false'}</Text>
{/* --------- This text field is not shown at all ------------*/}
<Text>
{props.connected.toString()}
</Text>
<Button icon="camera" mode="contained" onPress={props.setUnConnected}>
Press me
</Button>
<View style={{ height: 400, width: 400 }}>
<WebView
source={{ uri: 'https://stackoverflow.com/' }}
style={{ marginTop: 40 }}
// onLoad={() => this.setState({ connected: true })}
/>
</View>
</View>
const SettingsRoute = () => <Text>Settings</Text>;
const HelpRoute = () => <View></View>
export default class MyNavi extends React.Component {
constructor(props, context) {
super(props, context);
this.setUnConnected = this.setUnConnected.bind(this);
}
state = {
index: 0,
routes: [
{ key: 'viewcamera', title: 'View', icon: 'remove-red-eye' },
{ key: 'viewsettings', title: 'Settings', icon: 'settings' },
{ key: 'viewhelp', title: 'How-To', icon: 'help' },
],
visible: true,
connected: false,
};
_handleIndexChange = index => { this.setState({ index }); }
setUnConnected = function() {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
_renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'viewcamera':
return <ViewRoute jumpTo={jumpTo} connected={this.state.connected} setUnConnected={this.setUnConnected}/>; //Here you can pass data from state and function to your component
case 'viewsettings':
return <SettingsRoute jumpTo={jumpTo} />;
case 'viewhelp':
return <HelpRoute jumpTo={jumpTo} />;
}
}
render() {
return (
<BottomNavigation
navigationState={this.state}
onIndexChange={this._handleIndexChange}
renderScene={this._renderScene}
/>
);
}
}

undefined is not a function evaluating _this2.props.navigator.push

I am trying to scaffold a simple drawer and navigation in react-native.
As you can see I import the drawer and then I instantiate the Navigator below the Toolbar.
I want to be able to change the route from the AppDrawer but the only thing I get after the button click is
*undefined is not a function (evaluating '_this2.props.navigator.push({ id: 'component5' })') *
Note: I have not attached Component 3 or 5 code because they are simple text renders.
index.android.js
import React, {Component} from 'react';
import {AppRegistry, StyleSheet, Text, View, Navigator, ToolbarAndroid} from 'react-native';
import Component3 from "./app/components/Component3/Component3";
import Component5 from "./app/components/Component5/Component5";
import MyAppDrawer from "./app/components/Miscellaneous/AppDrawer";
import Drawer from 'react-native-drawer';
const drawerStyles = {
drawer: {
shadowColor: "#343477",
shadowOpacity: 0.8,
shadowRadius: 0,
}
}
export default class ReactTest extends Component {
constructor(props, context) {
super(props, context);
this.state = {
drawerType: 'overlay',
openDrawerOffset: 50,
closedDrawerOffset: 0,
panOpenMask: .1,
panCloseMask: .9,
relativeDrag: false,
panThreshold: .25,
tweenHandlerOn: false,
tweenDuration: 350,
tweenEasing: 'linear',
disabled: false,
tweenHandlerPreset: null,
acceptDoubleTap: false,
acceptTap: false,
acceptPan: true,
tapToClose: false,
negotiatePan: false,
rightSide: false,
};
}
openDrawer() {
this.drawer.open()
}
renderScene(route, navigator) {
switch (route.id) {
case 'component2':
return (<Component2 navigator={navigator}/>)
case 'component3':
return (<Component3 navigator={navigator}/>)
case 'component4':
return (<Component4 navigator={navigator}/>)
case 'component5':
return (<Component5 navigator={navigator} title="component5"/>)
case 'component6':
return (<Component6 user={route.user} navigator={navigator} title="component6"/>)
}
}
onActionSelected(position) {
console.log("Settings clicked");
}
onIconClicked(position) {
console.log("App Drawer clicked");
}
render() {
var controlPanel = <MyAppDrawer navigator={navigator} closeDrawer={() => {
this.drawer.close();
}}/>
return (
<View style={styles.containerToolbar}>
<Drawer
ref={c => this.drawer = c}
type={this.state.drawerType}
animation={this.state.animation}
captureGestures={true}
openDrawerOffset={this.state.openDrawerOffset}
closedDrawerOffset={this.state.closedDrawerOffset}
panOpenMask={this.state.panOpenMask}
//panCloseMask={this.state.panCloseMask}
relativeDrag={this.state.relativeDrag}
panThreshold={this.state.panThreshold}
content={controlPanel}
styles={drawerStyles}
disabled={this.state.disabled}
// tweenHandler={this.tweenHandler.bind(this)}
// tweenDuration={this.state.tweenDuration}
// tweenEasing={this.state.tweenEasing}
acceptDoubleTap={this.state.acceptDoubleTap}
acceptTap={this.state.acceptTap}
acceptPan={this.state.acceptPan}
tapToClose={this.state.tapToClose}
negotiatePan={this.state.negotiatePan}
// changeVal={this.state.changeVal}
side={this.state.rightSide ? 'right' : 'left'}
>
<ToolbarAndroid
style={styles.toolbar}
title="MyApp"
// logo={require('./dummy_logo.png')}
navIcon={require("./navigation_icon.png")}
onActionSelected={this.onActionSelected}
onIconClicked={this.openDrawer.bind(this)}
titleColor="black"
actions={[
{title: "Log out", show: "never"}
]}
/>
<Navigator
style={styles.container}
initialRoute={{id: 'component3'}}
renderScene={this.renderScene}/>
</Drawer>
</View>
);
}
}
const styles = StyleSheet.create({
containerToolbar: {
flex: 1,
//justifyContent: 'center',
justifyContent: 'flex-start',
// https://github.com/facebook/react-native/issues/2957#event-417214498
alignItems: 'stretch',
backgroundColor: '#F5FCFF',
},
toolbar: {
backgroundColor: '#e9eaed',
height: 56,
},
});
AppRegistry.registerComponent('ReactTest', () => ReactTest);
AppDrawer.js
import React, {Component} from 'react';
import {View, Text, Button, Navigator} from 'react-native';
import styles from './styles';
export default class AppDrawer extends Component {
constructor() {
super();
}
render() {
return (
<View style={styles.controlPanel}>
<Text style={styles.controlPanelWelcome}>
Control Panel
</Text>
<Button
onPress={() => {
console.log("pressed");
this.props.navigator.push({
id: 'component5',
});
}}
title="Component 5"
/>
</View>
)
}
}
Since you don't have MyAppDrawer inside your renderScene function, you don't have access to the navigator. You would need to add a ref and use that to get the navigator:
Add ref={navigator => this.navigator = navigator} to your Navigator component, then you can do
<MyAppDrawer navigator={this.navigator} closeDrawer={() => {
this.drawer.close();
}}/>

How to make a dynamic datasource in listview react native?

I don't know what happen to my code. I'm trying to create a Todo list. I'm using listview. I have two components the Todo and the AddTodo.
main component
import React, { Component } from 'react';
import { View, Text, TextInput, StyleSheet, ListView } from 'react-native';
import * as moment from 'moment';
import TodoList from '../compoents/TodoList';
import {TodoModel, ITodo} from '../models/TodoModel';
interface TodoProps{
todo: TodoModel;
ter:string;
}
interface TodoState{
dataSource: any;
myTodo: Array<ITodo>;
}
export default class Todo extends React.Component <TodoProps,TodoState>{
constructor(props:TodoProps) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => true});
this.state = {
dataSource: ds,
myTodo: []
};
}
componentWillMount = () => {
console.log(this.state.myTodo);
let data = {
title: this.props.ter
};
if (this.props.ter) {
this.state.myTodo.push(data);
}
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.state.myTodo),
myTodo: this.state.myTodo
});
}
render() {
return (
<View style={{marginTop: 60}}>
<ListView enableEmptySections={true} dataSource={this.state.dataSource} renderRow={(rowData) => <TodoList data={rowData} /> } />
</View>
)
}
}
this will view the list of todo that I add from the form
AddTodo component
import * as React from "react";
import { Alert, View, Text, StyleSheet, TextInput, TouchableOpacity } from 'react-native';
import {TodoModel} from '../models/TodoModel';
import { Actions} from 'react-native-router-flux';
interface TodoState{
todoText?: string;
}
interface TodoProps{
text: string;
}
export default class AddTodo extends React.Component <TodoProps,TodoState> {
constructor(props:TodoProps){
super(props);
this.state = {
todoText:" "
};
}
handleSubmit = () => {
Actions.todo({ter: this.state.todoText});
}
render() {
return (
<View style={{margin: 128, marginLeft: 15, marginRight:15}}>
<Text>ADD</Text>
<TextInput autoCorrect={false} style={styles.input} placeholder='Todo' onChangeText={(text) => this.setState({todoText:text})} value={this.state.todoText} />
<TouchableOpacity onPress={this.handleSubmit.bind(this)}>
<Text style={styles.button}>Submit</Text>
</TouchableOpacity>
</View>
)
}
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#ccc',
color: 'white',
height: 40,
lineHeight: 30,
marginTop: 10,
textAlign: 'center',
alignSelf: 'stretch',
borderRadius: 5,
justifyContent: 'center',
alignItems: 'center',
},
container: {
},
input: {
borderColor: '#ededed',
borderRadius: 5,
borderWidth: 1,
height: 37,
alignSelf: 'stretch',
}
})
the issue here. When the time I add one todo. I was successfully added to the myTodo array, but when I add the second todo. It removes the first todo, and show the second todo. It doesn't push and add to the array. Why it happen that way? but if you have tutorial on how to do it. It will more great to study for it. I'm very interested to learn react native.
update
export default class App extends React.Component<Props, State> {
render() {
return (
<Router>
<Scene key="root">
<Scene key="todo" component={Todo} title="Todo" initial={true} onRight={() => Actions.addTodo({text: 'Hello World!'})}
rightTitle="Add"/>
<Scene key="addTodo" component={AddTodo} title="Add Todo" backTitle="Cancel" />
</Scene>
</Router>
);
}
}
Let try my code, hope it can help you.
Your "push" will mutate the state directly and that could potentially lead to error prone code, even if you are "resetting" the state again afterwards
You can find more here: Correct modification of state arrays in ReactJS
Edited:
rewrite handleSubmit function
handleSubmit = () => {
Actions.pop({refresh :
{ter: this.state.todoText}
});
}
Change componentWillMount to componentWillReceiveProps
componentWillReceiveProps = (nextProps) => {
console.log(this.state.myTodo);
let data = {
title: nextProps.ter
};
let todoList = this.state.myTodo
if (nextProps.ter) {
todoList.push(data);
}
this.setState({
myTodo: todoList
});
}
You can see my answer for a very similar issue:
React Native: Child ListView will not update after element in parent is changed
So in fact, in your case, you may simply do this:
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => true});
export default class Todo extends React.Component <TodoProps,TodoState>{
constructor(props:TodoProps) {
super(props);
this.state = {
dataSource: ds.cloneWithRows([]), // or put your initial data here
myTodo: []
};
}
Then, every time you want to update the list, just use ds.cloneWithRows(NEW_DATA)
componentWillMount() {
// ...
this.setState({
dataSource: ds.cloneWithRows(this.state.myTodo),
myTodo: this.state.myTodo
});
}
One more thing, for react's default functions in component's life cycle, they are bound automatically, so don't need to use componentWillMount = () => {}, just use componentWillMount()
This is just the way you should do, if errors still happen, please show here, then we may figure them out together, thanks

Resources