Search code examples
javascriptflux

Should a Flux action creator dispatch granular actions or sweeping ones?


The premise

I'm refactoring some Flux stores/actions/action creators to be more Fluxy (PUBLISH, PUBLISH_SUCCESS, PUBLISH_FAILURE instead of a weird IS_LOADING action), and was wondering how to structure my actions: should my action creators call single actions (PUBLISH_SUCCESS) or multiple ones (ADD_AUTHOR, ADD_BOOK, etc)?

The example

Here's a more specific example:

I have a TasksStore that holds todo items for my innovative new task-management app, and I have a poorly named action creator TaskActions that lets me fetch my varieties from the server and add new ones with an API. Kinda like this:

const TasksStore = { ... };

const TaskActions = {
    fetchTasks(),
    addTask()
};

What actions should I dispatch to communicate with TasksStore?

I see two options: action-creator-api-specific actions (FETCH_TASKS, FETCH_TASKS_SUCCESS, FETCH_TASKS_FAILURE, ADD_TASK, ADD_TASK_SUCCESS, & ADD_TASK_FAILURE) or reusable actions (ADD_TASK called over and over for fetch and called once for addTask()).

Basically, should my API look like this (verbose, perhaps redundant, dispatch-able actions for each action-creator-action):

const TasksStore = {
    on('FETCH_SUCCESS', (tasks) => { // add tasks });
    on('ADD_SUCCESS', (task) => { // add task });
};

const TaskActions = {
    fetchTasks() {
        dispatch('FETCH');
        myApi.fetchTasks(
            (success_payload) => { dispatch('FETCH_SUCCESS', success_payload) },
            (failure_payload) => { dispatch('FETCH_FAILURE', failure_payload) }
        );
    },
    addTask() {
        dispatch('ADD');
        myApi.addTask(
            (success_payload) => { dispatch('ADD_SUCCESS', success_payload) },
            (failure_payload) => { dispatch('ADD_FAILURE', failure_payload) }
        );
    }
};

or like this (concise, reusable dispatch-able actions):

const TasksStore = {
    on('ADD', (task) => { // add task });
};

const TaskActions = {
    fetchTasks() {
        dispatch('FETCH');
        myApi.fetchTasks(
            (success_payload) => { 
                success_payload.forEach((task) => { dispatch('ADD', task); })
            },
            (failure_payload) => { dispatch('FETCH_FAILURE', failure_payload)
        );
    },
    addTask() {
        dispatch('ADD');
        myApi.addTask(
            (success_payload) => { dispatch('ADD', success_payload),
            (failure_payload) => { dispatch('ADD_FAILURE', failure_payload)
        );
    }
};

or something in between?

Thanks!


Solution

  • We decided to go with the more verbose route:

    For AJAX actions (like in the examples), we dispatch a specific "started" action, then a "success" or "failure" action:

    dispatch('RETICULATE_SPLINES');
    app.reticulateSpinesAndReturnPromise().then(
        (success) => { dispatch('RETICULATE_SPLINES_SUCCESS'); },
        (failure) => { dispatch('RETICULATE_SPLINES_FAILURE'); }
    );
    

    Why?

    1. This decouples action creators from the actions a bit— action creators expose a uniform, grokkable API in the form of dispatched actions: ActionCreator.fetchMessages will dispatch a FETCH_MESSAGES action, not ADD or FETCH_OR_ADD or something weird. It's a lot easier to understand a decoupled system like actions and stores (decoupled because all actions travel through a dispatcher) when there are no unexpected dispatches.
    2. It makes it a lot easier for multiple stores to listen for action-creator-actions. Does the MessageThreadsStore need to update when we .fetchMessages() but not .postMessage()? Listen for FETCH_MESSAGES_SUCCESS, not ADD which might have been called in both otherwise.

    Anyway, hope that helps.