flummox icon indicating copy to clipboard operation
flummox copied to clipboard

Action creator or store for performing save/update ajax

Open andwaal opened this issue 10 years ago • 3 comments

Hi, im struggling with deciding where to trigger my save/update AJAX calls from. In your examples all the I/O is done in the action creator. This makes a lots of sense for initial state, making sure the flux instance is loaded before rendering etc, but in cases where your true state is in the store im not sure if I follow this pattern. Ok time for example.

My store is as follows:

class MyStore extends Store {

    constructor(flux) {
        super();
        const actionIds = flux.getActionIds(FluxConst);
        this.register(actionIds.middleNameChanged, this.onMiddleNameChanged);

        this.state = {
            person :  Immutable.Map({ firstName:'John', lastName: 'Doe',middleName:''})
        };
    }
    onMiddleNameChanged(newMiddleName){
        this.setState(
            {
                person: this.state.person.set('middleName',newMiddleName)
            });
    }
}

The store holds a single person object represented using an immutable map. Next is my view. This is a simple form showing the person with an input to set a middle name( the view is wrapped in a <FluxComponent> further up the chain).

const Component = React.createClass({
    middleNameChanged: function(event) {
        this.props.flux.getActions(FluxConst).middleNameChanged(event.target.value);;
    },
    savePerson(){
        this.props.flux.getActions(FluxConst).savePerson();
    },
    render(){
        return(
            <div>
                <p>First name: {this.props.person.get('firstName')} </p>
                <p>Surname: {this.props.person.get('surName')}   </p>
                <p>Please enter middlename:</p>
                <input value={this.props.person.get('middleName')} onChange={this.middleNameChanged}/>
                <button onClick={this.savePerson}>Save me</button>
            </div>
        );
    }
});

And finally my action creator.

class MyActions extends Actions{
    async savePerson(person){
        return doSaveAjaxThatReturnsPromise(person);
    }
    middleNameChanged(middleName){
        return middleName;
    }
}

As you can see the action creator got two actions. One of them receiving an update when the user makes a change in the input. The second actions is a "savePerson" actions.

Here is my problem. My store owns the state, so it seems to me that it should be the store that passes the "person to save" to the action creator. But in all examples following this pattern the data is passed from from "view" -> "action creator" -> "store". This makes my view responsible for passing data to the server, even though its my store thats the "owner" of this person.

My first take on this was to trigger a "saveRequest" action to the store and let the store trigger the "savePerson" action with the "true-person" as payload,b ut this also seems a bit of. So right now im leaning against using the action creator for all the initial data fetching( using the component static method pattern from your doc app example) and performing the entire save/update inside the store.

Any ideas on how to handle this?

andwaal avatar Jun 04 '15 19:06 andwaal

Why don't you use the middleNameChanged for the Controlled Input to render and show only the changes you're making on UI and your savePerson to pass in the new value to make the ajax call?

After you get the response from the ajax call you could on your success handler (which you currently don't have registered in your store) change the state of the store if needed. And that could "throw" a render on your component.

joaovpmamede avatar Jun 07 '15 12:06 joaovpmamede

Im not quite sure what you mean. Are you suggesting that I keep the changes done as local state in the component until im ready to save?

Please feel free to attach an example illustration what you mean.

andwaal avatar Jun 07 '15 19:06 andwaal

Are you suggesting that I keep the changes done as local state in the component until im ready to save?

Yup.

const Component = React.createClass({
    getInitialState: function() {
        return { middleName: this.props.person.get('middleName') };
    },
    componentWillReceiveProps(nextProps) {
       this.setState({ middleName: nextProps.person.get('middleName') });
    },
    middleNameChanged: function(event) {
        this.setState({ middleName: event.target.value });
    },
    savePerson() {
        const middleName = React.findDOMNode(this.refs.middleName).value;

        this.props.flux.getActions(FluxConst).savePerson({ middleName: middleName });
    },
    render(){
        return(
            <div>
                <p>First name: {this.props.person.get('firstName')} </p>
                <p>Surname: {this.props.person.get('surName')}   </p>
                <p>Please enter middlename:</p>
                <input
                    value={this.state.middleName}
                    onChange={this.middleNameChanged}
                    ref="middleName"
                />
                <button onClick={this.savePerson}>Save me</button>
            </div>
        );
    }
});

I know that adding state that depends on props is an anti-pattern but in this case I wouldn't mind using it.

class MyStore extends Store {

    constructor(flux) {
        super();
        const actionIds = flux.getActionIds(FluxConst);
        this.register(actionIds. savePerson, this.onSavePerson);

        this.state = {
            person :  Immutable.Map({ firstName:'John', lastName: 'Doe',middleName:''})
        };
    }
    onSavePerson(person){
        this.setState(
            {
                person: this.state.person.merge(person)
            });
    }
}

joaovpmamede avatar Jun 07 '15 19:06 joaovpmamede