Euridice01
Euridice01

Reputation: 2568

How can I make my input field prefilled with data and editable on page load?

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

Answers (5)

Rajan
Rajan

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

Ashwin Mothilal
Ashwin Mothilal

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

Jurrian
Jurrian

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

junwen-k
junwen-k

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

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

Upvotes: 0

Aritra Ghosh
Aritra Ghosh

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

Related Questions