18

I have no idea why this is not working

Demo

I have the following es6 code

const {createFactory, createClass, DOM: { label, input, button }} = React;

const tester = createFactory(createClass({
  render() {
      return label({}
               ,`Name: ${this.props.name}`
               ,input({defaultValue: this.props.name})
               ,button({onClick: this.changeName}, "Change")
             )
  },
  changeName() {
    this.setProps({name: "Wilma"})
  }
}) )

React.render(tester({name: "Fred"}), document.querySelector('body'))

Clicking the button clearly changes the props, but the old defaultValue is still in the input! So what gives? What am I doing wrong? is this a bug? Is there a workaround?

George Mauer
  • 117,483
  • 131
  • 382
  • 612

3 Answers3

37

I found what seems to be a pretty good solution to this: Use the key prop to force rendering of an entirely new input.

In my particular case, I don't need the input to be controlled with its own onChange prop, as the form surrounding it ultimately controls the state within some store which populates the defaultValue. But the store's state might be asynchronously initialized/updated, and in which case the defaultValue should be updated. So here is a condensed version of my particular case:

import React, { PropTypes } from 'react';
import { Form } from 'provide-page';

const GatherContact = ({
  classes,
  onSubmit,
  movingContactName,
  movingContactEmail,
  movingContactPhone,
  userName,
  userEmail,
  userPhone
}) => (
  <Form onSubmit={onSubmit}>
    <div className={classes.GatherContact}>
      <h2 className={classes.GatherHeading}>
        How can we contact you?
      </h2>

      <input
        type="text"
        className={classes.GatherContactInput}
        placeholder="Name"
        name="movingContactName"
        key={`movingContactName:${movingContactName || userName}`}
        defaultValue={movingContactName || userName}
        required={true}
      />

      <input
        type="email"
        className={classes.GatherContactInput}
        placeholder="Email"
        name="movingContactEmail"
        key={`movingContactEmail:${movingContactEmail || userEmail}`}
        defaultValue={movingContactEmail || userEmail}
        required={true}
      />

      <input
        type="tel"
        className={classes.GatherContactInput}
        placeholder="Phone"
        name="movingContactPhone"
        key={`movingContactPhone:${movingContactPhone || userPhone}`}
        defaultValue={movingContactPhone || userPhone}
        required={true}
      />

      {userName
        ? undefined
        : (
          <input
            type="password"
            className={classes.GatherContactInput}
            placeholder="Password"
            name="userPassword"
            required={true}
            autoComplete="new-password"
          />
        )
      }
    </div>
  </Form>
);

GatherContact.propTypes = {
  classes: PropTypes.object.isRequired,
  onSubmit: PropTypes.func.isRequired,
  movingContactName: PropTypes.string.isRequired,
  movingContactEmail: PropTypes.string.isRequired,
  movingContactPhone: PropTypes.string.isRequired,
  userName: PropTypes.string.isRequired,
  userEmail: PropTypes.string.isRequired,
  userPhone: PropTypes.string.isRequired
};

export default GatherContact;
timbur
  • 558
  • 6
  • 10
  • 1
    This solution worked really well for me, thanks. But I'm a little confused about why the 'key' property and why does this works? Apart from this workaround, what is the 'key' property supposed to do to the input? It doesn't show up in the documentation. Would this workaround work with any other property name other than 'key'? – David Neto Dec 04 '16 at 19:20
  • 2
    @DavidNeto React uses the `key` property to know how to update child nodes. If the `key` were to remain the same, it would use the same exact DOM node and update its attributes as necessary. But since the `key` changes, it renders an entirely new DOM node. – timbur Jan 05 '17 at 00:47
  • thanks! this was exactly what I needed in order to force a re-rendering when only the defaultValue changes (I need to change only the defaultValue to change a calendar - rc-calendar - locale update) – abidibo Mar 22 '17 at 08:38
  • Thanks for that workaround, I hope there are no performance penalties for it. I had to use it for a day-field which was re-rendered too fast when I used controlled component with "value" (i.e. when deleting 10 to enter 21 for the day). Maybe this is just intended behaviour for uncontrolled components and we should look for a way to handle input typing on controlled components with a timing function before re-rendering, which seems a bit too much of effort for me... – Christof Kälin Aug 17 '17 at 15:40
  • If you have multiple `` elements , which is very difficult for troubleshooting especially when there is no warning message about that . – Ham May 07 '21 at 16:42
8

You only specify its default value, but don't tell it to change its value with a change to props.

,input({value: this.props.name})

Will change the value when this.props.name changes.

http://output.jsbin.com/melitecimo

Michael Parker
  • 12,724
  • 5
  • 37
  • 58
  • 2
    yes, I know what `value` does, but it also locks the input so that it cannot change. Plenty of workflows to get around that but this is a simple scenario in which [the reactjs docs seem to recommend using defaultValue](http://facebook.github.io/react/docs/forms.html#uncontrolled-components). In any case, that's not a good reason for something that sneakily stores state in the DOM – George Mauer Jun 11 '15 at 23:25
  • 1
    `defaultValue` only specifies the component's initial value when mounting. It isn't expected behavior for its value to continue to change with the props you gave it. I would recommend something like the LinkedStateMixin, but I'm not sure how well it behaves with es6. https://facebook.github.io/react/docs/two-way-binding-helpers.html if you're interested. – Michael Parker Jun 11 '15 at 23:31
  • 2
    Right answer but [I really see this as a bug](https://github.com/facebook/react/issues/4101) – George Mauer Jun 11 '15 at 23:40
  • I agree with @GeorgeMauer that this could be construed as a bug as there are certain use cases where the input value being locked causes problems. Feeding the value into a component - such as a date picker - that relies on it's own state to update it's input value, prevents the date picker from working because the value is locked through the value prop. Timbur has the hacky solution to such use cases below, but one could also use something like redux to render/update the correct input value across components, so this solution is still correct. Upvoted – colemerrick Sep 28 '17 at 02:19
2

If you want to change your defaultValue using State then please use key in your component. That's must be random generated.

Re-render DefaultValue when Value Changes in React

Like Below:

  const [keyData, setKeyData] = useState("Test");
<input 
  type="text" 
  defaultValue={keyData} 
  key={"OKAYG_" + (10000 + Math.random() * (1000000 - 10000))}
/>
Hari Om
  • 21
  • 1