Milos Maric
Milos Maric

Reputation: 13

Should child components have state of their own in ReactJS?

I'm developing a dialog with a specific look in ReactJS. The dialog should give user the possibility to choose start time and end time (something like an activity saying when the employee went to a pause and came back, meeting, etc.). Besides picking those two times, the dialog should give a text box to write a comment about the activity.

My idea was to make a reusable component inside the 'ActivityDialog' component. Since I need to choose two times (start and end time), I decided to make a reusable component called 'TimePicker' and use it twice. By specification, I need to make each of 4 digits in the time (2 in hours and 2 in minutes) to be selected (value changed) separately with arrow up / arrow down. Due to that, I've decided to make another reusable component which I named 'DigitPicker'.

  1. So, my question is, should TimePicker and DigitPicker have their own state (time and digit), or the state should be in the 'oldest' parent component (ActivityDialog)?
  2. In any of the cases, I think that the logic for calculating new time with tens/units changing should be in TimePicker component, not in the ActivityDialog component.

  3. If the answer on 1st question is that I only need the state inside the 'oldest' parent component, then my TimePicker and DigitPicker render methods are going to have the this.props.something inside HTML. Then my idea of those 2 components being reusable falls apart. Why? Well, lets take a look in the TimePicker-DigitPicker connection. From the TimePicker, I am going to pass a digit and callback which will change the value of the digit prop which will trigger re-rendering and the new digit will be shown. But when someone else takes my DigitPicker component and, he might not change the digit prop when I trigger the callback he passed to me. That would be solved if DigitPicker had its own state.

So my question is more about the concept of ReactJS, how should I use the parent-child connection, should the child have its own state or depend on the props that the parent sends? And how does that affect the fact that I want all tree components to remain reusable?

Many thanks for any advices.

Upvotes: 1

Views: 3067

Answers (3)

Nikki Koole
Nikki Koole

Reputation: 136

You more or less answer your own question; to have components that are reuseable, having them keep their own state is much nicer then depending on other components to feed it to them.

Separation of concerns and all, I can imagine your DigitPicker component being useful outside of the context of TimePickers and ActivityDialogs.

However, to actually let a parent respond to a change in a child, I tend to feed a child an eventHandler (bound to the parent) via its props. So when you pick a digit, an eventhandler living in TimePicker is triggered, which in turn might trigger an eventhandler in ActivityDialog.

But the state is living in the children.

edit: I took the time to actually build these components, its easier for me that way to reason about it

class ActivityDialog extends React.Component {
    render(){
        return (
            <div>
                <TimePicker></TimePicker>
                <TimePicker></TimePicker>
            </div>)
  }

}

class TimePicker extends React.Component {
    constructor() {
    super();
    this.state = {
      hours:0,
      minutes:0
    }
    this.onTimeChange = this.onTimeChange.bind(this);
  }

  onTimeChange(type, value) {
    if (type == 'hours') {
       this.setState({hours:value});
    }
    if (type == 'minutes') {
       this.setState({minutes:value});
    }
  }

  render() {
    return(
        <div> {this.state.hours} : {this.state.minutes}
            <div className="numberPicker">
            <NumberPicker handler={this.onTimeChange} type="hours" max={24}></NumberPicker>
            <NumberPicker handler={this.onTimeChange} type="minutes" max={60}></NumberPicker>
        </div>
      </div>
      );
  }
}

class NumberPicker extends React.Component {
    constructor() {
    super();
    this.onNumberChange = this.onNumberChange.bind(this);
  }

  onNumberChange(e){
    this.props.handler(this.props.type, e.target.textContent);
  }

  render(){
        var N = this.props.max; 
        var numbers = Array.apply(null, {length: N}).map(Number.call, Number)
        return <div className="number-container" > {
            numbers.map((number)=>{
                return(<div onClick={this.onNumberChange} key={number} className="number">{number}</div>);
            })
          }</div>
      }

}

conclusion : in this case you only need to keep the 'digits' as state of the TimePicker.

here is the jsfiddle https://jsfiddle.net/nikkikoole/69z2wepo/28489/

Upvotes: 0

TomW
TomW

Reputation: 4002

To present the opposite argument, generally it's more flexible and easier to understand things if you minimize the use of component state in React - it allows you to manage state outside of React using pure JS (e.g. using Flux or a MVC system like Backbone). In your case, your parent might need to track and process each 'time' change in any case - validating that the start time <= end time, etc. so it can highlight in red, say, when it's out of range or whatever.

However, you probably want to keep your API to your TimePicker simple - a simple 'value' prop that contains a Time object (or KISS - just a single number, seconds since midnight or something), and a onValueChanged callback that is passed back the new value when the user asks to change it. The component uses the value to work out what digits to render, and each digit and a callback down to each DigitPicker. If, after a change callback, the parent doesn't pass back the updated value, then presumably they didn't want the digit change to have any effect (perhaps because you've hit the min/max time that the parent wants you to use). If they did want the value to change, then they need to pass down the new value.

It comes down to whether you want to emulate a 'controlled' component or a 'non-controlled' one: see https://facebook.github.io/react/docs/forms.html for an explanation. In my large app, I have no uncontrolled ones...

Reuse is the same either way, but you have more control if you are specifying the value of the component rather than leaving it in control of its value.

Upvotes: 2

Daniel Macak
Daniel Macak

Reputation: 17093

It's perfectly valid to have a state inside reusable component, especially if it holds users input. You can still provide it with callbacks in props, which respond to onChange, onBlur or other events based on your needs, or you can communicate with your reusable component via refs, which is fairly flexible way to get user input.

The important thing is, that the reusable component should be as much independent on it's environment as possible. If you don't provide it with a state, the component (in case of your Pickers) will be dependent on props passed down from its parent expressing it's current user input on every rerender and on callbacks updating its current input somewhere higher in component hierarchy on every change, which is not what you want in every case, and it already forces you to make relatively large adjustments in you parent component, which misses the point of reusability.

Upvotes: 0

Related Questions