Shivam Tripathi
Shivam Tripathi

Reputation: 445

Only the last action dispatched reaches reducer

I have a component where user enters a name, which on changing requires two actions to be taken:

  1. Update the component's name field value
  2. Asynchronous fetch from the server if the name already exists in the database of the entityType of the

I am using thunk middleware for doing the async work, and then dispatching both actions inside the thunk action creator.

But upon passing actions to dispatch in the thunk action creator, only the action dispatched last reaches the reducer (and hence able to update the state). Why is this so? Where am I making the mistake?

Thank you for your answers!

Relevant code snippets are:

namesection.js

function NameSection({
    entityType,
    nameValue,
    onNameChange,
    warnIfExists
}) {
    return (
        <div>
            <form>
                <Row>
                    <Col md={6} mdOffset={3}>
                        <NameField
                            defaultValue={nameValue}
                            warn={warnIfExists}
                            onChange={onNameChange}
                        />
                    </Col>
                </Row>
            </form>
        </div>
    );
}

function mapStateToProps(rootState) {
    const state = rootState.get('nameSection');
    return {
        nameValue: state.get('name'),
        warnIfExists: state.get('warnIfExists')
    };
}

function mapDispatchToProps(dispatch, {entityType}) {
    return {
        onNameChange: (event) =>
            dispatch(handleNameChange(event.target.value, entityType)),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(NameSection);

action.js

export function handleNameChange(
    newName,
    entityType
) {
    return (dispatch) => {

        dispatch({
                meta: {debounce: 'keystroke'},
                payload: newName,
                type: UPDATE_NAME_FIELD
            });

        return request.get('/search/exists')
            .query({
                collection: entityType,
                q: newName
            })
            .then(res => dispatch({
                 meta: {debounce: 'keystroke'},
                 payload: res.text === 'true',
                 type: UPDATE_WARN_IF_EXISTS
            }))
            .catch((error: {message: string}) => error);
    };
}

reducer.js

function reducer(
    state = Immutable.Map({
        name: '',
        warnIfExists: false
    }),
    action
) {
    const {payload, type} = action;
    console.log('reducer', payload, type);
    switch (type) {
        case UPDATE_NAME_FIELD:
            return state.set('name', payload);
        case UPDATE_WARN_IF_EXISTS:
            return state.set('warnIfExists', payload);
        // no default
    }
    return state;
}

In this case, only warnIfExists is updated, while name field is not update.

Upvotes: 1

Views: 249

Answers (1)

timotgl
timotgl

Reputation: 2925

Debouncing the entire keypress event handler doesn't seem like a good idea somehow. Doesn't this also block the characters from showing up right away?

Have you tried splitting this in two actions?

  1. UPDATE_NAME_FIELD should always be dispatched and change the state immediately.
  2. CHECK_UNIQUE_NAME is dispatched from the same event handler, but is debounced, and then you retrieve the current value from getState before making an api request. Then this would result in CHECK_UNIQUE_NAME_SUCCESS, where the reducer handles the result, but also checks if the name has changed in the meantime (so that api responses for old values don't corrupt the state).

Upvotes: 0

Related Questions