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

丶灬走出姿态 提交于 2020-01-24 06:55:13

问题


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

回答1:


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



回答2:


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.




回答3:


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



回答4:


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




回答5:


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.



来源:https://stackoverflow.com/questions/58682297/how-can-i-make-my-input-field-prefilled-with-data-and-editable-on-page-load

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!