4

I think I may be missing something conceptually about how React and Reflux work.

If I have set the state of an object ("project"), as it renders to the screen (with its existing properties), how can I then use that same state (and store) to create a new project?

This is some code from my project view:

componentWillMount: function () {

    // We need to get the project based on projectID.
    var projectID = this.props.params.projectID;

    if (projectID) {
        ProjectActions.getProject(projectID);
    }
},

And here is some code from my project store:

data: {},

listenables: ProjectActions,

getInitialState: function() {
    return this.data;
},

onGetProject: function (projectID) {

    // Get the project based on projectID.
    $.ajax({
        type: 'GET',
        url: '/api/projects/getProject/' + projectID
    }).done(function(response){
        ProjectStore.getProjectSuccessful(response);
    }).error(function(error){
        alert('GET projects failed.');
    });

},

getProjectSuccessful: function(response) {
    for (var prop in response) {
        if (response.hasOwnProperty(prop)) {
            this.data[prop] = response[prop];
        }
    }
    this.trigger(this.data);
},

Then, say I click "new project", I use this code:

mixins: [Reflux.connect(ProjectStore), Router.Navigation],

getInitialState: function () {
    return {
        // Project properties:
        format: '',
        title: '',
        productionCompany: ''

    };
},

What I've found is that if I remove the "getInitialState" from my store, that there is no issue when I go to create a new project. However, once I do that, I can no longer edit my existing project, because there is nothing attached to the state (I can use prop to view but that's not enough.)

If I keep the "getInitialState" I get an error:

Uncaught Error: Invariant Violation: mergeIntoWithNoDuplicateKeys(): Tried to merge two objects with the same key: `format`. This conflict may be due to a mixin; in particular, this may be caused by two getInitialState() or getDefaultProps() methods returning objects with clashing keys.

Do I need a "NewProjectStore" that only has a create action? I had figured a store could handle create, get, update, etc.

Am I missing something?

D'Arcy Rail-Ip
  • 11,505
  • 11
  • 42
  • 67
  • Do you have a `projects` store? An array of all projects? – azium Sep 04 '15 at 21:43
  • Yes I have both a `Projects` store and a `Project` store. At first I just had `Projects`, but found that it was difficult to use state on a single project within it, because properties would be `this.state.project.[property]`, and React wasn't playing well with that. – D'Arcy Rail-Ip Sep 04 '15 at 22:03

1 Answers1

3

When you use Reflux.connect, it adds getInitialState from the store into your component. That would be why you get the error about duplicate values. Your answer is either to use Reflux.listenTo or remove getInitialState from your component.

Step 1: fix getInitialState() in your store

There's nothing wrong with having one store to hold your edit project or new project, as long as you are ok with only having one or the other at any given time. Based on your case, I would suggest keeping Reflux.connect and removing your component's getInitialState. One problem is that getInitialState in your store doesn't have the default properties that are declared in the component.

Step 2: fix your flux flow

This isn't wrong as in that it actually works, however, it's not following the flux guidelines. Most people agree that asynchronous actions belong in the action. The store merely stores data and usually also provides helper functions for getting said data in different ways. Try this:


Updated Code:

var ProjectActions = Reflux.createActions({
    getProject: {asyncResult: true}
});
ProjectActions.getProject.listen(function (projectID) {
    // Get the project based on projectID.
    $.ajax({
        type: 'GET',
        url: '/api/projects/getProject/' + projectID
    }).done(function(response){
        ProjectActions.getProject.completed(response);
    }).error(function(error){
        alert('GET projects failed.');
        ProjectActions.getProject.failed(error);
    });
});

var ProjectStore = Reflux.createStore({
    init: function () {
        // default project properties:
        this.data = {
            format: '',
            title: '',
            productionCompany: ''
        };
    },

    listenables: ProjectActions,

    getInitialState: function() {
        return this.data;
    },

    onGetProjectCompleted: function(response) {
        for (var prop in response) {
            if (response.hasOwnProperty(prop)) {
                this.data[prop] = response[prop];
            }
        }
        this.trigger(this.data);
    }
});
Jeff Fairley
  • 8,071
  • 7
  • 46
  • 55
  • Hi Jeff, thanks for the reply. I think you're putting me on the right track but I may be missing something. I removed the `getInitialState()` from my component, and moved the data initialization into `ProjectStore`. My problem now is that if I load a project then try to create a new project, all the fields are filled with the previously-loaded project's values. How can I re-call the `getInitialState()` in the store to clear the values and reset them to their defaults? – D'Arcy Rail-Ip Sep 08 '15 at 15:30
  • Add an action called `newProject`, and in the store add a listener which calls reset: `onNewProject: function() { this.init(); }`. – Jeff Fairley Sep 08 '15 at 16:25
  • Thanks again for the reply. Which function should I call this new action from? I would like it to be a function before the "render" gets called - because as it is, I need to add a `this.trigger(this.data)` to my `onNewProject` function to trigger the re-render, but I'm thinking I wouldn't need to re-render if the state was reset before the render. I've tried `getInitialState`, `getDefaultProps`, and `componentWillMount`, all seem to be after the render function is called. – D'Arcy Rail-Ip Sep 08 '15 at 16:50
  • Check out the lifecycle docs: https://facebook.github.io/react/docs/component-specs.html. Look at `componentWillMount()` and `componentWillUpdate()` – Jeff Fairley Sep 08 '15 at 16:58
  • Hmm, `componentWillMount()` is being called before `render()` for me - the docs mention that `setState()` needs to be called for it to be used before `render()`, I'd assumed setting the data in the store was the same. Do I need to return a value from the newProject action and set the state in the component? (Oddly, `componentWillUpdate()` doesn't seem to get called at all.) – D'Arcy Rail-Ip Sep 08 '15 at 17:43
  • Using `Reflux.connect` automatically calls `setState` whenever the store changes. You only need to call the action, which updates the store. The rest is auto-magic. – Jeff Fairley Sep 08 '15 at 18:58