React Native Table with Row Selection - react-native

I am trying to create a React Native screen that allows the user to select which items to send to the server for batch processing.
My thought was to have a table, and allow the user to select the rows they want, then click a button to submit to the server.
I need the state to contain a list of the ids from those rows, so that I can use it to allow the user to send a request with that array of ids.
A mock-up of my code is below, but it doesn't work. When the update of state is in place, I get an error of "selected items is not an object". When it is commented out, of course the state update doesn't work, but it also doesn't set the value of the checkbox from the array if I hard code it in the state initialization (meaning is 70 is in the array, the box is still not checked by default), and it does allow the box to get checked but not unchecked. How do I get it working?
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import CheckBox from '#react-native-community/checkbox';
import { Table, Row, TableWrapper, Cell } from 'react-native-table-component';
import moment from 'moment';
class FruitGrid extends Component {
constructor(props) {
super(props);
}
state = {
selectedItems : [70],
data: []
};
refresh() {
let rows = [
[69,'David','Apples'],
[70,'Teddy','Oranges'],
[73,'John','Pears']
];
this.setState({data: rows});
}
componentDidMount() {
this.refresh();
}
setSelection(id) {
const { selectedItems } = this.state;
if (id in selectedItems)
{
this.setState({selectedItems: selectedItems.filter(i => i != id)});
}
else
{
this.setState({selectedItems : selectedItems.push(id)});
}
}
render() {
const { selectedItems, data } = this.state;
let columns = ['',
'Person',
'Fruit'];
return (
<View style={{ flex: 1 }}>
<Table borderStyle={{borderWidth: 2, borderColor: '#c8e1ff'}}>
<Row data = {columns} />
{
data.map((rowData, index) =>
(
<TableWrapper key={index} style={styles.row}>
<Cell key={0} data = {<CheckBox value={rowData[0] in selectedItems} onValueChange={this.setSelection(rowData[0])} />} />
<Cell key={1} data = {rowData[1]} textStyle={styles.text}/>
<Cell key={2} data = {rowData[2]} textStyle={styles.text}/>
</TableWrapper>
)
)
}
</Table>
</View>
);
}
}
export default FruitGrid;
const styles = StyleSheet.create({
btn: { width: 58, height: 18, backgroundColor: '#8bbaf2', borderRadius: 2 },
btnText: { textAlign: 'center', color: '#000000' },
text: { margin: 6 },
row: { flexDirection: 'row' },
});

I introduced my own module for this feature from which you will be able to select row easily and much more.
You can use this component like this below
import DataTable, {COL_TYPES} from 'react-native-datatable-component';
const SomeCom = () => {
//You can pass COL_TYPES.CHECK_BOX Column's value in true/false, by default it will be false means checkBox will be uncheck!
const data = [
{ menu: 'Chicken Biryani', select: false }, //If user select this row then this whole object will return to you with select true in this case
{ menu: 'Chiken koofta', select: true },
{ menu: 'Chicken sharwma', select: false }
]
const nameOfCols = ['menu', 'select'];
return(
<DataTable
onRowSelect={(row) => {console.log('ROW => ',row)}}
data={data}
colNames={nameOfCols}
colSettings={[{name: 'select', type: COL_TYPES.CHECK_BOX}]}
/>
)
}
export default SomeCom;
React Native DataTable Component

