Adding checked checkboxes to an array and removing the unchecked ones - react native - react-native

What I need to do is - add/remove the name of each checkbox(which are checked/unchecked by the user) in an array and send to the server. I am stuck in the following code. Any help is appreciated. Thankyou
class App extends Component<Props> {
render() {
return (
<View style={{ padding: 15 }}>
{
response.map(
item => {
return (
<CheckBoxItem label={item.name} />
);
}
)
}
</View>
);
}
}
CheckBoxItem.js
class CheckBoxItem extends Component<Props> {
state = {
check: false,
problemTypeArray: [],
}
changeArray = (label) => {
let array = [...this.state.problemTypeArray, label];
let index = array.indexOf(label);
console.log('array', array);//returns array with length 1 all the time
}
render() {
return (
<View>
<CheckBox value={this.state.check} onValueChange={(checkBoolean) => { this.setState({ check: checkBoolean }); this.changeArray(this.props.label); }} />
<MyText>{this.props.label}</MyText>
</View>
);
}
}
export default CheckBoxItem;

The real trick to this is to maintain a list of the selected items in the parent component. Each CheckBoxItem can control its own state but you will need to pass a value back to the parent component each time it is checked/unchecked.
As you haven't shown where your CheckBox component has come from, I will show you how to do it using the react-native-elements CheckBox. The principles can then be applied to your own CheckBox.
Here is the App.js
import CheckBoxItem from './CheckBoxItem'
export default class App extends React.Component {
// set some initial values in state
state = {
response: [
{
name:'first'
},
{
name:'second'
},
{
name:'third'
},
{
name:'fourth'
},
{
name:'fifth'
},
{
name:'sixth'
},
],
selectedBoxes: [] // this array will hold the names of the items that were selected
}
onUpdate = (name) => {
this.setState(previous => {
let selectedBoxes = previous.selectedBoxes;
let index = selectedBoxes.indexOf(name) // check to see if the name is already stored in the array
if (index === -1) {
selectedBoxes.push(name) // if it isn't stored add it to the array
} else {
selectedBoxes.splice(index, 1) // if it is stored then remove it from the array
}
return { selectedBoxes }; // save the new selectedBoxes value in state
}, () => console.log(this.state.selectedBoxes)); // check that it has been saved correctly by using the callback function of state
}
render() {
const { response } = this.state;
return (
<View style={styles.container}>
{
response.map(item => <CheckBoxItem label={item.name} onUpdate={this.onUpdate.bind(this,item.name)}/>)
}
</View>
);
}
}
Here is the CheckBoxItem
import { CheckBox } from 'react-native-elements'
class CheckBoxItem extends Component<Props> {
state = {
check: false, // by default lets start unchecked
}
onValueChange = () => {
// toggle the state of the checkbox
this.setState(previous => {
return { check: !previous.check }
}, () => this.props.onUpdate());
// once the state has been updated call the onUpdate function
// which will update the selectedBoxes array in the parent componetn
}
render() {
return (
<View>
<CheckBox
title={this.props.label}
checked={this.state.check}
onPress={this.onValueChange}
/>
</View>
);
}
}
export default CheckBoxItem;
Explanation
When a CheckBoxItem is created two things are passed to it. One is a label and the second is an onUpdate function. The onUpdate function links the CheckBoxItem back to the parent component so that it can manipulate the state in the parent.
The onUpdate function takes the previous value of the state, before this update is applied, and it looks to see if the name of the checkbox exists in the selectedBoxes array. If it exists in the selectedBoxes array it is removed, otherwise it is added.
Now there exists an array in the parent component that you can access that contains all that items that have been selected.
Snack
Want to try out my code? Well I have created a snack that shows it working https://snack.expo.io/#andypandy/checkboxes
Setting state
You may have noticed that I am doing some unusual things with setState. I am making sure that setState gets called properly by using the previous value of the state and then applying my updates to that. I am also using the fact that setState takes a callback to perform actions after the state has been updated. If you would like to read more here are some great articles on setState.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6

Related

Force external component to re-render with new props

