2

In ReactJS, I'm writing a stateless component;
Since I've read avoiding unnecessary states is best practice.

The component represents an input field which executed a function when the input box contains a value.

    export const InputField = (props) => {
      
      const InputFieldContentsChanged = (event) => {
        props.onChange(event.target.value);
      };

      return (
        <div data-component="input-field"
          className={(props.value !== "" ? "value": "")}>
          <input type={props.type} value={props.value} onChange={InputFieldContentsChanged} />
          <span className="bar"></span>
          <label>{props.label}</label>
        </div>
      );
    };

    InputField.PropTypes = {
      type: PropTypes.oneOf([ "text", "password" ]).isRequired,
      label: PropTypes.string.isRequired,
      value: PropTypes.string,
      onChange: PropTypes.func.isRequired
    }

Now, I've created another component which just is a sample to test the component above. This looks like the following:

    export const SampleComponent = (props) => {
      
      let componentUsername = "";
      
      const onUsernameChanged = (username) => {
        componentUsername = username;
      };
      
      return (
        <InputField type="text" label="Username" value={componentUsername} onChange={onUsernameChanged} />
      );
    };

So, I'm binding the value to a custom variable in the component which is changed when the contents of the input field does change.

How does it come that the input field component does not update itself with the new username?

Kind regards,

Zach
  • 539
  • 1
  • 4
  • 22
Complexity
  • 5,682
  • 6
  • 41
  • 84
  • Can you maybe get a fiddle up and running? – Borjante Apr 19 '17 at 10:52
  • 1
    Stateless components are well stateless. They should be implemented as pure functions. Return value should only depend on arguments (props). Since nothing is changing props - no rerender occurs. But your components are not actually stateless, they do have mutable internal state (input value). So you need implement either both or the container one as statefull component. – Yury Tarabanko Apr 19 '17 at 10:55

2 Answers2

9

I'm writing a stateless React component since it's best practice to avoid state when not needed.

In your code you are trying to use your own kind of "state" though, and it's just a variable (componentUsername). But since it's not React state, the component does not re-render upon the change of the variable. React simply doesn't know about the change.

So, either use the usual setState instead of re-assigning the your own "state" variable, or put the logic in the parent component and pass the componentUsername to the SampleComponent via props:

const SampleComponent = props => (
  <input type="text" onChange={props.onChange} value={props.value} />
);

class ParentComponent extends React.Component {
  constructor() {
    super();
    this.state = { value: '' };
    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(e) {
    console.log(e.target.value);
    this.setState({ value: e.target.value });
  }

  render() {
    return (
      <SampleComponent
        value={this.state.value}
        onChange={this.handleInputChange}
      />
    );
  }
}

ReactDOM.render(<ParentComponent />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Fabian Schultz
  • 18,138
  • 5
  • 49
  • 56
  • Thanks for this. If I follow you correctly, the "top-root" component should have "state" in order to trigger a re-render, is that correct? – Complexity Apr 19 '17 at 10:57
  • Another thing, If I assign the componentUsername as a property to the InputField, how can I trigger a re-render then because a component cannot update it's own props? – Complexity Apr 19 '17 at 10:59
  • Yes, that's correct! Of course there's exceptions when it comes to state containers like Redux or Mobx. – Fabian Schultz Apr 19 '17 at 10:59
  • Because the parent component updates the props of `SampleComponent`. `componentUsername` in this case should be part of the state of the parent. – Fabian Schultz Apr 19 '17 at 11:00
  • Ok, that makes it a lot clear. Can you advice me just the following? Should I maintain the state in the parent component, or should I use internal state in the InputField component itself? – Complexity Apr 19 '17 at 11:01
  • That's really up to you and depends where you need the state. I am assuming that there will not be anymore logic inside `SampleComponent`, so it's probably best to use the parent's state and leave `SampleComponent` stateless. – Fabian Schultz Apr 19 '17 at 11:07
  • Thanks, so basically, you're saying that I should set my state on the smallest possible component, correct my if I'm wrong. I would only need to set the state on the parent component if I needed it? – Complexity Apr 19 '17 at 11:11
  • Usually you want to pass state down as much as you can so there's less fragmented state to worry about. I added a working example to my answer. – Fabian Schultz Apr 19 '17 at 11:22
5

The idea of functional components is to not perform any changes to the state or props.

Since there is no trigger to re-render you component you won't see any change.

Change this React.Function to a React.Component.

const InputField = (props) => {  
  const InputFieldContentsChanged = (event) => {
    console.log(event.target.value);
    props.onChange(event.target.value);
  };

  return (
    <div data-component="input-field"
      className={(props.value !== "" ? "value": "")}>
      <input type={props.type} value={props.value} onChange={InputFieldContentsChanged} />
      <span className="bar"></span>
      <label>{props.label}</label>
    </div>
  );
};

class SampleComponent extends React.Component {
  constructor() {
    super();
    this.state = { componentUsername : ""};
  }
  
  onUsernameChanged = (username) => {
    console.log(username);
    this.setState({componentUsername: username});
  }
  
  render() {
    return (
      <InputField type="text" label="Username" value={this.state.componentUsername} onChange={this.onUsernameChanged} />
    );
  }
};

ReactDOM.render(<SampleComponent/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
simmer
  • 2,639
  • 1
  • 18
  • 22
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400