Reputation: 2568
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>
);
}
}
Upvotes: 4
Views: 4596
Reputation: 1568
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.
Upvotes: -1
Reputation: 2522
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.
Upvotes: 1
Reputation: 849
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>
);
}
Upvotes: 3
Reputation: 3644
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
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
Upvotes: 0
Reputation: 666
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>
);
}
}
Upvotes: 0