0

I have:

onKeyPress(id, e) {
        if(e.key == 'Enter') {
            this.saveField(id, e.target.value);
        }
    }

    onBlur(id, e) {
        this.saveField(id, e.target.value);
    }

    saveField(id, date) {
        this.setState({
            updatingField: true
        })
        this.context.executeAction(SetJobChaserDate, {date: date, id: id});
        this.setState({
            editingChaser: false,
            editingTrackingSent: false,
            updatingField: false
        })
    }

The problem is, is that it seems the setState after the action fires immediately, thus not giving me the effect in another component.

How can I setState only after the action has completed (regardless of success or fail)?


Here is my action:

import qwest from 'qwest';

export default function SetJobChaserDate(actionContext, payload) {
    return qwest.post('jobs/set/chaser-date', {id: payload.id, date: payload.date}, {responseType: 'json'})
        .then(function (response) {

            actionContext.dispatch('RECEIVED_JOBS_DATA', {data: response.response, clear: false})

        })
}

import { EventEmitter } from 'events';

class JobStore extends EventEmitter {

    constructor() {
        super();
        this.jobs = new Map();
        this.counts = {};
        this.loading = false;
    }

    handleJobsData(payload) {
        if (payload.clear === true) {
            this.jobs = new Map();
        }
        payload.data.jobs.forEach((job) => {
            this.jobs.set(job.id, job);
        });
        if(payload.data.counts) {
            this.counts = payload.data.counts;
        }
        this.loading = false;
        this.emit('change');

    }

    handleReceiving() {
        this.loading = true;
        this.emit('loading');
    }

    handleCounts(payload) {
        console.log(payload)
    }

    getCounts() {
        return this.counts;
    }

    getJobs() {
        return this.jobs;
    }

    dehydrate () {
        return this.jobs;
    }

    rehydrate (state) {

    }

}

JobStore.dispatchToken = null;

JobStore.handlers = {
    'RECEIVED_JOBS_DATA': 'handleJobsData',
    'RECEIVED_COUNTS'   : 'handleCounts',
    'RECEIVING_JOB_DATA': 'handleReceiving'
};

JobStore.storeName = 'JobStore';

export default JobStore;

Update:

componentWillReceiveProps() {
        this.context.getStore(JobStore).on('change', () => {
            this.setState({
                updatingField: false
            });
        });
    }

    onKeyPress(id, e) {
        if (e.key == 'Enter') {
            this.saveField(id, e.target.value);
        }
    }

    onBlur(id, e) {
        this.saveField(id, e.target);
    }

    saveField(id, target) {
        console.log(target)
        this.setState({
            updatingField: true
        })
        this.context.executeAction(SetJobChaserDate, {date: target.value, id: id});
        this.setState({
            editingChaser: false,
            editingTrackingSent: false
        })
    }
Pandaiolo
  • 11,165
  • 5
  • 38
  • 70
imperium2335
  • 23,402
  • 38
  • 111
  • 190
  • Are you saying that this.setState({ editingChaser: false, editingTrackingSent: false, updatingField: false }) is set as soon as executeAction fires? – shet_tayyy May 11 '16 at 12:35
  • It is to be set when the component loads and after the action has completed. – imperium2335 May 11 '16 at 13:00

1 Answers1

0

You are using an updating state, that shows your process is asynchronous. The code you are dispatching is indeed a promise, and dispatching an action when the process is done : 'RECEIVED_JOBS_DATA'.

You should hence move the updatingField to be a component prop that comes from the store, and is changed in your reducer whenever the action starts and ends.

That way, you could just use this.props.updatingField in your component, which value will come from the store and depend on the current state of your external request.

That way, you'll follow one React/Flux best practice of preferring global state instead of local state.

Pandaiolo
  • 11,165
  • 5
  • 38
  • 70
  • I'm getting "ShippingTable.js:37 Uncaught TypeError: Cannot read property 'then' of undefined", also, can't we use => to avoid using self = this somehow? – imperium2335 May 11 '16 at 12:38
  • This is the action, but in your method, you use `executeAction`. What is its code? Your function returns a promise, but it seems that the dispatcher does not forward this promise to the caller. – Pandaiolo May 11 '16 at 12:44
  • executeAction is built into flux isn't it? I have not defined that function anywhere. – imperium2335 May 11 '16 at 12:46
  • (I edited with simpler ES6 code) What implementation of flux are you using ? If you are dispatching a return action already, you have to make your component listen to the store on one of its props and update your state in `componentWillReceiveProps` I guess. Let me update my answer to reflect this. – Pandaiolo May 11 '16 at 12:49
  • Yes that rings a bell, I have posted the relevant store. – imperium2335 May 11 '16 at 12:51
  • I have updated my question with the changes I have made using 'componentWillReceiveProps' but now I get an error: (node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit. – imperium2335 May 11 '16 at 12:56
  • I mostly use redux, so i'm not familiar with the flux impementation you use (vanilla? do you have a reducer to update your data based on actions dispatched? I guess no, this is in redux :)), but it's a matter of connecting your store data to your component props, so your state can be global when it need to. – Pandaiolo May 11 '16 at 12:58
  • In your case, if I read correctly your store code, your store prop is `loading` and is already correctly set in the store based on request state. You just have to connect it to one of your component prop. Not sure how in basic flux :) – Pandaiolo May 11 '16 at 13:05
  • I found the problem. I have 1000s of those ShippingTable components on my page, so the listener is being bound to each one when an update happens. This is wrong, it needs to just be for the one the user is currently editing. Does that mean I need to move some stuff into the ChaserField component? It works but takes a long time to complete, and at the end I get the error: "arning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component." – imperium2335 May 11 '16 at 13:08
  • For every instance of ShippingTable. – imperium2335 May 11 '16 at 13:08
  • Sounds like you should rethink what state should be global, and which one should be local (if any). If you have many instances of the same data structure, usually the best way to handle this is with a central store managing the array, and passing a unique `id` to the components and managing the state in their parent component. – Pandaiolo May 11 '16 at 13:12
  • I still have the same problem when I move everything down a level into the ChaserField component, so I am totally lost. – imperium2335 May 11 '16 at 13:38
  • Are you using fluxible ? I found this on a google search : http://fluxible.io/api/fluxible-context.html#executeaction-action-payload-done- seems you can use a third parameter in `executeAction` to trigger your state change, if you don't want to go through a heavier refactoring – Pandaiolo May 11 '16 at 13:55
  • Yes, I just tried that but the state doesn't change. – imperium2335 May 11 '16 at 13:58