最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin1浏览0评论

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 ponent 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'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 ponent 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>
        );
    }
}
Share Improve this question edited Nov 16, 2019 at 16:18 Euridice01 asked Nov 3, 2019 at 16:36 Euridice01Euridice01 2,55812 gold badges48 silver badges80 bronze badges
Add a ment  | 

5 Answers 5

Reset to default 3 +25

I think best approach here is to make it a functional ponent. 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 ponentDidMount 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 ponent state. Then we will use a static ponent class method getDerivedStateFromProps to update your state based on your props reactively. It is called just like ponentDidUpdate and the returned value will be your new ponent state.

Based on your code, I assume that your this.props.objResponse.bar is ing from an API response as seen in your ment

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 ponent 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 parison later
      prevProps: { foo, bar },
      foo,
      bar,
    }
  }

  static getDerivedStateFromProps(props, state) {
    const { prevProps } = state;
    // Compare the ining 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 ponentDidMount() {
    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 ponentDidMount() as below

 ponentDidMount() {
    if (this.props.isFromHome) {
      this.setState({ isFromHome: true });
    } else {
      this.setState({ isFromHome: false });
    }
  }

when you called setState() it will re-render the ponent.

发布评论

评论列表(0)

  1. 暂无评论