Thanks to a friend, I found 3 issues in the code that were causing the problem:
When checking to see if the array contains the object, I first need to check that the array is an array and contains items. New check (wrapped in a function for reuse):
checkIfChecked(id, selectedItems)
{
return selectedItems?.length && selectedItems.includes(id);
}
The state update was modifying the state without copying. New state update function:
setSelection(id) {
const { selectedItems } = this.state;
if (this.checkIfChecked(id,selectedItems))
{
this.setState({selectedItems: selectedItems.filter(i => i != id)});
}
else
{
let selectedItemsCopy = [...selectedItems]
selectedItemsCopy.push(id)
this.setState({selectedItems : selectedItemsCopy});
}
}
The onValueChange needed ()=> to prevent immediate triggering, which lead to a "Maximum Depth Reached" error. New version
onValueChange={()=>this.setSelection(rowData[0])} />}
The full working code is here:
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import CheckBox from '#react-native-community/checkbox';
import { Table, Row, TableWrapper, Cell } from 'react-native-table-component';
import moment from 'moment';
class FruitGrid extends Component {
constructor(props) {
super(props);
}
state = {
selectedItems : [],
data: []
};
refresh() {
let rows = [
[69,'David','Apples'],
[70,'Teddy','Oranges'],
[73,'John','Pears']
];
this.setState({data: rows});
}
componentDidMount() {
this.refresh();
}
setSelection(id) {
const { selectedItems } = this.state;
if (this.checkIfChecked(id,selectedItems))
{
this.setState({selectedItems: selectedItems.filter(i => i != id)});
}
else
{
let selectedItemsCopy = [...selectedItems]
selectedItemsCopy.push(id)
this.setState({selectedItems : selectedItemsCopy});
}
}
checkIfChecked(id, selectedItems)
{
return selectedItems?.length && selectedItems.includes(id);
}
render() {
const { selectedItems, data } = this.state;
let columns = ['',
'Person',
'Fruit'];
return (
<View style={{ flex: 1 }}>
<Table borderStyle={{borderWidth: 2, borderColor: '#c8e1ff'}}>
<Row data = {columns} />
{
data.map((rowData, index) =>
(
<TableWrapper key={index} style={styles.row}>
<Cell key={0} data = {<CheckBox value={this.checkIfChecked(rowData[0],selectedItems)} onValueChange={()=>this.setSelection(rowData[0])} />} />
<Cell key={1} data = {rowData[1]} textStyle={styles.text}/>
<Cell key={2} data = {rowData[2]} textStyle={styles.text}/>
</TableWrapper>
)
)
}
</Table>
</View>
);
}
}
export default FruitGrid;
const styles = StyleSheet.create({
btn: { width: 58, height: 18, backgroundColor: '#8bbaf2', borderRadius: 2 },
btnText: { textAlign: 'center', color: '#000000' },
text: { margin: 6 },
row: { flexDirection: 'row' },
});

Related

Using react-native-calendars, how to pass the pressed date back into 'markedDates' prop?

Goal: Be able to select 2 dates on a calendar using react-native-calendars using the onDayPress prop, and use the result in markedDates prop to form a period of days.
Component.js:
import React, { useState, useEffect } from 'react';
import { Image, View, Animated, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import { Calendar } from 'react-native-calendars';
const { width } = Dimensions.get('window');
const CalendarPicker = (props) => {
const [ markedDates, setMarkedDates ] = useState({});
const markDate = (dateString) => {
setMarkedDates(
(markedDates[dateString] = {
endingDay: true,
color: 'blue'
})
);
};
useEffect(() => {});
return (
<Calendar
style={{
width: width * 0.8
}}
theme={{
arrowColor: '#219F75'
}}
minDate={Date()}
onDayPress={({ dateString }) => markDate(dateString)}
hideArrows={false}
hideExtraDays={true}
hideDayNames={false}
markedDates={markedDates}
markingType={'period'}
/>
);
};
export default CalendarPicker;
Problem: Nothing happens. the date isn't "marked", the useState variable is assigned the data correctly though. Wondering if its a re-render issue? How can this be resolved to display the selected date as "marked"?
According to react-native-calendar when you want to highlight dates between start & end, you need to create markedDates as below,
<Calendar
markedDates={{
"2020-01-16": { startingDay: true, color: "green" },
"2020-01-17": { color: "green" },
"2020-01-18": { color: "green" },
"2020-01-19": { endingDay: true, color: "gray" }
}}
markingType={"period"}
/>
Check below example code
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Calendar } from 'react-native-calendars';
import moment from 'moment';
export default class CalendarExample extends React.Component {
state = {
markedDates: {},
isStartDatePicked: false,
isEndDatePicked: false,
startDate: ''
}
onDayPress = (day) => {
if (this.state.isStartDatePicked == false) {
let markedDates = {}
markedDates[day.dateString] = { startingDay: true, color: '#00B0BF', textColor: '#FFFFFF' };
this.setState({
markedDates: markedDates,
isStartDatePicked: true,
isEndDatePicked: false,
startDate: day.dateString,
});
} else {
let markedDates = this.state.markedDates
let startDate = moment(this.state.startDate);
let endDate = moment(day.dateString);
let range = endDate.diff(startDate, 'days')
if (range > 0) {
for (let i = 1; i <= range; i++) {
let tempDate = startDate.add(1, 'day');
tempDate = moment(tempDate).format('YYYY-MM-DD')
if (i < range) {
markedDates[tempDate] = { color: '#00B0BF', textColor: '#FFFFFF' };
} else {
markedDates[tempDate] = { endingDay: true, color: '#00B0BF', textColor: '#FFFFFF' };
}
}
this.setState({
markedDates: markedDates,
isStartDatePicked: false,
isEndDatePicked: true,
startDate: ''
});
} else {
alert('Select an upcomming date!');
}
}
}
render() {
return (
<View style={styles.container}>
<Calendar
minDate={Date()}
monthFormat={"MMMM yyyy"}
markedDates={this.state.markedDates}
markingType="period"
hideExtraDays={true}
hideDayNames={true}
onDayPress={this.onDayPress}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'rgba(52, 52, 52, 0.8)',
padding: 20,
justifyContent: 'center'
}
});
Change this according to your requirements & if you have any doubts feel free to ask.
Hope this helps you.

