Skip to content

Commit

Permalink
Social: Fix parallel social connections requests mixing up UI state (#…
Browse files Browse the repository at this point in the history
…38408)

* Social: Fix parallel social connections requests mixing up UI state

* Add changelog

* Better safe than sorry
  • Loading branch information
manzoorwanijk authored Jul 19, 2024
1 parent 1a2d874 commit 228835b
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Social | Fixed parallel social connection requests messing up the UI state
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
TOGGLE_CONNECTIONS_MODAL,
UPDATE_CONNECTION,
UPDATING_CONNECTION,
REQUEST_TYPE_REFRESH_CONNECTIONS,
ADD_ABORT_CONTROLLER,
REMOVE_ABORT_CONTROLLERS,
} from './constants';

/**
Expand Down Expand Up @@ -107,6 +110,64 @@ export function mergeConnections( freshConnections ) {
};
}

/**
* Create an abort controller.
* @param {AbortController} abortController - Abort controller.
* @param {string} requestType - Type of abort request.
*
* @returns {object} - an action object.
*/
export function createAbortController( abortController, requestType ) {
return {
type: ADD_ABORT_CONTROLLER,
requestType,
abortController,
};
}

/**
* Remove abort controllers.
*
* @param {string} requestType - Type of abort request.
*
* @returns {object} - an action object.
*/
export function removeAbortControllers( requestType ) {
return {
type: REMOVE_ABORT_CONTROLLERS,
requestType,
};
}

/**
* Abort a request.
*
* @param {string} requestType - Type of abort request.
*
* @returns {Function} - a function to abort a request.
*/
export function abortRequest( requestType ) {
return function ( { dispatch, select } ) {
const abortControllers = select.getAbortControllers( requestType );

for ( const controller of abortControllers ) {
controller.abort();
}

// Remove the abort controllers.
dispatch( removeAbortControllers( requestType ) );
};
}

/**
* Abort the refresh connections request.
*
* @returns {Function} - a function to abort a request.
*/
export function abortRefreshConnectionsRequest() {
return abortRequest( REQUEST_TYPE_REFRESH_CONNECTIONS );
}

/**
* Effect handler which will refresh the connection test results.
*
Expand All @@ -118,15 +179,32 @@ export function refreshConnectionTestResults( syncToMeta = false ) {
try {
const path = select.connectionRefreshPath() || '/wpcom/v2/publicize/connection-test-results';

const freshConnections = await apiFetch( { path } );
// Wait until all connections are done updating/deleting.
while (
select.getUpdatingConnections().length > 0 ||
select.getDeletingConnections().length > 0
) {
await new Promise( resolve => setTimeout( resolve, 100 ) );
}

const abortController = new AbortController();

dispatch( createAbortController( abortController, REQUEST_TYPE_REFRESH_CONNECTIONS ) );

// Pass the abort controller signal to the fetch request.
const freshConnections = await apiFetch( { path, signal: abortController.signal } );

dispatch( mergeConnections( freshConnections ) );

if ( syncToMeta ) {
dispatch( syncConnectionsToPostMeta() );
}
} catch ( e ) {
// Do nothing.
// If the request was aborted.
if ( 'AbortError' === e.name ) {
// Fire it again to run after the current operation that cancelled the request.
dispatch( refreshConnectionTestResults( syncToMeta ) );
}
}
};
}
Expand Down Expand Up @@ -210,6 +288,9 @@ export function deleteConnectionById( { connectionId, showSuccessNotice = true }
try {
const path = `/jetpack/v4/social/connections/${ connectionId }`;

// Abort the refresh connections request.
dispatch( abortRefreshConnectionsRequest() );

dispatch( deletingConnection( connectionId ) );

await apiFetch( { method: 'DELETE', path } );
Expand Down Expand Up @@ -269,6 +350,9 @@ export function createConnection( data, optimisticData = {} ) {
...optimisticData,
} )
);
// Abort the refresh connections request.
dispatch( abortRefreshConnectionsRequest() );

// Mark the connection as updating to show the spinner.
dispatch( updatingConnection( tempId ) );

Expand Down Expand Up @@ -383,6 +467,9 @@ export function updateConnectionById( connectionId, data ) {
try {
const path = `/jetpack/v4/social/connections/${ connectionId }`;

// Abort the refresh connections request.
dispatch( abortRefreshConnectionsRequest() );

// Optimistically update the connection.
dispatch( updateConnection( connectionId, data ) );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export const SET_RECONNECTING_ACCOUNT = 'SET_RECONNECTING_ACCOUNT';
export const SET_KEYRING_RESULT = 'SET_KEYRING_RESULT';

export const TOGGLE_CONNECTIONS_MODAL = 'TOGGLE_CONNECTIONS_MODAL';

export const ADD_ABORT_CONTROLLER = 'ADD_ABORT_CONTROLLER';

export const REMOVE_ABORT_CONTROLLERS = 'REMOVE_ABORT_CONTROLLERS';

export const REQUEST_TYPE_DEFAULT = 'DEFAULT';

export const REQUEST_TYPE_REFRESH_CONNECTIONS = 'REFRESH_CONNECTIONS';
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
TOGGLE_CONNECTIONS_MODAL,
UPDATE_CONNECTION,
UPDATING_CONNECTION,
ADD_ABORT_CONTROLLER,
REMOVE_ABORT_CONTROLLERS,
REQUEST_TYPE_DEFAULT,
} from '../actions/constants';

/**
Expand Down Expand Up @@ -92,6 +95,33 @@ const connectionData = ( state = {}, action ) => {
};
}

case ADD_ABORT_CONTROLLER: {
const requestType = action.requestType || REQUEST_TYPE_DEFAULT;

return {
...state,
abortControllers: {
...state.abortControllers,
[ requestType ]: [
...( state.abortControllers?.[ requestType ] || [] ),
action.abortController,
],
},
};
}

case REMOVE_ABORT_CONTROLLERS: {
const requestType = action.requestType || REQUEST_TYPE_DEFAULT;

return {
...state,
abortControllers: {
...state.abortControllers,
[ requestType ]: [],
},
};
}

case SET_KEYRING_RESULT:
return {
...state,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { REQUEST_TYPE_DEFAULT } from '../actions/constants';

/**
* Returns the connections list from the store.
*
Expand Down Expand Up @@ -161,6 +163,18 @@ export function getReconnectingAccount( state ) {
return state.connectionData?.reconnectingAccount ?? '';
}

/**
* Get the abort controllers for a specific request type.
*
* @param {import("../types").SocialStoreState} state - State object.
* @param {string} requestType - The request type.
*
* @returns {Array<AbortController>} The abort controllers.
*/
export function getAbortControllers( state, requestType = REQUEST_TYPE_DEFAULT ) {
return state.connectionData?.abortControllers?.[ requestType ] ?? [];
}

/**
* Whether a mastodon account is already connected.
*
Expand Down

0 comments on commit 228835b

Please sign in to comment.