ReactJS Do something only after the action has completed

531 views Asked by At

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
        })
    }
1

There are 1 answers

14
Pandaiolo On

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.