How to save React Native phone sensor data to an array without using state arrays (getting Maximum update depth exceeded)?

I'm having some trouble figuring out how to handle sensor data in React Native. My goal is to get phone sensor data for a limited time (let's say 10 seconds) and after completion save the list obtained to local database. However pushing new gyroscope value to state array is one option but getting maximum update depth exceeded error in there. The code is below.
import React from 'react';
import {
StyleSheet,
View,
Text,
Dimensions,
} from 'react-native';
import { Gyroscope } from 'expo-sensors';
import { Button } from "native-base";
import CountDown from 'react-native-countdown-component';
import UUID from 'pure-uuid';
import {insertGyroData} from './measureService';
const SAMPLING_RATE = 20; // defaults to 20ms
export class Measure extends React.Component {
state = {
gyroscopeData: {},
measuringStarted: false,
x_gyro: [0],
y_gyro: [0],
z_gyro: [0]
};
componentDidMount() {
this.toggleGyroscope();
}
componentWillUnmount() {
this.unsubscribeAccelerometer();
}
toggleGyroscope = () => {
if (this.gyroscopeSubscription) {
this.unsubscribeAccelerometer();
} else {
this.gyroscopeSubscribe();
}
};
gyroscopeSubscribe = () => {
Gyroscope.setUpdateInterval(20);
this.gyroscopeSubscription = Gyroscope.addListener(gyroscopeData => {
this.setState({ gyroscopeData });
});
};
unsubscribeAccelerometer = () => {
this.gyroscopeSubscription && this.gyroscopeSubscription.remove();
this.gyroscopeSubscription = null;
};
referenceMeasurementCompleted = async (x, y, z) => {
this.setState({ measuringStarted: false });
const uuid = new UUID(4).format();
await insertGyroData([uuid, 'admin', '12345', x.toString(), y.toString(), z.toString()]);
alert('Reference measurements completed');
};
render() {
let { x, y, z } = this.state.gyroscopeData;
const { x_gyro, y_gyro, z_gyro } = this.state;
if (this.state.measuringStarted) {
this.setState(({ x_gyro: [...x_gyro, x], y_gyro: [...y_gyro, y], z_gyro: [...z_gyro, z] }));
return (
<View style={styles.container}>
<Text>Gyroscope:</Text>
<Text>
x: {round(x)} y: {round(y)} z: {round(z)}
</Text>
<Text>Time spent:</Text>
<CountDown
until={5}
size={30}
onFinish={() => this.referenceMeasurementCompleted(x_gyro, y_gyro, z_gyro)}
digitStyle={{ backgroundColor: '#FFF' }}
digitTxtStyle={{ color: '#00c9ff' }}
timeToShow={['M', 'S']}
timeLabels={{ m: 'MM', s: 'SS' }}
/>
</View>
);
} else {
return (
<View style={styles.container}>
<Button
rounded
primary
onPress={() => this.setState({ measuringStarted: true })}
>
<Text style={styles.buttonText}>Start reference measurements</Text>
</Button>
</View>
);
}
}
}
function round(n) {
if (!n) {
return 0;
}
return Math.floor(n * 100) / 100;
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: Dimensions.get('window').height,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center'
},
buttonText: {
color: 'white',
fontSize: 20,
paddingHorizontal: 10,
},
});
With the current solution I'm getting Maximum Depth Update Exceeded error
Please help!
The error is obviously coming from this block since you are changing state inside the render.
if (this.state.measuringStarted) {
this.setState(({ x_gyro: [...x_gyro, x], y_gyro: [...y_gyro, y], z_gyro: [...z_gyro, z] }));
}
One way you can try is updating the state in
componentDidUpdate(prevProps,prevState){}
But take care that it does not lead to Maximum Stack Reached error. With proper conditional statements you can set the state to new data without infinite recursion.

