Skip to content

Commit

Permalink
[Glitch] Proposal: a modern & typed way of writing Redux actions doin…
Browse files Browse the repository at this point in the history
…g API requests

Port 10ec421 to glitch-soc

Signed-off-by: Claire <[email protected]>
  • Loading branch information
renchap authored and ClearlyClaire committed May 23, 2024
1 parent 0b6a726 commit e969d28
Show file tree
Hide file tree
Showing 14 changed files with 281 additions and 125 deletions.
21 changes: 6 additions & 15 deletions app/javascript/flavours/glitch/actions/account_notes.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';
import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions';
import { apiSubmitAccountNote } from 'flavours/glitch/api/accounts';
import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions';

import api from '../api';

export const submitAccountNote = createAppAsyncThunk(
export const submitAccountNote = createDataLoadingThunk(
'account_note/submit',
async (args: { id: string; value: string }) => {
const response = await api().post<ApiRelationshipJSON>(
`/api/v1/accounts/${args.id}/note`,
{
comment: args.value,
},
);

return { relationship: response.data };
},
(accountId: string, note: string) => apiSubmitAccountNote(accountId, note),
(relationship) => ({ relationship }),
{ skipLoading: true },
);
86 changes: 1 addition & 85 deletions app/javascript/flavours/glitch/actions/interactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatus } from './importer';

export const REBLOG_REQUEST = 'REBLOG_REQUEST';
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
export const REBLOG_FAIL = 'REBLOG_FAIL';

export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL';
Expand All @@ -15,10 +11,6 @@ export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST';
export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
export const FAVOURITE_FAIL = 'FAVOURITE_FAIL';

export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST';
export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
export const UNREBLOG_FAIL = 'UNREBLOG_FAIL';

export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST';
export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL';
Expand Down Expand Up @@ -51,83 +43,7 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';

export function reblog(status, visibility) {
return function (dispatch) {
dispatch(reblogRequest(status));

api().post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(response.data.reblog));
dispatch(reblogSuccess(status));
}).catch(function (error) {
dispatch(reblogFail(status, error));
});
};
}

export function unreblog(status) {
return (dispatch) => {
dispatch(unreblogRequest(status));

api().post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unreblogSuccess(status));
}).catch(error => {
dispatch(unreblogFail(status, error));
});
};
}

export function reblogRequest(status) {
return {
type: REBLOG_REQUEST,
status: status,
skipLoading: true,
};
}

