Using Draft Data for Editing Pilots

With those building blocks set, we can now begin updating our <PilotDetails> form to work with the new editing logic and data.

Displaying the Copied Pilot Entry

Since we’re copying edited items from entities to editingEntities, our <PilotDetails> form will need to be able to switch which slice of state it’s reading the pilot data from. We’ll add some additional logic to the mapState function so that it can determine what slice it should use to load the data. We also need to update the action creators to actually dispatch the EDIT_ITEM_EXISTING and EDIT_ITEM_STOP actions at the same time as we dispatch the PILOT_EDIT_START and PILOT_EDIT_STOP actions.

Commit db9b7f7: Add ability to display pilot entry from “draft” editingEntities slice

features/editing/editingSelectors.js

import {createSelector} from "reselect";

import orm from "app/orm";

export const selectEditingEntities = state => state.editingEntities;

export const getEditingEntitiesSession = createSelector(
    selectEditingEntities,
    editingEntities => orm.session(editingEntities)
);

As with our entities slice, we’ll create a memoized selector that ensures we only have a single Redux-ORM Session instance per update to the editingEntities slice.

features/pilots/pilotsActions.js

+import {
+   editExistingItem,
+   stopEditingItem
+} from "features/editing/editingActions";


-export function startEditingPilot() {
-   return {
-       type : PILOT_EDIT_START,
-   };
-}
+export function startEditingPilot(pilotID) {
+   return (dispatch, getState) => {
+       dispatch(editExistingItem("Pilot", pilotID));
+       dispatch({type : PILOT_EDIT_START});
+   }
+}

-export function stopEditingPilot() {
-   return {
-       type : PILOT_EDIT_STOP,
-   };
-}
+export function stopEditingPilot(pilotID) {
+   return (dispatch, getState) => {
+       dispatch({type : PILOT_EDIT_STOP});
+       dispatch(stopEditingItem("Pilot", pilotID));
+   }
+}

We need to dispatch two different actions each time the “Start Editing” and “Stop Editing” buttons are clicked. We’ll change the existing plain action creators into thunks, pass in the pilot IDs as arguments, and dispatch both the pilot-related action and the editing-related action in each thunk.

features/pilots/PilotDetails.jsx

import {getEntitiesSession} from "features/entities/entitySelectors";
+import {getEditingEntitiesSession} from "features/editing/editingSelectors";

// Skip to mapState

    const currentPilot = selectCurrentPilot(state);

+   const pilotIsSelected = Boolean(currentPilot);
+   const isEditingPilot = selectIsEditingPilot(state);
+
+   if(pilotIsSelected) {
+       const session = isEditingPilot ?
+           getEditingEntitiesSession(state) :
+           getEntitiesSession(state);

        const {Pilot} = session;

        if(Pilot.hasId(currentPilot)) {
            pilot = Pilot.withId(currentPilot).ref;
        }
+    }

export class PilotDetails  extends Component {
+   onStartEditingClicked = () => {
+       const {id} = this.props.pilot;
+       this.props.startEditingPilot(id);
+   }

+   onStopEditingClicked = () => {
+       const {id} = this.props.pilot;
+       this.props.stopEditingPilot(id);
=   }

// Omit rendering code
                    <Button
                        primary
                        disabled={!canStartEditing}
                        type="button"
-                       onClick={actions.startEditingPilot}
+                       onClick={this.onStartEditingClicked}
                    >
                        Start Editing
                    </Button>
                    <Button
                        secondary
                        disabled={!canStopEditing}
                        type="button"
-                       onClick={actions.stopEditingPilot}
+                       onClick={this.onStopEditingClicked}
                    >
                        Stop Editing
                    </Button>

We rework the mapState function to determine whether we’re currently editing a pilot or just displaying it. Based on that, we retrieve either the editing Session or the “current data” Session, and read the Pilot out of there.

The <PilotDetails> component gets a couple new click handlers, which extract the pilot ID from props, and call the appropriate action creator.

features/editing/editingReducer.js

    const newItemAttributes = readEntityData(sourceEntities, itemType, itemID);
+   if(newItemAttributes.name) {
+       newItemAttributes.name += " (copied)";
+   }
    const creationPayload = {itemType, itemID, newItemAttributes}

As a visual indicator that we really are displaying the edited item, we’ll temporarily modify our copyEntity() function to append the text " (copied)" to the end of the name field.

If we select a pilot and start editing it, we should now see:

widget

Progress! We now have two different versions of the same pilot being displayed: the “current” entry in the list, and the “draft” entry in the form.

Updating the Edited Entry

If you try typing in the “Name” field, you’ll notice some very strange behavior. As soon as you type anything, the list item for the pilot will update as well. That’s because we’re still dispatching ENTITY_UPDATE, instead of EDIT_ITEM_UPDATE. We need to modify <PilotDetails> to actually update the “draft” version. While we’re at it, we’ll remove the little “(copied)” modification we made to the reducer.

Commit b90405e: Update pilot editing logic to apply to draft entry

features/pilots/PilotDetails.jsx

-import {updateEntity} from "features/entities/entityActions";
+import {editItemAttributes} from "features/editing/editingActions";

const actions = {
    startEditingPilot,
    stopEditingPilot,
-   updateEntity,
+   editItemAttributes,
}


   onInputChanged = (e) => {
        const newValues = getValueFromEvent(e);
        const {id} = this.props.pilot;

-       this.props.updateEntity("Pilot", id, newValues);
+       this.props.editItemAttributes("Pilot", id, newValues);
    }

    onDropdownChanged = (e, result) => {
        const {name, value} = result;
        const newValues = { [name] : value};
        const {id} = this.props.pilot;

-       this.props.updateEntity("Pilot", id, newValues);
+       this.props.editItemAttributes("Pilot", id, newValues);
    }

Now, if you edit the pilot in the form, only that form should update. The corresponding entry in the list should stay as it was.