React Native setState does not refresh render - react-native

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

Related

loading on navigating between screens

I'm new in RN. When I want to navigate between screens I create this function:
displayScreen2 = () => {
this.props.navigation.navigate("screen2")
}
and I call it in onPress={this.displayScreen2}
with TouchableOpacity or any Touchable when the user clicks he has to wait 1 second or 2 before displaying the screen. So what I want is to change the Touchable icon to an loader.
It's simple if I use a conditional rendering but I don't know how to do it now, when I have to change my state? Any suggestions?
this is my approach:
<TouchableOpacity
style={Styles.topButton}
onPress= {() => {
this.setState({loading: 'load'},
() => {
displayScoreListView()
// this.setState({loading: 'icone'})
}
)
}}
>
<Text style={Styles.scoreListButtonTextRed}>{this.state.loading}</Text>
that not work, tha state change but visualy not because if I return to the first screen I have 'load' in the text component
You could create a custom component wrapping whatever Touchable you prefer, I've used this technique in my production apps before. The button has it's own state which allows you to automatically display a loading indicator when necessary.
export class ButtonWorker extends Component {
state = {
working: false
}
onButtonPress = () => {
this.setState(
{ working: true },
() => {
this.props.onPress(this.onWorkFinished);
}
);
}
onWorkFinished = () => {
this.setState({ working: false });
}
render() {
return (
<TouchableOpacity>
{this.state.working ? (
<ActivityIndicator />
) : (
this.props.children
)}
</TouchableOpacity>
);
}
}
And then use it like a normal button with additional logic!
export class NavigationScreen extends Component {
navigate = (done) => {
// ... Asynchronous logic goes here
done();
this.props.navigation.navigate("Screen2");
}
render() {
return (
<Fragment>
{/* ... */}
<ButtonWorker onPress={this.navigate} />
</Frament>
);
}
}

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

Adding checked checkboxes to an array and removing the unchecked ones - 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

Flat List not re-rendering even when I change a state

Expo-cli: 2.2.0
React-Navigation: 2.18.0
I have the following two screens for React Navigation, where one is to input the form details and another is the screen where the user can either edit on the submissions or confirm.
My Input interface looks like this:
export default class PickDropInterface extends
React.Component<NavigationProps<>> {
this.state = { tasks: [],
}
onSubmit = () => { this.props.navigation.navigate("Confirmation",
{tasks: this.state.tasks, deleteItem: this.deleteItem.bind(this)}); }
deleteItem = (key) => { var filteredTasks =
this.state.tasks.filter(function (item) { return (item.key !==key);
});
render() {
return (
<ItemDetail onSubmit={this.onSubmit} /> ) }
My Confirmation screen looks like this:
export default class Confirmation extends React.Component<NavigationProps<>> {
this.state={
refresh: false,
}
deleteItem = (key) => {
this.props.navigation.state.params.deleteItem(key);
this.setState({
refresh: !this.state.refresh
})
}
_renderItem =({ item }) =>
(
<View style={styles.cardStyle}>
<Button
primary
label="Delete" onPress= {() => {this.deleteItem(item.key)}} /></View>
)
render() {
return (
<FlatList data={task}
renderItem= {this._renderItem}
keyExtractor= {(item) => item.key.toString()}
extraData={this.state} />
)
}
Expected Output:
The delete button to prompt refresh in the FlatList and show the new Task list.
Current Output:
https://www.youtube.com/watch?v=RmrurTBQpak&feature=youtu.be
I don't know why FlatList didn't re-render, but I found a much simpler solution to what I wanted.
I used conditional rendering instead and I kind of think it's the way to do it instead of navigating to the other screen.
What I did is:
I made a new state called 'orderComplete' and set it to false as default.
Whenever, it is 'false', I made inputInterface above to render whereas it was 'true', I made the above ConfirmationScreen render.
More on Conditional Rendering can be found in React's official documentation.
FlatList above works like a charm now.

react-native reseting TextInput after ClearTextOnFocus

I am building a form in React-Native and have set ClearTextOnFocus to true as it is easier to handle dynamic formating for editing.
I am trying to add a reset function by setting all local state to the redux store, but if the user has not typed anything in a selected TextInput, the local state has not changed, and react native does not re-render the TextInput; leaving it blank.
Anyone have any thoughts on how I can unclear the TextInput or force React to re-render. Code is a work in progress, but here are the relevant bits.
Thanks
class GoalScreen extends Component {
componentWillMount = () => this.setPropsToState();
onReset = () => {
this.setPropsToState();
}
onChange = text => this.setState({ [text.field]: text.input });
setPropsToState = () => {
const { name } = this.props.goal;
this.setState({ name });
};
render() {
const { name } = this.state;
return (
<View style={styles.screenContainer}>
<Text style={styles.text}> Name </Text>
<TextInput
placeholder="a brand new bag"
keyboardType="default"
autoCorrect={false}
style={styles.inputField}
clearTextOnFocus
onChangeText={text => this.onChange({ input: text, field: 'rate' })}
value={name}
/>
</View>
}
}
So, I'm not using Redux, and my use case might be a bit different than yours, but I thought my solution might still be relevant here, if only to confirm that (after hours of wrangling with this) it appears that passing true to the clearTextOnFocus prop prevents further updates to a TextInput component.
I tried every conceivable workaround (like setNativeProps(), forceUpdate()) but nothing worked, so I ended up having to basically write my own logic for clearing and resetting the input text.
This component should 1) clear input text on focus and then 2) reset it to its previous value if the user hasn't pressed a key:
class ResettableInput extends Component {
state = {
Current: this.props.value,
Previous: ""
};
KeyPressed = false;
//cache current input value for later revert if necessary, and clear input
onFocus = () => {
this.setState({ Previous: this.state.Current, Current: "" });
};
//record whether key was pressed so input value can be reverted if necessary
onKeyPress = () => {
this.KeyPressed = true;
};
onChangeText = text => {
this.setState({ Current: text });
};
//if no key was pressed, revert input to previous value
onBlur = () => {
if (!this.KeyPressed) {
this.setState({ Current: this.state.Previous, Previous: "" });
}
};
render = () => {
return (
<TextInput
onChangeText={this.onChangeText}
value={this.state.Current}
onBlur={this.onBlur}
onFocus={this.onFocus}
onKeyPress={this.onKeyPress}
/>
);
};
}