react native todo list with TextInput

Is it possible to build a todo list with react native that can
add new TexInput with the return key
focus the new TextInput when created
remove TextInputs with the delete key if the TextInput is empty and focus another input
I have a basic list that can add items and focus them but not remove items.
https://snack.expo.io/#morenoh149/todo-list-textinput-spike
import * as React from 'react';
import { TextInput, View } from 'react-native';
export default class App extends React.Component {
currentTextInput = null
state = {
focusedItemId: 0,
items: [
{ id: 0, text: 'the first item' },
{ id: 1, text: 'the second item' },
],
};
addListItem = index => {
let { items } = this.state;
const prefix = items.slice(0, index + 1);
const suffix = items.slice(index + 1, items.length);
const newItem = { id: Date.now(), text: '' };
let result = prefix.concat([newItem]);
result = result.concat(suffix);
this.setState({
focusedItemId: newItem.id,
items: result,
});
};
focusTextInput() {
// focus the current input
this.currentTextInput.focus();
}
componentDidUpdate(_, pState) {
// if focused input id changed and the current text input was set
// call the focus function
if (
pState.focusedItemId !== this.state.focusedItemId
&& this.currentTextInput
) {
this.focusTextInput();
}
}
render() {
const { focusedItemId } = this.state;
return (
<View style={{ flex: 1, justifyContent: 'center' }}>
{this.state.items.map((item, idx) => (
<TextInput
style={{ borderWidth: 1, borderColor: 'black' }}
value={item.text}
ref={item.id === focusedItemId
? c => this.currentTextInput = c
: null}
autoFocus={item.id === focusedItemId}
onChangeText={text => {
const newItems = this.state.items;
newItems[idx].text = text;
this.setState({
items: newItems,
});
}}
onSubmitEditing={event => this.addListItem(idx)}
/>
))}
</View>
);
}
}
To remove items you can add a callback to the onKeyPress and check if it was the Backspace (delete) key and if the text field was empty already. If so, you remove the item from the item list.
onKeyPress={({ nativeEvent: { key: keyValue } }) => {
if(keyValue === 'Backspace' && !items[idx].text) {
this.removeListItem(idx)
}
}}
In the removeListItem function you can remove the item at the index and update the selected id to the id previous in the list to focus this one.
removeListItem = index => {
const { items } = this.state;
const newItems = items.filter(item => item.id !== items[index].id)
this.setState({
focusedItemId: items[index - 1] ? items[index - 1].id : -1,
items: newItems.length ? newItems : [this.createNewListItem()],
});
}
Please find the full working demo here: https://snack.expo.io/#xiel/todo-list-textinput-spike

Error while updating property 'd' of a view managed by: RNSVGPath