I am trying to set up e-mail sign-up. I have a screen with a TextInput, which I want to reuse. I have an EmailConnector from which I navigate to the TextInputScreen. This TextInputScreen containt a TextInputComponent. Here, the user enters his email. If the email is invalid, I throw an error and try to update the error message in my TextInputScreen.
The problem that I am facing right now is that the error message from my TextInputComponent is not updated when an error is thrown.
The flow is this:
The user taps on "Sign-up with email" in a separate screen -> openEmailScreen is called
The user inputs an email and taps on the keyboard "Done" button -> inputReceived is called
If the email is invalid -> an error is thrown in inputReceived and the error message is displayed in TextInputViewComponent
Refreshing the errror message in step #3 is NOT working at the moment and I can't figure out how to get it working.
This is my EmailConnector:
export default class EmailConnector {
static keyboardTypes = {
email: 'email-address',
default: 'default',
};
static openEmailScreen = async navigation => {
navigation.navigate('TextInputScreen', {
placeholder: strings.onboarding.email_flow.email_placeholder,
keyboardType: this.keyboardTypes.email,
onKeyboardPressed: () => this.inputReceived(),
errorMessage: 'placeholder message',
})
}
//method called when the "Done" button from the keyboard is pressed
static inputReceived = () => {
try {
const email = new SignUpUserBuilder().setEmail('testexample.com').build();//used to validate the email
}
catch(error) {
console.log(error);
****//HERE I need to figure out a way to change props.errorMessage and force TextInputViewComponent to rerender****
<TextInputViewComponent errorMessage = 'Invalid email'/>;
const viewComponent = new TextInputViewComponent();
viewComponent.forceUpdate();
}
}
}
This is my TextInputScreen:
class TextInputScreen extends Component {
render() {
return (
<View style={styles.rootView}>
<TextInputViewComponent
placeholder={this.props.route.params.placeholder}
keyboardType={this.props.route.params.keyboardType}
onKeyboardPressed={this.props.route.params.onKeyboardPressed}
errorMessage={this.props.route.params.errorMessage}
/>
</View>
)
}
}
export default TextInputScreen;
And this is my TextInputViewComponent:
class TextInputViewComponent extends Component {
constructor(props) {
super(props);
this.state = {
shouldRefreshComponent: false
}
}
refreshComponent = () => {
this.setState({
shouldRefreshComponent: true
})
}
render() {
return (
<View>
<TextInput
placeholder={this.props.placeholder}
placeholderTextColor={colors.placeholder}
keyboardType={this.props.keyboardType}
style={styles.textInput}
onSubmitEditing= {() => this.props.onKeyboardPressed()}
/>
<Text
style={{fontSize: 18, color: colors.white}}
ref={Text => { this._textError = Text}}>
{this.props.errorMessage}
</Text>
</View>
)
}
}
export default TextInputViewComponent;
From the inputReceived method, I have tried calling forceUpdate and setState for the TextInputViewComponent, but in both cases I get the message: "Can't call forceUpdate/setState on a component that is not yet mounted"
Any help will be greatly appreciated!
In general terms if I want a parent component to mutate it's data or update when a child component changes I pass the child a function to its props.
For example
class Parent {
state = {
value: 1
}
updateValue() {
this.setState({value: 2})
}
render() (
<Child
updateValue={this.updateValue}
/>
)
That way I can call the function inside the child to update the parent's state.
class Child {
updateParent() {
this.props.updateValue()
}
}

react-select (AsyncSelewhen typing) not deleting

I have a dropdown using AsyncSelect that when trying to clear it up it doesn't clear. As I have the setup right now I have to: display a previously selected value upon rendering (set by the state, which in turn gets its initial value from a prop), be able to update this value (onChange), be able to type into the input a string to search. Right now, after having a value pre-populated, when I click on the dropdown to type and search, my input doesn't get cleared and I don't see what I input, almost as if the input wasn't allowing edits.
Update: for example, when I click over the drop down I see the cursor but if I hit "delete" the value is not reset.
Any help will be appreciated it.
import React, { Component } from 'react';
import AsyncSelect from 'react-select/async';
class DropDown extends Component {
constructor(props) {
super(props);
this.state = {
selectedValue: null,
loaded: false,
};
this.onChange = this.onChange.bind(this);
this.loadOptions = this.loadOptions.bind(this);
}
componentDidMount() {
const { value } = this.props;
this.setState({ selectedValue: value, componentMounted: true });
}
onChange(value) {
const { updateParent } = this.props;
this.setState({ selectedValue: value });
updateParent(value);
}
// this returns a promise with my data
loadOptions() {
const { getDropDownOptions } = this.props;
return getDropDownOptions();
}
render() {
const { selectedValue, loaded } = this.state;
return (
loaded && (
<AsyncSelect
defaultOptions
isClearable
inputValue={selectedValue}
onChange={event => this.onChange(event)}
loadOptions={this.loadOptions}
{...{ ...this.props }}
/>
)
);
}
}
DropDown.defaultProps = {
isClearable: true,
};
export default DropDown;
You are passing the selected option which is of shape { value : '1', lable:'apple' } as the input value to the <AsyncSelect/>, there is no need to explicitly pass the input value into the component since it will be managed from inside Select component. Just change,
<AsyncSelect
...
inputValue={selectedValue}
...
/>
to
<AsyncSelect
...
value={selectedValue}
...
/>

React Native setState does not refresh render

I try to get which is not active (in term of NativeBase.io - https://docs.nativebase.io/Components.html#button-def-headref, which simply means that it has no background color) and after I click it, it becomes active (it has a background color).
I define button like this:
<Button active={this.state.selected} onPress={() => this.select()} first>
<Text>Puppies</Text>
</Button>
selected variable in my state is by default false. When I run the application, it works correctly.
The select() method is implemented:
select() {
this.setState({ selected: true })
}
I expect that after I click on the button, it should change its background but it isn't. I check the value of this.state.selected and it changes appropriately. What I'm doing wrong?
export default class MyComponent extends Component {
state = {
selected: false
}
handlePress = () => {
const { selected } = this.state;
this.setState({
selected: !selected,
})
}
render() {
const { selected } = this.state;
return (
<Button active={selected} onPress={this.handlePress} first>
<Text>Puppies</Text>
</Button>
);
}
}

how can i set a state variable as component's property in react native?

i started learning react native and building an android app.so i have facing some issue with setting and getting a component's property.here is my code
i have two components named as content-container and bar-chart.
inside content-container ,here is my code block:
state = {
barChartResponse: {},
arcChartResponse: {},
stackChartResponse: {},
lineChartResponse: {},
token:'abc',
};
componentWillMount() {
this.setState({token:'xyz'});
}
render() {
return (
<ScrollView>
<BarChart chartData = {this.state.token} />
</ScrollView>
);
}
now i am trying to get this property inside bar-chart component as follows:
constructor(props) {
super(props);
Alert.alert("ChartData is : ",props.chartData);
}
it displays me value what i set in state object by default i.e. abc, but i want updated value.
please help me to find out what am i doing wrong....... thanks in advance.
You can use componentWillRecieveProps but it is deprecated and in RN>54 you can use componentDidUpdate or getDerivedStateFromProps to get state from parent like this:
componentDidUpdate(nextProps){
if (this.props.chartData !== nextProps.chartData) {
alert(nextProps.chartData)
}
}
or
static getDerivedStateFromProps(props, current_state) {
if (current_state.chartData !== props.chartData) {
return {
chartData: props.chartData,
}
}
}
You need to update state of parent component it will automatically reflect in child component but next time you will receive in componentWillRecieveProps(nextProps) then render method.
for example:
state = {
barChartResponse: {},
arcChartResponse: {},
stackChartResponse: {},
lineChartResponse: {},
token:'abc',
};
componentWillMount() {
this.setState({token:'xyz'});
}
updateState = () => {
this.setState({token: "newToken"})
}
render() {
return (
<ScrollView>
<Button onPress={this.updateState}>update State</Button>
<BarChart chartData = {this.state.token} />
</ScrollView>
);
}
in BarChart.js
componentWillRecieveProps(nextProps) {
// you can compare props here
if(this.props.chartData !== nextProps.chartData) {
alert(nextProps.chartData)
}
}

Refresh overview scene after changing state in another scene with react / redux / react-native-router-flex

Most simplified working example provided in github !!!
I have a simple app to learn building apps with react native and redux. From my understanding if you display data from the redux state in your render method and then values of this state is changed, then the value will be changed as well and react rerenders all components which needs to be rerendered due to the state change.
I have the application available on github: https://github.com/schingeldi/checklist
Its really simple. I have an overview, if you click on the status of an entry, you get to a detailed page. If you click on "Mark xxx" the status in changed in the redux state (according to logs) but its not refreshed in the overview scene.
Basically I have an Overview.js:
class Overview extends Component {
constructor(props) {
super(props);
this.state = {fetching:false};
}
entries() {
// console.log("Overview");
// console.log(this.props);
// console.log(this.props.entries);
return Object.keys(this.props.entries).map(key => this.props.entries[key]);
}
componentDidMount() {
this.setState({fetching:true});
this.props.actions.getEntries()
.then( (res) => {
this.setState({fetching: false});
})
}
handleChange(entryId) {
Actions.detail({id: entryId});
}
render() {
return (
<View>
<ScrollView>
{ !this.state.fetching && this.entries().map((entry) => {
return (
<TouchableHighlight key={entry.id}>
<View >
<Text>{entry.name}</Text>
<TouchableHighlight onPress={(entryId ) => this.handleChange(entry.id)}><Text>{entry.status}</Text></TouchableHighlight>
<Text>---------------------------</Text>
</View>
</TouchableHighlight>
)
}
)
}
{this.state.fetching ? <Text>Searching </Text> : null }
</ScrollView>
</View>
)}
}
function mapStateToProps(state) {
return {entries: state.default.entries };
}
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(actions,dispatch)};
}
export default connect(mapStateToProps, mapDispatchToProps)(Overview);
When clicking on the Status ( {entry.status} ) I open another Scene Details.js:
class Detail extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentWillMount() {
this.setState({
entry: this.props.entries[this.props.id]
})
}
patchEntry(newStatus) {
console.log("Details: patchEntry with " + this.props.id +" and " + newStatus );
this.props.actions.patchEntry(this.props.id, newStatus);
}
render() {
return (
<View>
<Text>{this.state.entry.name}</Text>
<TouchableHighlight onPress={() => this.patchEntry('done')}><Text>Mark done</Text></TouchableHighlight>
<TouchableHighlight onPress={() => this.patchEntry('cancelled')}><Text>Mark cancelled</Text></TouchableHighlight>
</View>
)
}
}
function mapStateToProps(state) {
console.log(state);
return {entries: state.default.entries };
}
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(actions,dispatch)};
}
export default connect( mapStateToProps, mapDispatchToProps)(Detail);
And I have an action and a reducer which are called perfectly fine when one of the TouchableHighlights are pressed. I even see in the logs that the state is changed when outputting the whole state.
But my question is, how do I get the status refreshed on the Overview scene, once I got back (pop) from the Detail scene?
If you need anymore information let me know, but it should be simple to reproduce as I wrote a whole working app. Just clone, npm install and run it.
Thanks a lot for your help.
I did a quick look into your code and here are some suggestions/information.
In you Detail.js file you're setting your state once the component is mounted.
When you update your redux store and get the refreshed props, it won't update your UI because it's reflecting your state, and your state won't get the new value because you're only setting it on componentWillMount method. Check more information here in the docs.
Also it seems it's not very clear for you when to use the React component's state.
In this example, from Detail.js file you don't need the component's state at all. You can compute that value directly from the properties.
Ex:
render() {
const entry = this.props.entries[this.props.id];
return (
<View>
<Text>{entry.name}</Text>
<TouchableHighlight onPress={() => this.patchEntry('done')}><Text>Mark done</Text></TouchableHighlight>
<TouchableHighlight onPress={() => this.patchEntry('cancelled')}><Text>Mark cancelled</Text></TouchableHighlight>
</View>
)
}
You could even do that inside your mapStateToProps function. More info here.
Ex:
function mapStateToProps(state, ownProps) {
return {
entries: state.default.entries,
entry: state.default.entries[ownProps.id],
};
}
It seems your Overview.js file is OK regarding the UI being updated, because it's render method is reflecting the props and not it's state.
UPDATE 06/27
I've just checked your reducers and you may have some fixes to do there as well.
case ENTRY_PATCHING:
let patchedEntries = state.entries;
patchedEntries[action.data.entryId].status = action.data.newStatus;
return {...state,
entries: patchedEntries
}
In this reducer you're mutation your state, and you must not do that. The redux store can't be mutated. You can check more details about it here http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html
So, fix example:
case ENTRY_PATCHING:
const patchedEntry = {
...state.entries[action.data.entryId],
status: action.data.newStatus
}
return {
...state,
entries: {
...state.entries,
[action.data.entryId]: patchedEntry,
}
}