Anyone know how to auto submit a Redux Form in React Native when certain conditions are met? My attempt below is throwing a warning.
In the doc there is an example for remote submitting, but it's using HTML form's <form onSubmit={handleSubmit}>. What is the equivalent of this in React Native?
My attempt: Submit when the form's input length >= 2
class MyClass extends React.Component {
constructor(props) {
super(props);
this.handlSubmitWrapper = this.handlSubmitWrapper.bind(this);
}
handlSubmitWrapper() {
const { handleSubmit } = this.props;
handleSubmit(() => console.log('submitting...'))();
}
getTextInput({ input: { onChange, value, ...otherProps }, autoSubmit }) {
if (value && value.length > 1) {
// triger submit
autoSubmit();
}
return (
<TextInput
onChangeText={onChange}
style={{height: 50, backgroundColor: '#666'}}
{...otherProps}
maxLength={2}
/>
);
}
render() {
return (
<View>
<Field name="myText"
component={this.getTextInput}
autoSubmit={this.handlSubmitWrapper}
/>
</View>
);
}
}
const MyForm = reduxForm({
form: 'setupPasscode',
})(MyClass);
export default connect()(MyForm);
warning:
ExceptionsManager.js:71 Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
You are calling the submit action when rendering a component. You cannot do this with react. You should instead use the TextInput onChange method to achieve that.
class MyClass extends React.Component {
constructor(props) {
super(props);
this.handlSubmitWrapper = this.handlSubmitWrapper.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handlSubmitWrapper() {
const { handleSubmit } = this.props;
handleSubmit(() => console.log('submitting...'))();
}
handleInputChange(event) {
const newText = event.nativeEvent.text;
if (newText && newText.length > 1) {
// triger submit
this.handlSubmitWrapper();
}
}
getTextInput({ input: { onChange, value, ...otherProps } }) {
return (
<TextInput
onChange={this.handleInputChange}
onChangeText={onChange}
style={{height: 50, backgroundColor: '#666'}}
{...otherProps}
maxLength={2}
/>
);
}
render() {
return (
<View>
<Field name="myText" component={this.getTextInput} />
</View>
);
}
}
const MyForm = reduxForm({
form: 'setupPasscode',
})(MyClass);
export default connect()(MyForm);
Related
I've tried to console.log the value of the text input but I get the error "undefined is not an object (evaluating 'this.state.inputValue')". What's the problem? Thank you!
class SearchScreen extends React.Component {
state = {
inputValue: "",
};
search() {
console.log(this.state.inputValue);
}
render() {
return (
<View>
<TextInput
onChangeText={
((inputValue) => this.setState({ inputValue }),
this.search)
}
value={this.state.inputValue}
/>
</View>
);
}
}
export default SearchScreen;
The problem is in the way you've implemented it. Please try as below...
class SearchScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
};
}
search() {
console.log(this.state.inputValue);
}
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => {
this.setState({ inputValue });
this.search();
}}
value={this.state.inputValue}
/>
</View>
);
}
}
export default SearchScreen;
This problem occurred because two things.
First:
The this.setState is a async function.
If you pass a function after the setState this will work like a .then() in a promisse.
Second:
If you pass one function after another separating them by ',' the rightmost function will be executed first
You can resolve this doing something like that:
onChange={ inputValue => {
this.setState({ inputValue });
this.search();
}}
Or you can try something like that:
class SearchScreen extends React.Component {
state = {
inputValue: "",
};
search = () {
console.log(this.state.inputValue);
}
setSearch = inputValue => {
// The function 'search' will be execute after the state was set
this.setState(
{ inputValue },
() => this.search()
);
}
render() {
return (
<View>
<TextInput
onChangeText={ inputValue => this.setSearch(inputValue) }
value={this.state.inputValue}
/>
</View>
);
}
}
export default SearchScreen;
You didn't set the value of state property. provide a value to setState.
this.setState({property : value})
The problem is this line:
onChangeText={
((inputValue) => this.setState({ inputValue }),
this.search)
}
You can use the short function notation only when your function has one statement:
(inputValue) => this.setState({ inputValue })
You actualy have 2 statements though, so you need to create a full function block using {}
(inputValue) => {
this.setState({ inputValue })
this.search()
}
I'm having issues getting one of my fields to pre-populate with info and be editable. I've tried moving around the code where it sets the field with the data and either it's blank and editable or shows the pre-populated data but the UI prevents me from editing it.
The issue I'm having is with the bar field. Putting it in the constructor pre-populates the field with info but the UI is preventing me from editing it. Why? Where should this field be set or how can I fix it? Do I need to call where this object gets populated before navigating to this page, so it gets populated during constructor initialization or..?
Here's the class component snippet:
export class FooBarBazComponent extends Component{
constructor(props){
super(props);
this.state = {
foo: "",
bar: ""
};
const fooDetails = this.props.navigation.state.params.fooDetails;
this.state.foo = fooDetails.foo;
}
render(){
const disabled = this.state.foo.length !== 5 || this.state.bar.length < 5;
//I didn't put this code in the constructor because this object is undefined in the constructor
if(this.props.objResponse) {
this.state.bar = this.props.objResponse.bar;
}
return(
<View style={Styles.inputRow}>
<View style={Styles.inlineInput}>
<FormLabel labelStyle={Styles.label}>FOO</FormLabel>
<TextInputMask
onChangeText={foo => this.setState({ foo })}
value={this.state.foo}
/>
</View>
<View style={Styles.inlineInput}>
<FormLabel labelStyle={Styles.label}>BAR</FormLabel>
<TextInputMask
onChangeText={bar => this.setState({ bar })}
value={this.state.bar}
/>
</View>
</View>
);
}
}
I think best approach here is to make it a functional component. You can use React Hooks for stateful logic and keep your code way more cleaner.
I'd destructure the props and set them directly in the initial state. Then I'd add some conditional logic for rendering the input fields only when the initial state is set. Done!
When you want to change the state, just use the set function!
import React, { useState } from 'react';
export default function FooBarBazComponent({ navigation, objResponse }) {
// Initiate the state directly with the props
const [foo, setFoo] = useState(navigation.state.params.fooDetails);
const [bar, setBar] = useState(objResponse.bar);
const disabled = foo.length !== 5 || bar.length < 5;
return (
<View style={styles.inputRow} >
{/* Only render next block if foo is not null */}
{foo && (
<View style={styles.inlineInput}>
<FormLabel labelStyle={Styles.label}>FOO</FormLabel>
<TextInputMask
onChangeText={foo => setFoo(foo)}
value={foo}
/>
</View>
)}
{/* Only render next block if objResponse.bar is not null */}
{objResponse.bar && (
<View style={styles.inlineInput}>
<FormLabel labelStyle={Styles.label}>BAR</FormLabel>
<TextInputMask
onChangeText={bar => setBar(bar)}
value={bar}
/>
</View>
)}
</View>
);
}
I see few problems in the code.
state = {
foo: "",
bar: ""
};
The above need to be changed like this
this.state = {
foo: "",
bar: ""
};
Or else put your code outside the constructor.
Then from this,
const fooDetails = this.props.navigation.state.params.fooDetails;
this.state.foo = fooDetails.foo;
to
this.state = {
foo: props.navigation.state.params.fooDetails,
bar: ""
};
Because you should not mutate the state directly. and you have your props in the constructor already.
Then from this,
if(this.props.objResponse) {
this.state.bar = this.props.objResponse.bar;
}
}
move this to componentDidMount or where you do your API call. You should not mutate state and you shouldn't update the state in render method which will create a loop.
And also update the state using this.setState method.
If you still face any problem then you need to check your TextInputMask Component after doing the above.
You should never assign props to the state directly. It is an absolute no-no. Also if possible try moving to react hooks, it is much simpler and cleaner than this approach.
export class FooBarBazComponent extends Component {
constructor(props)
{
state = {
foo: "",
bar: ""
};
const fooDetails = this.props.navigation.state.params.fooDetails;
this.state.foo = fooDetails.foo;
}
static getDerivedStateFromProps(props, state) {
if (props.objResponse && props.objResponse.bar !== state.bar) {
return {
...state,
bar: props.objResponse.bar
}
}
return null;
}
render() {
const disabled =
this.state.foo.length !== 5 || this.state.bar.length < 5;
return (
<View style={styles.inputRow}>
<View style={styles.inlineInput}>
<FormLabel labelStyle={Styles.label}>FOO</FormLabel>
<TextInputMask
onChangeText={foo => this.setState({ foo })}
value={this.state.foo}
/>
</View>
<View style={styles.inlineInput}>
<FormLabel labelStyle={Styles.label}>BAR</FormLabel>
<TextInputMask
onChangeText={bar => this.setState({ bar })}
value={this.state.bar}
/>
</View>
</View>
);
}
}
First we will save the current props as prevProps in your component state. Then we will use a static component class method getDerivedStateFromProps to update your state based on your props reactively. It is called just like componentDidUpdate and the returned value will be your new component state.
Based on your code, I assume that your this.props.objResponse.bar is coming from an API response as seen in your comment
I didn't put this code in the constructor because this object is undefined in the constructor
If possible, it is better to use functional component with React hooks instead of using class in the future.
Here are some clean sample codes for your reference.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class FooBarBazComponent extends React.Component {
constructor(props) {
super(props);
const { foo, bar } = props;
this.state = {
// save previous props value into state for comparison later
prevProps: { foo, bar },
foo,
bar,
}
}
static getDerivedStateFromProps(props, state) {
const { prevProps } = state;
// Compare the incoming prop to previous prop
const { foo, bar } = props;
return {
// Store the previous props in state
prevProps: { foo, bar },
foo: prevProps.foo !== foo ? foo : state.foo,
bar: prevProps.bar !== bar ? bar : state.bar,
};
}
handleOnChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
renderInput = (name) => (
<div>
<label>
{`${name}:`}
<input onChange={this.handleOnChange} type="text" name={name} value={this.state[name]} />
</label>
</div>
)
render() {
const { prevProps, ...rest } = this.state;
return (
<section>
{this.renderInput('foo')}
{this.renderInput('bar')}
<div>
<pre>FooBarBazComponent State :</pre>
<pre>
{JSON.stringify(rest, 4, '')}
</pre>
</div>
</section>
);
}
}
class App extends React.Component {
// This will mock an api call
mockAPICall = () => new Promise((res) => setTimeout(() => res('bar'), 1000));
state = { bar: '' }
async componentDidMount() {
const bar = await this.mockAPICall();
this.setState({ bar });
}
render() {
const { bar } = this.state;
return (
<FooBarBazComponent foo="foo" bar={bar} />
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Hopefully this gives you a general idea on how to do it.
Working example : https://codesandbox.io/s/react-reactive-state-demo-2j31u?fontsize=14
Try to setState() in componentDidMount() as below
componentDidMount() {
if (this.props.isFromHome) {
this.setState({ isFromHome: true });
} else {
this.setState({ isFromHome: false });
}
}
when you called setState() it will re-render the component.
I've been using React native for a month now but it's my first time to use a CheckBox in my application. So, lately I've been struggling to check a specific checkbox inside a Flatlist but now I can.
But upon testing my checkboxs I did notice that once I check a specific a CheckBox(or more than 1 checkbox) it doesn't UNCHECK.
So, my goal is to make a CheckBox that can check(ofcourse) and also uncheck, if ever a user mischeck or mistap a CheckBox.
Here's my code
export default class tables extends Component {
constructor(props){
super(props)
this.state = {
...
check: false
}
}
checkBox_Test = (item, index) => {
let { check } = this.state;
check[index] = !check[index];
this.setState({ check: check })
alert("now the value is " + !this.state.check);
alert("now the value is " + item.tbl_id);
console.log(item.tbl_id)
}
render() {
return(
<View>
....
<Flatlist
....
<CheckBox
value = { this.state.check }
onChange = {() => this.checkBox_Test(item) }
/>
....
/>
<View/>
)
}
}
Method 1: Make check an object
export default class tables extends Component {
constructor(props){
super(props)
this.state = {
...
check: {}
}
}
checkBox_Test = (id) => {
const checkCopy = {...this.state.check}
if (checkCopy[id]) checkCopy[id] = false;
else checkCopy[id] = true;
this.setState({ check: checkCopy });
}
render() {
return(
<View>
....
<Flatlist
....
<CheckBox
value = { this.state.check[item.tbl_id] }
onChange = {() => this.checkBox_Test(item.tbl_id) }
/>
....
/>
<View/>
)
}
}
Method 2: Make a separate item for each FlatList item
class ListItem extends Component {
constructor(props){
super(props)
this.state = {
...
check: false
}
}
checkBox_Test = (id) => {
this.setState((prevState) => ({ check: !prevState.check }));
}
render() {
return(
<View>
<CheckBox
value = { this.state.check }
onChange = { this.checkBox_Test }
/>
</View>
)
}
}
Let me know if it works for you
I'm starting with react native and trying to bind some actions to class methods but I'm getting some errors about methods not found.
I tried binding:
class AccountsScreen extends Component {
constructor (props) {
super(props)
this.userChange = this.userChange.bind(this)
this.state = {
user: '',
password: ''
}
}
render () {
return (
<View>
<Text>Pass</Text>
<TextInput
onChange={this.userChange}
/>
</View>
)
}
userChange (user) {
this.setState({user: user})
}
}
and arrow functions
class AccountsScreen extends Component {
constructor (props) {
super(props)
this.state = {
user: '',
password: ''
}
}
render () {
return (
<View>
<Text>Pass</Text>
<TextInput
onChange={(user) => this.userChange(user)}
/>
</View>
)
}
userChange (user) {
this.setState({user: user})
}
}
but I keep getting the same error:
"this.setState is not a function"
Totally stuck. Any ideas?
actually no need to make a function to set your state, you can just do this
<TextInput onChangeText={(user) => this.setState({user: user})} />
Actually it was a stupid mistake. I was using onChange instead of onChangeText method....
I have a project in react-native (0.23) with Meteor 1.3 as back-end and want to display a list of contact items. When the user clicks a contact item, I would like to display a checkmark in front of the item.
For the connection to Meteor DDP I use the awesome library inProgress-team/react-native-meteor.
import Meteor, { connectMeteor, MeteorListView, MeteorComplexListView } from 'react-native-meteor';
class ContactsPicker extends React.Component {
constructor(props) {
super(props);
this.state = {
subscriptionIsReady: false
};
}
componentWillMount() {
const handle = db.subscribe("contacts");
this.setState({
subscriptionIsReady: handle.ready()
});
}
render() {
const {subscriptionIsReady} = this.state;
return (
<View style={gs.standardView}>
{!subscriptionIsReady && <Text>Not ready</Text>}
<MeteorComplexListView
elements={()=>{return Meteor.collection('contacts').find()}}
renderRow={this.renderItem.bind(this)}
/>
</View>
);
}
The first problem is, that subscriptionIsReady does not trigger a re-render once it returns true. How can I wait for the subscription to be ready and update the template then?
My second problem is that a click on a list item updates the state and should display a checkmark, but the MeteorListView only re-renders if the dataSource has changed. Is there any way to force a re-render without changing/ updating the dataSource?
EDIT 1 (SOLUTION 1):
Thank you #user1078150 for providing a working solution. Here the complete solution:
'use strict';
import Meteor, { connectMeteor, MeteorListView, MeteorComplexListView } from 'react-native-meteor';
class ContactsPicker extends React.Component {
getMeteorData() {
const handle = Meteor.subscribe("contacts");
return {
subscriptionIsReady: handle.ready()
};
}
constructor(props) {
super(props);
this.state = {
subscriptionIsReady: false
};
}
componentWillMount() {
// NO SUBSCRIPTION HERE
}
renderItem(contact) {
return (
<View key={contact._id}>
<TouchableHighlight onPress={() => this.toggleSelection(contact._id)}>
<View>
{this.state.selectedContacts.indexOf(contact._id) > -1 && <Icon />}
<Text>{contact.displayName}</Text>
</View>
</TouchableHighlight>
</View>
)
}
render() {
const {subscriptionIsReady} = this.data;
return (
<View>
{!subscriptionIsReady && <Text>Not ready</Text>}
<MeteorComplexListView
elements={()=>{return Meteor.collection('contacts').find()}}
renderRow={this.renderItem.bind(this)}
/>
</View>
);
}
}
connectMeteor(ContactsPicker);
export default ContactsPicker;
You have to do something like this :
getMeteorData() {
const handle = Meteor.subscribe("contacts");
return {
ready: handle.ready()
};
}
render() {
const { ready } = this.data;
}