I am struggling for last few days with the below error in react native.
My intention:
Dynamically fetch chart data and plot multiple charts on my page.
Whenever I have a succesful fetch the sData[] gets filled. However my chart keeps thrwoing an error:
Error while updating property 'd' of a view managed by: RNSVGPath
null
Attempt to invoke interface method 'int java.Charsequence.length()' on a null object reference
If the fetch fails and my sData is set to default array [5,4,3,2,1] as below in the code, the chart is able to render.
What am i missing/messing? Please help.
import React, { Component } from 'react';
import {AsyncStorage} from 'react-native';
import { LineChart, Grid } from 'react-native-svg-charts';
import { Container, Header, Content, List, ListItem, Text, Left, Right, Body , Button, Title} from 'native-base';
const data = [1,2,3,4,5,6,7];
export default class SomeDetails extends Component {
constructor(props)
{
super(props);
this.state = { 'user': '',
'email': '',
'privLevel': '',
'phNum': '',
UserApiUrl: '<SOMEAPI>',
sData: [],
someData: ''
}
}
componentDidMount() {
this._loadInitialState().done();
}
_loadInitialState = async () => {
var uPhVal = await AsyncStorage.getItem('uPh');
var uEmailVal = await AsyncStorage.getItem('uEmail');
var uPrivVal = await AsyncStorage.getItem('uPlevel');
var uName = await AsyncStorage.getItem('username');
if(uName !== null)
{
this.setState({'user': uName});
this.setState({'phNum': uPhVal});
this.setState({'email': uEmailVal});
this.setState({'privLevel':uPrivVal})
}
var postString = "SOME STRING FOR MY API"
console.log(postString);
response = await fetch(this.state.UserApiUrl, {
method: 'POST',
body: postString
})
res = await response.json();
console.log(res.success);
if (res.success == "true") {
this.setState({ someData: res.someLatestVal });
var dataItems = this.state.someData.split(';');
for(let j=0;j<dataItems.length; j++)
{
var dataI = dataItems[j].split(':');
this.setState({sData: this.state.sData.concat([dataI[0]]) } );
}
}
else {
// console.log("Req: Unable to fetch");
this.setState({sData: [1,2,3,4,5]});
this.setState({loading: true});
}
console.log(this.state.sData);
}
render() {
const { navigation } = this.props;
const someName = navigation.getParam('someName', 'no-name');
return (
<Container>
<Content>
<List>
<ListItem>
<Text>Sensorname: { someName } </Text>
</ListItem>
<LineChart
style={{ height: 70, width: 120 }}
data={ this.state.sData }
svg={{ stroke: 'rgb(134, 65, 244)' }}
contentInset={{ top: 20, bottom: 20 }}
>
<Grid/>
</LineChart>
</List>
</Content>
</Container>
);
}
}
We fixed this by ensuring VictoryLine always gets at least 2 data points. If passed only one data point it may crash.
Here's the working code with a simple check for whether a data set has at least two items:
{dataSetsWithGaps.map(coordList =>
coordList.length < 2 ? null : (
<VictoryLine
key={`line_${coordList[0].x.toString()}`}
interpolation={interpolationMethod}
data={coordList}
/>
),
)}
remove - yarn remove react-native-svg
add - yarn add react-native-svg#9.13
it worked for me

onChangeText and setState for nested state don't work with submit

For this component I have two nested states.
And I have a text input with a submit button as shown in the code below.
I'd like to save user's input in this.state.schoolForm.userInput whenever onChangeText fires, and then save this.state.schoolForm.userInput to this.userInputs.schoolName when commit button is clicked.
This would work when I just setState to this.state.userInputValue (A simple not nested state) However, it wouldn't work when I try to setState to the nested state: this.state.schoolForm.userInput
When I click submit button nothing happens but it's supposed to transit to the next state. It seems like the way I save to a nested state is causing the problem but I have followed this post React setState for nested state and I can't find anything wrong from my code.
import React from 'react';
import { StyleSheet, Button, StatusBar, Text, TextInput, View} from 'react-native';
const states = {
schoolForm: {
prompt: "Where did you go to school?",
userInput: "School name",
},
durationForm: {
prompt: "For how long?",
userInput: "Duration",
},
}
export default class NewEducation extends React.Component {
constructor(props) {
super(props);
this.state = {
//form: states.reviewForm,
form: states.schoolForm,
userInputs: {
schoolName: "",
duration: ""
},
//for testing
userInputValue: "",
}
}
_nextState = () => {
switch(this.state.form) {
case states.schoolForm:
this.setState({form:states.durationForm});
break;
case states.durationForm:
this.setState({form:states.degreeForm});
break;
default:
break;
}
}
_submitInfo = () => {
switch(this.state.form) {
case states.schoolForm:
//this.setState({ userInputs: { ...this.state.userInputs, schoolName: this.state.form.userInput} });
this.setState({ userInputs: { ...this.state.userInputs, schoolName: this.state.userInputValue}});
break;
case states.durationForm:
//this.setState({ userInputs: { ...this.state.userInputs, duration: this.state.form.userInput} });
this.setState({ userInputs: { ...this.state.userInputs, duration: this.state.userInputValue}});
break;
default:
break;
}
this._nextState();
}
render() {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content"/>
<Text style={styles.textContentWhite}>{this.state.form.prompt}</Text>
<TextInput
style={styles.textContentWhite}
placeholder={this.state.form.userInput}
placeholderTextColor="#B7BEDE"
onChangeText={(userInputValue) =>
//this.setState({ form: {userInput: userInputValue} }
this.setState({form: { ...this.state.form, userInput: userInputValue }}
)}
/>
<Button
onPress={this._submitInfo}
title="Submit"
color="white"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: "black",
},
textContentWhite: {
fontSize: 20,
color: 'white',
},
});