How to update checkbox appearance or state in child - react-native

I have searched about but not found a solution (I'm a beginner, so...) despite a few posts such as:
Use modal to take user checkbox input and return data back to parent component
I have a parent with a number of checkboxes. Depending on the state of a switch I insert either child1 or child2 into the parent.
Each child has 4-5 checkboxes, not just one.
I have figured out that the best way (for me) is to have the state for the checkbox values and the checkbox visual state (as in a showing as checked or unchecked) should be handled in the parent with props. I don't want to use redux.
I am having issue with the checkbox not showing as checked in the child, even though the value is being passed back. I had a toggle function in each child but that just meant that while it worked, it was one step behind, showing unchecked when checked and vice versa.
My parent:
import React, { Component } from "react";
import { StyleSheet, Text, View, Image, ImageBackground, ScrollView, Switch, Platform } from "react-native";
import CircleCheckBox, { LABEL_POSITION } from "react-native-circle-checkbox";
import Child1 from "../components/Child1";
import Child2 from "../components/Child2";
// Function called when checkbox is toggled
onchange(checkedBoxName, value){
if(this.state[checkedBoxName]==false){
this.setState({
[checkedBoxName]: true,
})
Let myResult = null,
// Do math and stuff with the value argument and set outputText
this.setState({outputText: myResult});
}
class ParentScreen extends Component {
constructor(props) {
super(props);
this.state = {
outputText: "",
///checkbox states that are in the parent here
//followed by states for child1 checkbox
Checked10: false,
Checked11: false,
Checked12: false,
Checked13: false,
// then child2 checkboxes
Checked14: false,
Checked15: false,
Checked16: false,
Checked17: false,
}
render(){
return(
// bunch of views and checkboxes etc in the parent like this:
<View style={[styles.checkbox, styles.checkbox2]} >
<CircleCheckBox outerSize={28} filterSize={26} innerSize={14}
outerColor={"#ff0000"} filterColor={"#FFF"} innerColor={"#ff0000"}
style={styles.checkbox} checked={this.state.checked1} onToggle={(checked) => this.onchange("checked1", 1)} />
</View>
// etc
// Then choosing and displaying my child component:
<View>
{switchChild ? <Child1 changeChild1 ={this.onchange.bind(this)} /> : <Child2 changeChild2 ={this.onchange.bind(this)} />}
</View>
);
}
Then in my child component:
import React, { Component } from "react";
import { View, Text, StyleSheet, Image } from "react-native";
import CircleCheckBox, {LABEL_POSITION} from "react-native-circle-checkbox";
class PL extends Component {
// no checkbox states here
render() {
return(
<View style={[styles.checkbox, styles.checkbox2]} >
<CircleCheckBox outerSize={28} filterSize={26} innerSize={14} outerColor={"#ff0000"} filterColor={"#FFF"} innerColor={"#ff0000"} style={styles.checkbox} checked={this.state.checked10} onToggle={(checked) => { this.props.changeChild1("checked10", 10) }} />
</View>
<View style={[styles.checkbox, styles.checkbox2]} >
<CircleCheckBox outerSize={28} filterSize={26} innerSize={14} outerColor={"#ff0000"} filterColor={"#FFF"} innerColor={"#ff0000"} style={styles.checkbox} checked={this.state.checked11} onToggle={(checked) => { this.props.changeChild1("checked11", 11) }} />
</View>
<View style={[styles.checkbox, styles.checkbox2]} >
<CircleCheckBox outerSize={28} filterSize={26} innerSize={14}
outerColor={"#ff0000"} filterColor={"#FFF"} innerColor={"#ff0000"} style={styles.checkbox} checked={this.state.checked12} onToggle= {(checked) => { this.props.changeChild1("checked12", 12) }} />
</View>
//etc
);
}
}
So, what I need is a way to show that each child checkbox is checked when it's clicked, particularly when there are multiple checkboxes in the child. Since it's more than one checkbox, I can't just hard code in a particular state. I assume I use a variable in the "checked={this.state.checked11}" line (like: checked={this.state[aVarWithCheckboxName]} or maybe there is another way to do this?
Will this need to pass multiple functions to work?

Ok. So after a lot more reading and pulling out bits here and there, I fixed the problem. But I think it might be a bit of overkill so would like to hear how to avoid sending the whole state to the child, and just send, or reference to, a collection of state items.
Got rid of all state in child components. State for all checkboxes were put into the parent along with all of the functions that processed the data.
Parent:
Did not need ".bind(this)" thanks to ES6.
<View>
{switchChild ? <Child1 changeChild1 ={this.onchange} {...this.state} /> : <Child2 changeChild2 ={this.onchange} {...this.state} />}
</View>
Child:
<View style={[styles.checkbox, styles.checkbox2]} >
<CircleCheckBox //all the styling goes here
checked={this.props.checked10} onToggle={(checked) => { this.props.changeChild1("checked10", 10) }} /> }} />
</View>
So now no matter how many checkboxes I have, I can access the parent state with the individual name of the checkbox. Sending back the data to run the onchange function remains the same.
If there is a better way of handling this (targeting the checked item in the parent state), let me know.

Related

Adding a function to the passed this.props.onPress-function in a React Native app

I have changed all the TouchableOpacity-components to a custom component so I can add a universal function to all the buttons / clickable views in my application. I call the new component HapticButton.
All the HapticButton-components will contain onPress properties like this:
<HapticButton activeOpacity={1.0} onPress={() => { console.log("button was pressed"); }}>...</HapticButton>
And my HapticButton-class looks like this:
import React from 'react';
import { TouchableOpacity, Text} from 'react-native';
export default class HapticButton extends React.Component {
render() {
return (
<TouchableOpacity activeOpacity={this.props.activeOpacity} style={[this.props.style]} onPress={this.props.onPress}>
{this.props.children}
</TouchableOpacity>
);
}
vibrate() {
// Code that makes a haptic feedback when called
}
};
I succesfully pass on the onPress-property to my new HapticButton-component, but how do I merge together my the this.props.onPress-property with the vibrate()-function so that this gets called every time the HapticButton is pressed?
You can easily merge two function call together as below
<TouchableOpacity onPress={() => {
this.props.onPress();
this.vibrate();
}}>
or you can directly invoke this.props.onPress() in the vibrate function
<TouchableOpacity onPress={this.vibrate} ...
vibrate() {
this.props.onPress();
// Code that makes a haptic feedback when called
}
Not much differences based on your use case between the two ways I've shared, readability wise I think first way is better

React Native components seem to be sharing a state

I'm having an issue with React-native where I have a component TouchTimer which uses an AnimatedTimer component. This timer is supposed to start and stop when it is tapped, which it does, however all of the TouchTimer components I add to a page will start and stop whenever any of them are tapped, rather than only affecting the tapped component.
Below is a snippet of my component:
TouchTimer.tsx
export class TouchTimer extends React.Component<TouchTimerProps> {
state: {
...
paused: boolean,
}
constructor(props) {
super(props);
...
this.state = {
...
paused: true,
}
}
startStop() {
this.setState({paused: !this.state.paused});
}
render() {
const { time } = this.props;
return (
<TouchableHighlight onPress={() => this.startStop()}>
<View>
<AnimatedTimer
...
time={time}
pause={this.state.paused}
/>
<View style={styles.timeContainer}>
<Text style={styles.time}>{this.state.remaining}</Text>
</View>
</View>
</TouchableHighlight>
)
}
}
And here is a snippet of the screen containing these components:
Details.tsx
import { TouchTimer } from '../components/TouchTimer';
...
export class RecipeDetailsScreen extends React.Component<NavigationInjectedProps> {
...
{this.state.steps.map(step => (
<List.Item
key={step.id}
title={"Step " + step.index}
style={styles.step}
description={step.short_desc}
right={() => (step.time > 0 &&
<TouchTimer
time={step.time * 60000}
/>
)}
/>
)
}
I have tried wrapping the TouchTimer components in a View and changing the paused boolean to a prop, to no avail.
I have also tested to see if this issue appears when the components are not siblings, and when they are not produced as the result of a callback, and the issue still persists in both these cases.
If anybody has any advice or answers on how to make these timers independent I would very much appreciate it!
Curiously that component seems to be implemented with a global pauseFlag that applies to all component instances. See https://github.com/dalisalvador/react-native-animated-timer/blob/master/src/Components/AnimatedTimer.js#L34
So I don't think you're doing anything wrong here, this is a limitation of the library code that is coupling all instances of your timer to the same pauseFlag value.

How to correctly large state updates in React Native?

I am writing a small ReactNative application that allows users to invite people to events.
The design includes a list of invitees, each of which is accompanied by a checkbox used to invite/uninvite said invitee. Another checkbox at the top of the list that performs a mass invite/uninvite on all invitees simultaneously. Finally a button will eventually be used to send out the invites.
Because the state of each of these elements depends changes made by the other I often need to re-render my entire UI whenever the user takes action on one of them. But while this works correctly it is causing me quite a few performance issues, as shown in this video
Here's the code I'm using:
import React, { Component } from 'react';
import { Container, Header, Title,
Content, Footer, FooterTab,
Button, Left, Right,
Center, Body, Text, Spinner, Toast, Root , CheckBox, ListItem, Thumbnail} from 'native-base';
import { FlatList, View } from 'react-native';
export default class EventInviteComponent extends Component {
constructor(props) {
super(props);
console.disableYellowBox = true;
this.state = {
eventName: "Cool Outing!",
invitees:[]
}
for(i = 0; i < 50; i++){
this.state.invitees[i] = {
name: "Peter the " + i + "th",
isSelected: false,
thumbnailUrl: 'https://is1-ssl.mzstatic.com/image/thumb/Purple111/v4/62/08/7e/62087ed8-5016-3ed0-ca33-50d33a5d8497/source/512x512bb.jpg'
}
}
this.toggelSelectAll = this.toggelSelectAll.bind(this)
}
toggelSelectAll(){
let invitees = [...this.state.invitees].slice();
let shouldInviteAll = invitees.filter(invitee => !invitee.isSelected).length != 0
let newState = this.state;
newState = invitees.map(function(invitee){
invitee.isSelected = shouldInviteAll;
return invitee;
});
this.setState(newState);
}
render() {
let invitees = [...this.state.invitees];
return (
<Root>
<Container>
<Content>
<Text>{this.state.eventName}</Text>
<View style={{flexDirection: 'row', height: 50, marginLeft:10, marginTop:20}}>
<CheckBox
checked={this.state.invitees.filter(invitee => !invitee.isSelected).length == 0}
onPress={this.toggelSelectAll}/>
<Text style={{marginLeft:30 }}>Select/deselect all</Text>
</View>
<FlatList
keyExtractor={(invitee, index) => invitee.name}
data={invitees}
renderItem={(item)=>
<ListItem avatar style={{paddingTop: 20}}>
<Left>
<Thumbnail source={{ uri: item.item.thumbnailUrl}} />
</Left>
<Body>
<Text>{item.item.name}</Text>
<Text note> </Text>
</Body>
<Right>
<CheckBox
checked={item.item.isSelected}/>
</Right>
</ListItem>}/>
</Content>
<Footer>
<FooterTab>
<Button full
active={invitees.filter(invitee => invitee.isSelected).length > 0}>
<Text>Invite!</Text>
</Button>
</FooterTab>
</Footer>
</Container>
</Root>);
}
}
In your code, in class method toggelSelectAll() {...} you modify the state directly by using this.state = ..., which is something to be avoided. Only use this.state = ... in your class constructor() {...} to initialize the state, and you should only use this.setState({...}) to update the state anywhere else.
Not sure if this should help your performance issues, but try replacing toggelSelectAll() with the following:
toggelSelectAll() {
const {invitees} = this.state;
const areAllSelectedAlready = invitees.filter(({isSelected}) => !isSelected).length === 0;
this.setState({
invitees: invitees.map(invitee => ({
...invitee,
isSelected: !areAllSelectedAlready
}))
});
}
Good luck! And, let me know if you would like me to refactor your above code to remove the 2nd this.state = ... in your constructor (which, once again, should be avoided when writing React).
I suggest:
Dividing your code by creating multiple components, so you won't have a massive render()
Using Redux to store invitee / global state, so you can choose which components should re-render in case of modifications
That's a good way to learn React Native!

setState() adds a Proxy object to property instead of string value in react native

This is a very weird problem and I'm sure I'm overlooking something very simple.
When I load a new component AddNewCategory, I initially set a an empty string to a text property on this.state. Logging on console shows the text value as empty too.
I update this value using onChange of Button component. I know that the text property is so far updating as expected because the value of the input field changes accordingly.
<Button onChange={text=>this.setState({})}>
But when I try to retrieve the value of text property, I see that instead of a string, the text is now assigned a Proxy object.
I'm only trying to get the value of the input field so I can pass the value on to an action creator.
Here's the entire code,
import React from 'react';
import { View, Text, TextInput, Button} from 'react-native';
class AddNewCategory extends React.Component {
constructor(props) {
super(props)
this.state={
text: ''
};
console.log('after initialising constructor');
console.log(this.state);
this.onContinueButtonPress = this.onContinueButtonPress.bind(this);
}
onContinueButtonPress() {
console.log('after onContinueButtonPress');
console.log(this.state);
this.props.addNewCategory('Random Value');
}
render(){
return(
<View>
<Text>Name your new task list</Text>
<TextInput
placeholder="Things to do today.."
value={this.state.text}
onChange={(text)=>this.setState({text: text})}
/>
<Button
title={'Continue'}
onPress={this.onContinueButtonPress}
/>
</View>
);
}
};
export default AddNewCategory;
I have no idea why you are giving an onChange prop to a Button component.
Anyway, for a TextInput, you should give the onChangeText property.
<TextInput onChangeText={text => this.setState({ text })}
Other properties have been omitted.
Using just onChange is like handling the usual onChange event when doing web development, where the first parameter to the callback method only gives the event; to get the actual input value you have to say event.target.value.
onChange={(event) => console.log(event.target.value)}

React Native: passing data between screens

I'm trying to create a small app, but have many questions)
I have 3 screen and use react-navigation to pass data. the scheme is following:
1) loading screen (fetch data and save it to AsyncStorage+handling data and save to obj1 for pickers)
(pass obj1)
2)main screen (get data,render pickers based on it, take selected values and pass them next)
(pass pickers selection+input)
3)some results(get data from Asyncstorage, some calculating and render results)
so I have two questions.
when I navigate back from 3) to 2) I have an error that screen2 need data, which was passed from screen1. yes - i've checked if this data pass to 3 and then to 2 when Back Button is pressed, and there is no error, but I'm sure this is bad solution
and second..trying to explain) on screen 3 some calculations made on pickers selection, so it hasn't problem. but rest of them needed get data from AsyncStorage and then convert it according to Picker values and render to ListView. Despite I'm putting getting from AS on componentWillMount it's still take much time so data for rendering is undefined. Of course I'm using states, but I think this is a bad logic of data handling..
UPD
so I'm trying pass data from child(screen) to parent(index.ios.js), where it define as first loading view( I'm using navigator's stack screens)
export default class App extends Component {
constructor(props) {
super(props);
};
}
myCallback(dataFromChild) {
console.log("yeah", dataFromChild);
}
render() {
return (
<LoadingScreen callbackFromParent={this.myCallback}/>
);
}
}
and LoadingScreen.js:
render() {
return(
<View style={{flex: 1, backgroundColor: '#dc143c', marginTop:20}}>
<Image
source={require('./WhiteLogo.png')}
style={{flex:3, height: undefined, width: undefined, marginLeft:20, marginRight:20}}
resizeMode="contain"
qwer={this.props.callbackFromParent('listInfo').bind(this)}
/>
<Spinner color='white' style={{flex:1}}/>
</View>
);
}
}
and I've got an error "Unhandled JS Exception: this.props.callbackFromParent is not a function"
AsyncStorage might not be the best solution for what you are trying. Using react-navigation for data delivery is not the best neither. I would suggest checking redux for storing states globally and delivering it to the pages when needed. There is also another way which is passing functions to the props of child components for pulling up any data from child to parent components. You can check this and this or the sample code is below.
Parent
class Parent extends Component {
updateState (data) {
this.setState(data);
}
render() {
<View>
<Child updateParentState={this.updateState.bind(this)} />
</View>
}
}
Child
class Child extends Component {
render() {
<View>
<Button title="Change" onPress={() => {this.props.updateParentState({name: 'test})}} />
</View>
}
}