Preact: state gets overwritten?

Created on 19 Apr 2016  路  4Comments  路  Source: preactjs/preact

Hi,
I'm having trouble passing functions down to child components that are meant to update different parts of a top-level container state. Here's a small example:

class EventForm extends Component {
    handleSubmit (e) {
        e.preventDefault();
        console.log(this.state); // undefined
    }
    render ({ }, { title, description }) {
        return <div>
                    <form onSubmit={this.handleSubmit}>
                        <h2>{title}</h2> // updates properly on input change
                        <h3>{description}</h3> // updates properly on input change
                        <EventInfo titleInput={this.linkState('title')}
                                           descInput={this.linkState('description')}/>
                        <button type="submit" class="btn btn-success btn-lg col-md-12 btn-submit">Generate Button Code</button>
                    </form>
                </div>
    }
}
class EventInfo extends Component {
    render ({titleInput, descInput}, { title, description }) {
        return <div>
                <h1>Event info component</h1>
                <input value={title} onInput={titleInput} />
                <input value={description} onInput={descInput} />
                </div>
    }
}

Passing down the call to linkState properly updates each respective field on the DOM, but when submitting the form, the "state" (that is properly updating components on the DOM) is undefined. Am I doing something wrong?

question

Most helpful comment

@zelias500 Hi! Two issues in the above code:

You're storing state in the parent Component, which is the generally the right way to go - however, state values are not automatically passed to child components. State is per-component, and transient - if you remove and re-add a component, it gets wiped. You will need to pass them as props.

Secondly, the value of this in your handleSubmit() function is actually the <form> element it got bound to. ES class methods aren't bound to their containing instance. The most common way to deal with this is to instead declare the method as a property of your class using an Arrow function, see the example below for what I mean.

The code above is very close to working, you just need to pass those state values from the parent Component to the child Component as props:

class EventForm extends Component {
    // using an Arrow here means "this" always refers to our component
    handleSubmit = (e) => {
        e.preventDefault();
        console.log(this.state);
    };

    render ({ }, { title, description }) {
        return <div>
            <form onSubmit={this.handleSubmit}>
                <h2>{title}</h2>
                <h3>{description}</h3>
                <EventInfo title={title} titleInput={this.linkState('title')}
                           description={description} descInput={this.linkState('description')}/>
                <button type="submit" class="btn btn-success btn-lg col-md-12 btn-submit">Generate Button Code</button>
            </form>
        </div>
    }
}

class EventInfo extends Component {
    // instead of referring to title and description from state, they are passed in as props:
    render ({ title, description, titleInput, descInput }) {
        return <div>
            <h1>Event info component</h1>
            <input value={title} onInput={titleInput} />
            <input value={description} onInput={descInput} />
        </div>
    }
}

_Sidebar: Now your child component (<EventInfo />) is stateless, which means you can optionally express it as a pure function for simplicity:_

const EventInfo = ({ title, description, titleInput, descInput }) => (
    <div>
        <h1>Event info component</h1>
        <input value={title} onInput={titleInput} />
        <input value={description} onInput={descInput} />
    </div>
);

Here's a working JSFiddle of the above code:
https://jsfiddle.net/developit/3o3vezcr/

Cheers!

All 4 comments

@zelias500 Hi! Two issues in the above code:

You're storing state in the parent Component, which is the generally the right way to go - however, state values are not automatically passed to child components. State is per-component, and transient - if you remove and re-add a component, it gets wiped. You will need to pass them as props.

Secondly, the value of this in your handleSubmit() function is actually the <form> element it got bound to. ES class methods aren't bound to their containing instance. The most common way to deal with this is to instead declare the method as a property of your class using an Arrow function, see the example below for what I mean.

The code above is very close to working, you just need to pass those state values from the parent Component to the child Component as props:

class EventForm extends Component {
    // using an Arrow here means "this" always refers to our component
    handleSubmit = (e) => {
        e.preventDefault();
        console.log(this.state);
    };

    render ({ }, { title, description }) {
        return <div>
            <form onSubmit={this.handleSubmit}>
                <h2>{title}</h2>
                <h3>{description}</h3>
                <EventInfo title={title} titleInput={this.linkState('title')}
                           description={description} descInput={this.linkState('description')}/>
                <button type="submit" class="btn btn-success btn-lg col-md-12 btn-submit">Generate Button Code</button>
            </form>
        </div>
    }
}

class EventInfo extends Component {
    // instead of referring to title and description from state, they are passed in as props:
    render ({ title, description, titleInput, descInput }) {
        return <div>
            <h1>Event info component</h1>
            <input value={title} onInput={titleInput} />
            <input value={description} onInput={descInput} />
        </div>
    }
}

_Sidebar: Now your child component (<EventInfo />) is stateless, which means you can optionally express it as a pure function for simplicity:_

const EventInfo = ({ title, description, titleInput, descInput }) => (
    <div>
        <h1>Event info component</h1>
        <input value={title} onInput={titleInput} />
        <input value={description} onInput={descInput} />
    </div>
);

Here's a working JSFiddle of the above code:
https://jsfiddle.net/developit/3o3vezcr/

Cheers!

Closing since I think this answers your question, but let me know! :)

This worked perfectly, thanks for the help!

One question though -- even if the state itself isn't passed down in my original example, why don't the linkState() functions passed down as props close over the state of the parent component?

They did - the issue was that your form handler method was not bound, so the value of this was the form. When you were referencing this.state in that handler, it was looking for a .state property on the form itself.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

skaraman picture skaraman  路  3Comments

k15a picture k15a  路  3Comments

jasongerbes picture jasongerbes  路  3Comments

youngwind picture youngwind  路  3Comments

adriaanwm picture adriaanwm  路  3Comments