export function reblogSuccess(status) {
return {
type: REBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}

export function reblogFail(status, error) {
return {
type: REBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}

export function unreblogRequest(status) {
return {
type: UNREBLOG_REQUEST,
status: status,
skipLoading: true,
};
}

export function unreblogSuccess(status) {
return {
type: UNREBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}

export function unreblogFail(status, error) {
return {
type: UNREBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
export * from "./interactions_typed";

export function favourite(status) {
return function (dispatch) {
Expand Down
30 changes: 30 additions & 0 deletions app/javascript/flavours/glitch/actions/interactions_typed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { apiReblog, apiUnreblog } from 'flavours/glitch/api/interactions';
import type { StatusVisibility } from 'flavours/glitch/models/status';
import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions';

import { importFetchedStatus } from './importer';

export const reblog = createDataLoadingThunk(
'status/reblog',
(statusId: string, visibility: StatusVisibility) =>
apiReblog(statusId, visibility),
(data, { dispatch, discardLoadData }) => {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(data.reblog));

// The payload is not used in any actions
return discardLoadData;
},
);

export const unreblog = createDataLoadingThunk(
'status/unreblog',
(statusId: string) => apiUnreblog(statusId),
(data, { dispatch, discardLoadData }) => {
dispatch(importFetchedStatus(data));

// The payload is not used in any actions
return discardLoadData;
},
);
16 changes: 15 additions & 1 deletion app/javascript/flavours/glitch/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AxiosResponse, RawAxiosRequestHeaders } from 'axios';
import type { AxiosResponse, Method, RawAxiosRequestHeaders } from 'axios';
import axios from 'axios';
import LinkHeader from 'http-link-header';

Expand Down Expand Up @@ -58,3 +58,17 @@ export default function api(withAuthorization = true) {
],
});
}

export async function apiRequest<ApiResponse = unknown>(
method: Method,
url: string,
params?: unknown,
) {
const { data } = await api().request<ApiResponse>({
method,
url,
params,
});

return data;
}
7 changes: 7 additions & 0 deletions app/javascript/flavours/glitch/api/accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { apiRequest } from 'flavours/glitch/api';
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';

export const apiSubmitAccountNote = (id: string, value: string) =>
apiRequest<ApiRelationshipJSON>('post', `/api/v1/accounts/${id}/note`, {
comment: value,
});
10 changes: 10 additions & 0 deletions app/javascript/flavours/glitch/api/interactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { apiRequest } from 'flavours/glitch/api';
import type { Status, StatusVisibility } from 'flavours/glitch/models/status';

export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
apiRequest<{ reblog: Status }>('post', `v1/statuses/${statusId}/reblog`, {
visibility,
});

export const apiUnreblog = (statusId: string) =>
apiRequest<Status>('post', `v1/statuses/${statusId}/unreblog`);
4 changes: 2 additions & 2 deletions app/javascript/flavours/glitch/containers/status_container.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({

onModalReblog (status, privacy) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog(status.id));
} else {
dispatch(reblog(status, privacy));
dispatch(reblog(status.id, privacy));
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const mapStateToProps = (state, { account }) => ({
const mapDispatchToProps = (dispatch, { account }) => ({

onSave (value) {
dispatch(submitAccountNote({ id: account.get('id'), value}));
dispatch(submitAccountNote(account.get('id'), value));
},

});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ const mapDispatchToProps = dispatch => ({
},

onModalReblog (status, privacy) {
dispatch(reblog(status, privacy));
dispatch(reblog(status.id, privacy));
},

onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog(status.id));
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class Footer extends ImmutablePureComponent {

_performReblog = (status, privacy) => {
const { dispatch } = this.props;
dispatch(reblog(status, privacy));
dispatch(reblog(status.id, privacy));
};

handleReblogClick = e => {
Expand All @@ -134,7 +134,7 @@ class Footer extends ImmutablePureComponent {

if (signedIn) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog(status.id));
} else if ((e && e.shiftKey) || !boostModal) {
this._performReblog(status);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},

onModalReblog (status, privacy) {
dispatch(reblog(status, privacy));
dispatch(reblog(status.id, privacy));
},

onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog(status.id));
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/flavours/glitch/features/status/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,9 @@ class Status extends ImmutablePureComponent {
const { dispatch } = this.props;

if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog(status.id));
} else {
dispatch(reblog(status, privacy));
dispatch(reblog(status.id, privacy));
}
};

Expand Down
28 changes: 15 additions & 13 deletions app/javascript/flavours/glitch/reducers/statuses.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { Map as ImmutableMap, fromJS } from 'immutable';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
import { normalizeStatusTranslation } from '../actions/importer/normalizer';
import {
REBLOG_REQUEST,
REBLOG_FAIL,
UNREBLOG_REQUEST,
UNREBLOG_FAIL,
FAVOURITE_REQUEST,
FAVOURITE_FAIL,
UNFAVOURITE_REQUEST,
Expand All @@ -16,6 +12,10 @@ import {
UNBOOKMARK_REQUEST,
UNBOOKMARK_FAIL,
} from '../actions/interactions';
import {
reblog,
unreblog,
} from '../actions/interactions_typed';
import {
STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS,
Expand Down Expand Up @@ -65,6 +65,7 @@ const statusTranslateUndo = (state, id) => {

const initialState = ImmutableMap();

/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
export default function statuses(state = initialState, action) {
switch(action.type) {
case STATUS_FETCH_REQUEST:
Expand All @@ -91,14 +92,6 @@ export default function statuses(state = initialState, action) {
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
case UNBOOKMARK_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
case REBLOG_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], true);
case REBLOG_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false);
case UNREBLOG_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], false);
case UNREBLOG_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], true);
case STATUS_MUTE_SUCCESS:
return state.setIn([action.id, 'muted'], true);
case STATUS_UNMUTE_SUCCESS:
Expand Down Expand Up @@ -128,6 +121,15 @@ export default function statuses(state = initialState, action) {
case STATUS_TRANSLATE_UNDO:
return statusTranslateUndo(state, action.id);
default:
return state;
if(reblog.pending.match(action))
return state.setIn([action.meta.params.statusId, 'reblogged'], true);
else if(reblog.rejected.match(action))
return state.get(action.meta.params.statusId) === undefined ? state : state.setIn([action.meta.params.statusId, 'reblogged'], false);
else if(unreblog.pending.match(action))
return state.setIn([action.meta.params.statusId, 'reblogged'], false);
else if(unreblog.rejected.match(action))
return state.get(action.meta.params.statusId) === undefined ? state : state.setIn([action.meta.params.statusId, 'reblogged'], true);
else
return state;
}
}
Loading

0 comments on commit e969d28

Please sign in to comment.