How to make a dynamic datasource in listview react native? - reactjs

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

Related

react native function return value not showing

i am using react native code but some how code not working. please let me check how i can fix. i am getting value from cache and trying to return & show value.
i tried lot some how code not working if someone has any idea please let me know
import React, { memo } from 'react';
import { Text, View, StyleSheet, AsyncStorage } from 'react-native';
import { theme } from "../core/theme";
class Dashdata extends React.Component{
constructor(){
super();
this.getDataName = this.getDataName.bind(this);
this.state = {
displayname: ''
};
}
getDataName = () => {
const displayname = '';
console.log('getting value from cachedd');
const loginName = AsyncStorage.getItem('#kidssafety:displayname')
.then((result)=>{
console.log(result);
return (
<Text>{result}</Text>
)
});
}
render(){
return(
<View>
<Text style={styles.header}>Welcome Data {this.getDataName()}</Text>
</View>
)
}
}
const styles = StyleSheet.create({
header: {
fontSize: 22,
color: theme.colors.primary,
fontWeight: "bold",
paddingVertical: 14,
flex: 1,
marginTop: 100,
width: '100%',
textAlign: 'left'
}
});
export default memo(Dashdata);
AsyncStorage returns a promise. So you have to wait until it resolves.
Use Async/await to fix your problem.
getDataName = async () => {
const loginName = await AsyncStorage.getItem('#kidssafety:displayname')
this.setState({
displayname: loginName
})
}
Now you can display your values inside render
<Text style={styles.header}>Welcome Data {this.state.displayname}</Text>
Important
Since you are using as getDataName as an arrow function, you don't have to bind it as
this.getDataName = this.getDataName.bind(this)
Hope this helps you. Feel free for doubts.
AsyncStorage.getItem returns a promise and by the time it resolves and returns a value, your render would have moved to the next line. Ideally you should store the result to the state and use it when ready. Then your component will look like.
import React, { memo } from 'react';
import { Text, View, StyleSheet, AsyncStorage } from 'react-native';
import { theme } from "../core/theme";
class Dashdata extends React.Component{
constructor(){
super();
this.getDataName = this.getDataName.bind(this);
this.state = {
displayname: '',
result: '' // add result here
};
}
getDataName = () => {
const displayname = '';
console.log('getting value from cachedd');
const loginName = AsyncStorage.getItem('#kidssafety:displayname')
.then((result)=>{
console.log(result);
this.setState({result}) // set result to state
});
}
render(){
const { result } = this.state
return(
<View>
{!!result && (<Text style={styles.header}>Welcome Data {result})</Text>}
</View>
)
}
}
const styles = StyleSheet.create({
header: {
fontSize: 22,
color: theme.colors.primary,
fontWeight: "bold",
paddingVertical: 14,
flex: 1,
marginTop: 100,
width: '100%',
textAlign: 'left'
}
});
export default memo(Dashdata);

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.

Changing the style of "toggled" elements in a list, react-native

