0

See this gist for the complete picture.

Basically I will have this form:

form

When you click the plus, another row should appear with a drop down for day and a time field.

I can create the code to add inputs to the form, however I'm having trouble with the individual components (selectTimeInput is a row) actually updating their values.

The onChange in the MultipleDayTimeInput is receiving the correct data, it is just the display that isn't updating. I extremely new to react so I don't know what is causing the display to not update....

I think it is because the SelectTimeInput render function isn't being called because the passed in props aren't being updated, but I'm not sure of the correct way to achieve that.

Thinking about it, does the setState need to be called in the onChange of the MultipleDayTimeInput and the input that changed needs to be removed from the this.state.inputs and readded in order to force the render to fire... this seems a little clunky to me...

2
  • can you post code? i have an idea of what it could be, but I need to see some code Commented Nov 23, 2016 at 22:15
  • right at the top of the post is a gist link! @jakeaaron Commented Nov 23, 2016 at 22:16

2 Answers 2

3

When you update the display value of the inputs in state, you need to use this.setState to change the state data and cause a re-render with the new data. Using input.key = value is not the correct way.

Using State Correctly
There are three things you should know about setState().

Do Not Modify State Directly
For example, this will not re-render a component:

// Wrong
this.state.comment = 'Hello';
Instead, use setState():

// Correct
this.setState({comment: 'Hello'});
The only place where you can assign this.state is the constructor.

read more from Facebook directly here

I would actually suggest a little bit of a restructure of your code though. It's not really encouraged to have components as part of your state values. I would suggest having your different inputs as data objects in your this.state.inputs, and loop through the data and build each of the displays that way in your render method. Like this:

suppose you have one input in your this.state.inputs (and suppose your inputs is an object for key access):

inputs = {
    1: {
        selectedTime: 0:00,
        selectedValue: 2
    }
}

in your render, do something like this:

render() {
    let inputs = Object.keys(this.state.inputs).map((key) => {
        let input = this.state.inputs[key]
        return (<SelectTimeInput
            key={key}
            name={'option_' + key}
            placeholder={this.props.placeholder}
            options={this.props.options}
            onChange={this.onChange.bind(this, key)}
            timeValue={input.selectedTime}
            selectValue={input.selectedValue} 
         />)      
    )}
    return (
        <div>
            <button className="button" onClick={this.onAddClick}><i className="fa fa-plus" /></button>

            { inputs }
        </div>
    );
}

Notice how we're binding the key on the onChange, so that we know which input to update. now, in your onChange function, you just set the correct input's value with setState:

onChange(event, key) {
    this.setState({
        inputs: Immutable.fromJS(this.state.inputs).setIn([`${key}`, 'selectedTime'], event.target.value).toJS()

        // or
        inputs: Object.assign(this.state.inputs, Object.assign(this.state.inputs[key], { timeValue: event.target.value }))

    })
}

this isn't tested, but basically this Immutable statement is going to make a copy of this.state.inputs and set the selectedTime value inside of the object that matches the key, to the event.target.value. State is updated now, a re-render is triggered, and when you loop through the inputs again in the render, you'll use the new time value as the timeValue to your component.

again, with the Object.assign edit, it isn't tested, but learn more [here]. 2 Basically this statement is merging a new timeValue value in with the this.state.inputs[key] object, and then merging that new object in with the entire this.state.inputs object.

does this make sense?

Sign up to request clarification or add additional context in comments.

9 Comments

Could you just show the Immutable alternative in pure js? I don't want to start sticking Immutable in here just yet.
I'd be willing to bet those object assignment statements aren't exactly correct, but hopefully it gives you the right idea, and you can learn more about Object.assign with the link i provided in the edited answer
Yeah I know Object.assign fortunately!
great! then you know the implementation, so the basic idea is not to edit state objects directly, and use setState to cause a re-render in the correct way!
Just so you know setState will accept a function with the first argument as prevState. You can use this to replace using this.state inside of your set state. This is preferred because setState calls are batches and this will ensure the values are set properly.
|
0

I modified the onChange in the MultipleDayTimeInput:

onChange(event) {
    const comparisonKey = event.target.name.substring(event.target.name.length - 1);
    const input = this.getInputState(comparisonKey);

    input.selected = event.target.value;
    input.display = this.renderTimeInput(input);

    let spliceIndex = -1;
    for (let i = 0; i < this.state.inputs.length; i++) {
        const matches = inputFilter(comparisonKey)(this.state.inputs[i]);

        if (matches) {
            spliceIndex = i;
            break;
        }
    }

    if (spliceIndex < 0) {
        throw 'error updating inputs';
    }

    this.setState({
        inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
    });
}

The key points are:

// re render the input
input.display = this.renderTimeInput(input);

// set the state by copying the inputs and interchanging the old input with the new input....
this.setState({
    inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});

Having thought about it though, input is an object reference to the input in the this.state.inputs so actually [...this.states.inputs] would have been enough??

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.