samedi 15 février 2020

How to set initial state of glimmer component based on argument?

I am struggling to figure out how to implement data down, actions up in a glimmer component hierarchy (using Ember Octane, v3.15).

I have a parent component with a list of items. When the user clicks on a button within the Parent component, I want to populate an Editor component with the data from the relevant item; when the user clicks "Save" within the Editor component, populate the changes back to the parent. Here's what happens instead:

GIF of my app

How can I make the text box be populated with "Hello", and have changes persisted back to the list above when I click "Save"?

Code


<ul>

    <li> <button >Edit</button></li>

</ul>

<Editor @currentModel= @save= />
// app/components/parent.js
import Component from '@glimmer/component';
export default class ParentComponent extends Component {
    @tracked models = [
        { id: 1, text: 'Hello'},
        { id: 2, text: 'World'}
    ]
    @tracked currentModel = null;

    @action
    edit(model) {
        this.currentModel = model;
    }

    @action
    save(model) {
        // persist data
        this.models = models.map( (m) => m.id == model.id ? model : m )
    }
}


<small>Editing ID: </small>

<Input @value= />
<button >Save</button>
// app/components/editor.hbs
import Component from '@glimmer/component';
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";

export default class EditorComponent extends Component {
    @tracked text;
    @tracked id;

    constructor() {
        super(...arguments)
        if (this.args.currentModel) {
            this.text = this.args.currentModel.text;
            this.id = this.args.currentModel.id;
        }

    }

    @action
    save() {
        // persist the updated model back to the parent
        this.args.save({ id: this.id, text: this.text })
    }
}

Rationale/Problem

I decided to implement Editor as a stateful component, because that seemed like the most idiomatic way to get form data out of the <Input /> component. I set the initial state using args. Since this.currentModel is @tracked in ParentComponent and I would expect re-assignment of that property to update the @currentModel argument passed to Editor.

Indeed that seems to be the case, since clicking "Edit" next to one of the items in ParentComponent makes <small>Editing ID: </small> appear. However, neither the value of the <Input /> element nor the id are populated.

I understand that this.text and this.id are not being updated because the constructor of EditorComponent is not being re-run when currentModel changes in the parent... but I'm stuck on what to do instead.


What I've tried

As I was trying to figure this out, I came across this example (code), which has pretty much the same interaction between BlogAuthorComponent (hbs) and BlogAuthorEditComponent (hbs, js). Their solution, as applied to my problem, would be to write EditorComponent like this:



<small>Editing ID: </small>
<Input @value= />
<button >Save</button>

// app/components/editor.hbs
import Component from '@glimmer/component';
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";

export default class EditorComponent extends Component {
    get isEditing() {
        return !!this.args.currentModel
    }

    @action
    save() {
        // persist the updated model back to the parent
        this.args.save({ id: this.id, text: this.text })
    }
}

enter image description here

It works! But I don't like this solution, for a few reasons: - Modifying a property of something passed to the child component as an arg seems... spooky... I'm honestly not sure why it works at all (since while ParentComponent#models is @tracked, I wouldn't expect properties of POJOs within that array to be followed...) - This updates the text in ParentComponent as you type which, while neat, isn't what I want---I want the changes to be persisted only when the user clicks "Save" (which in this case does nothing) - In my real app, when the user is not "editing" an existing item, I'd like the form to be an "Add Item" form, where clicking the "Save" button adds a new item. I'm not sure how to do this without duplicating the form and/or doing some hairly logic as to what goes in <Input @value...

I also came across this question, but it seems to refer to an old version of glimmer.

Thank you for reading this far---I would appreciate any advice!




Aucun commentaire:

Enregistrer un commentaire