I'm having trouble changing the style of only one element in a list.
Below is my Main class, as well as StationDetails class, which is a component I've created to render the list elements one by one.
There is one line (Line 31) in the StationDetails I cant seem to figure out the problem with. I want to style the component based on whether or not the elements' ID is included in the activeStations list.
Here is the line:
style={activeStations.includes(stations.id) ? pressedStyle : buttonStyle}
Here is my Main class
import React, { Component } from "react"
import axios from "axios"
import { Text, View, ScrollView } from "react-native"
import StationDetails from "./StationDetails"
class Main extends Component {
constructor(props) {
super(props)
this.state = { stations: [], pressStatus: false, activeStations: [] }
this.handleClick = this.handleClick.bind(this)
}
componentWillMount() {
axios
.get("https://api.citybik.es/v2/networks/trondheim-bysykkel")
.then(response =>
this.setState({ stations: response.data.network.stations })
)
}
handleClick() {
this.setState({ pressStatus: !this.state.pressStatus })
}
renderStations() {
return this.state.stations.map(stations => (
<StationDetails
activeStations={this.state.activeStations}
handleClick={this.handleClick}
pressStatus={this.state.pressStatus}
key={stations.id}
stations={stations}
>
{stations.name}
</StationDetails>
))
}
render() {
return (
<ScrollView style={{ flex: 1, marginTop: 20 }}>
{this.renderStations()}
</ScrollView>
)
}
}
export default Main
And here is my StationDetails component.
import React from "react"
import { Text, View } from "react-native"
import Card from "./felles/Card"
import CardSection from "./felles/CardSection"
import Button from "./felles/Button"
const StationDetails = ({
stations,
handleClick,
pressStatus,
activeStations
}) => {
const {
headerTextStyle,
leftPartStyle,
rightPartStyle,
pressedStyle,
buttonStyle
} = styles
return (
<Card style={{ flex: 1, flexDirection: "row" }}>
<CardSection style={leftPartStyle}>
<Text style={headerTextStyle}>
{stations.name}
</Text>
<Text>
Free bikes: {stations.free_bikes}
</Text>
</CardSection>
<CardSection style={rightPartStyle}>
<Button
onPress={() => {
if (!activeStations.includes(stations.id)) {
activeStations.push(stations.id)
} else {
activeStations.splice(activeStations.indexOf(stations.id), 1)
}
}}
style={
activeStations.includes(stations.id) ? pressedStyle : buttonStyle
}
>
Abonner
</Button>
</CardSection>
</Card>
)
}
const styles = {
textStyle: {
fontSize: 14
},
leftPartStyle: {
flex: 3,
flexDirection: "column",
justifyContent: "space-between"
},
rightPartStyle: {
flex: 1
},
pressedStyle: {
backgroundColor: "green"
},
headerTextStyle: {
fontSize: 18
},
thumbnailStyle: {
height: 50,
width: 50
},
buttonStyle: {
backgroundColor: "#fff"
}
}
export default StationDetails
You are trying to set state of Main.activeStations from StationDetails which is not advisable. Few things to keep in mind
Main's activeStations is in it's local component level state.
You shouldn't be trying to mutate that from a child component.
Since you assign mutated activeStations state to Main.activeStations from StationDetails, ReactNative (RN) doesn't find a difference in state in it's reconciliation process so does not re-render StationDetails.
We need RN to re-render StationDetails so that it will show the correct style for the buttons etc.
Documentation on setState
This is how I would do it
Let Main render StationDetails
Get a callback from StationDetails on which station was selected
Let Main take care of mutating it's own internal state (activeStations)
By doing it this way,
StationDetails is responsible only for rendering a list of stations given the props and nothing else. It's a dumb component that renders a list.
Main is responsible for handling it's own internal state
Heres the result :
Main.js class
import React, { Component } from 'react';
import axios from 'axios';
import { ScrollView } from 'react-native';
import StationDetails from './StationDetails';
export default class App extends Component {
constructor(props) {
super(props);
this.onSelectStation = this.onSelectStation.bind(this);
this.state = {
stations: [],
pressStatus: false,
activeStations: []
};
}
componentWillMount() {
axios.get('https://api.citybik.es/v2/networks/trondheim-bysykkel')
.then(response => this.setState({ stations: response.data.network.stations }));
}
onSelectStation(stationKey) {
const { activeStations } = this.state;
const activeStationsEdit = activeStations;
if (!activeStations.includes(stationKey)) {
activeStationsEdit.push(stationKey);
} else {
activeStationsEdit.splice(activeStations.indexOf(stationKey), 1);
}
this.setState({ activeStations: activeStationsEdit });
}
renderStations() {
return this.state.stations.map((stations) =>
<StationDetails
activeStations={this.state.activeStations}
pressStatus={this.state.pressStatus}
key={stations.id}
stations={stations}
stationId={stations.id}
onSelectStation={this.onSelectStation}
>
{stations.name}
</StationDetails>
);
}
render() {
return (
<ScrollView style={{ flex: 1, marginTop: 20 }}>
{this.renderStations()}
</ScrollView>
);
}
}
StationDetails class
import React from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
const StationDetails = ({ stations, activeStations, stationId, onSelectStation }) => {
const { headerTextStyle, leftPartStyle, container, pressedStyle, buttonStyle } = styles;
return (
<View style={container}>
<View style={leftPartStyle}>
<Text style={headerTextStyle}>
{stations.name}
</Text>
<Text>
Free bikes: {stations.free_bikes}
</Text>
</View>
<TouchableOpacity
onPress={() => onSelectStation(stationId)}
style={activeStations.includes(stations.id) ? pressedStyle : buttonStyle}
/>
</View>
);
}
const styles = {
container: {
flex: 1,
flexDirection: 'row',
marginBottom: 10
},
leftPartStyle: {
flex: 3,
flexDirection: 'column',
justifyContent: 'space-between'
},
pressedStyle: {
backgroundColor: 'green',
flex: 1,
},
headerTextStyle: {
fontSize: 18
},
buttonStyle: {
backgroundColor: 'skyblue',
flex: 1,
}
};
export default StationDetails;

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

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>
);
}
}

(React Native) Null is not an object

This is the portion of the code React Native is having trouble rendering:
You input {this.state.zip}.
Im a beginner and I was following a tutorial in "Learning React Native" yet he code in the book is not working.
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
TextInput,
Image,
View
} from 'react-native';
class WeatherProject extends Component {
// If you want to have a default zip code, you could add one here
getInitialState() {
return ({
zip: ''
});
}
// We'll pass this callback to the <TextInput>
_handleTextChange(event) {
// log statements are viewable in Xcode,
// or the Chrome debug tools
console.log(event.nativeEvent.text);
this.setState({
zip: event.nativeEvent.text
});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
You input {this.state.zip}.
</Text>
<TextInput
style={styles.input}
onSubmitEditing={this._handleTextChange}/>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
borderWidth: 2,
height: 40
}
});
AppRegistry.registerComponent('WeatherProject', () => WeatherProject);
[enter image description here][1]
In ES6 Classes (one where you are extending Component rather than using createClass), initial states are set with this.state = {zip: ''} in constructor.
So it would-be
class WeatherProject extends Component {
constructor(props){
super(props);
this.state = {
zip: ""
}
}
}
getInitialState is typically used with React.createClass. For defining a component as class, the following code should be in constructor:
getInitialState() {
return ({
zip: ''
});
}
constructor:
constructor() {
super();
this.state = {
zip: ''
}
}

Resources