Skip to content

Commit

Permalink
Implement Optimistic Effects (twentyhq#1415)
Browse files Browse the repository at this point in the history
* Fix person deletion not reflected on Opportunities POC

* Fix companies, user deletion

* Implement optimistic effects

* Implement optimistic effects

* Implement optimistic effects

* Fix accoding to PR
  • Loading branch information
charlesBochet authored Sep 4, 2023
1 parent ae072b6 commit a46210b
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useApolloClient } from '@apollo/client';
import { useRecoilCallback } from 'recoil';

import { optimisticEffectState } from '../states/optimisticEffectState';
import { OptimisticEffect } from '../types/OptimisticEffect';

export function useOptimisticEffect() {
const apolloClient = useApolloClient();

const registerOptimisticEffect = useRecoilCallback(
({ snapshot, set }) =>
(optimisticEffect: OptimisticEffect<unknown, unknown>) => {
const { key } = optimisticEffect;
const optimisticEffects = snapshot
.getLoadable(optimisticEffectState)
.getValue();

set(optimisticEffectState, {
...optimisticEffects,
[key]: optimisticEffect,
});
},
);

const triggerOptimisticEffects = useRecoilCallback(
({ snapshot }) =>
(typename: string, entities: any[]) => {
const optimisticEffects = snapshot
.getLoadable(optimisticEffectState)
.getValue();

Object.values(optimisticEffects).forEach((optimisticEffect) => {
if (optimisticEffect.typename === typename) {
optimisticEffect.resolver({
cache: apolloClient.cache,
entities,
variables: optimisticEffect.variables,
});
}
});
},
);

return {
registerOptimisticEffect,
triggerOptimisticEffects,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { atom } from 'recoil';

import { OptimisticEffect } from '../types/OptimisticEffect';

export const optimisticEffectState = atom<
Record<string, OptimisticEffect<unknown, unknown>>
>({
key: 'optimisticEffectState',
default: {},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApolloCache } from '@apollo/client';

type OptimisticEffectResolver<T, QueryVariables> = ({
cache,
entities,
variables,
}: {
cache: ApolloCache<T>;
entities: T[];
variables: QueryVariables;
}) => void;

export type OptimisticEffect<T, QueryVariables> = {
key: string;
typename: string;
variables: QueryVariables;
resolver: OptimisticEffectResolver<T, QueryVariables>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ApolloCache } from '@apollo/client';

import {
Company,
GetCompaniesQuery,
GetCompaniesQueryVariables,
} from '~/generated/graphql';

import { GET_COMPANIES } from '../queries/getCompanies';

function optimisticEffectResolver({
cache,
entities,
variables,
}: {
cache: ApolloCache<Company>;
entities: Company[];
variables: GetCompaniesQueryVariables;
}) {
const existingData = cache.readQuery<GetCompaniesQuery>({
query: GET_COMPANIES,
variables: { orderBy: variables.orderBy, where: variables.where },
});

if (!existingData) {
return;
}

cache.writeQuery({
query: GET_COMPANIES,
variables: { orderBy: variables.orderBy, where: variables.where },
data: {
companies: [...entities, ...existingData.companies],
},
});
}

export function getCompaniesOptimisticEffect(
variables: GetCompaniesQueryVariables,
) {
return {
key: 'generic-entity-table-data-companies',
variables: variables,
typename: 'Company',
resolver: optimisticEffectResolver,
};
}
2 changes: 2 additions & 0 deletions front/src/modules/companies/table/components/CompanyTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { companiesAvailableColumnDefinitions } from '@/companies/constants/companiesAvailableColumnDefinitions';
import { getCompaniesOptimisticEffect } from '@/companies/graphql/optimistic-effects/getCompaniesOptimisticEffect';
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
Expand Down Expand Up @@ -53,6 +54,7 @@ export function CompanyTable() {
<GenericEntityTableData
getRequestResultKey="companies"
useGetRequest={useGetCompaniesQuery}
getRequestOptimisticEffect={getCompaniesOptimisticEffect}
orderBy={orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]}
whereFilters={whereFilters}
filterDefinitionArray={companiesFilters}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ApolloCache } from '@apollo/client';

import {
GetPeopleQuery,
GetPeopleQueryVariables,
Person,
} from '~/generated/graphql';

import { GET_PEOPLE } from '../queries/getPeople';

function optimisticEffectResolver({
cache,
entities,
variables,
}: {
cache: ApolloCache<Person>;
entities: Person[];
variables: GetPeopleQueryVariables;
}) {
const existingData = cache.readQuery<GetPeopleQuery>({
query: GET_PEOPLE,
variables: { orderBy: variables.orderBy, where: variables.where },
});

if (!existingData) {
return;
}

cache.writeQuery({
query: GET_PEOPLE,
variables: { orderBy: variables.orderBy, where: variables.where },
data: {
people: [...entities, ...existingData.people],
},
});
}

export function getPeopleOptimisticEffect(variables: GetPeopleQueryVariables) {
return {
key: 'generic-entity-table-data-person',
variables: variables,
typename: 'Person',
resolver: optimisticEffectResolver,
};
}
2 changes: 2 additions & 0 deletions front/src/modules/people/table/components/PeopleTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { peopleAvailableColumnDefinitions } from '@/people/constants/peopleAvailableColumnDefinitions';
import { getPeopleOptimisticEffect } from '@/people/graphql/optimistic-effect-callback/getPeopleOptimisticEffect';
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
Expand Down Expand Up @@ -52,6 +53,7 @@ export function PeopleTable() {
<GenericEntityTableData
getRequestResultKey="people"
useGetRequest={useGetPeopleQuery}
getRequestOptimisticEffect={getPeopleOptimisticEffect}
orderBy={orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]}
whereFilters={whereFilters}
filterDefinitionArray={peopleFilters}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useEffect } from 'react';

import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { OptimisticEffect } from '@/apollo/optimistic-effect/types/OptimisticEffect';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { SortOrder } from '~/generated/graphql';

export function GenericEntityTableData({
useGetRequest,
getRequestResultKey,
getRequestOptimisticEffect,
orderBy = [
{
createdAt: SortOrder.Desc,
Expand All @@ -19,18 +22,24 @@ export function GenericEntityTableData({
}: {
useGetRequest: any;
getRequestResultKey: string;
getRequestOptimisticEffect: (variables: any) => OptimisticEffect<any, any>;
orderBy?: any;
whereFilters?: any;
filterDefinitionArray: FilterDefinition[];
setActionBarEntries?: () => void;
setContextMenuEntries?: () => void;
}) {
const setEntityTableData = useSetEntityTableData();
const { registerOptimisticEffect } = useOptimisticEffect();

useGetRequest({
variables: { orderBy, where: whereFilters },
onCompleted: (data: any) => {
const entities = data[getRequestResultKey] ?? [];
setEntityTableData(entities, filterDefinitionArray);
registerOptimisticEffect(
getRequestOptimisticEffect({ orderBy, where: whereFilters }),
);
},
});

Expand Down
3 changes: 3 additions & 0 deletions front/src/pages/companies/Companies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { v4 } from 'uuid';

import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { CompanyTable } from '@/companies/table/components/CompanyTable';
import { SEARCH_COMPANY_QUERY } from '@/search/graphql/queries/searchCompanyQuery';
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
Expand Down Expand Up @@ -30,6 +31,7 @@ export function Companies() {
const [insertCompany] = useInsertOneCompanyMutation();
const upsertEntityTableItem = useUpsertEntityTableItem();
const upsertTableRowIds = useUpsertTableRowId();
const { triggerOptimisticEffects } = useOptimisticEffect();

async function handleAddButtonClick() {
const newCompanyId: string = v4();
Expand Down Expand Up @@ -61,6 +63,7 @@ export function Companies() {
if (data?.createOneCompany) {
upsertTableRowIds(data?.createOneCompany.id);
upsertEntityTableItem(data?.createOneCompany);
triggerOptimisticEffects('Company', [data?.createOneCompany]);
}
},
refetchQueries: [getOperationName(SEARCH_COMPANY_QUERY) ?? ''],
Expand Down
5 changes: 4 additions & 1 deletion front/src/pages/people/People.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { v4 } from 'uuid';

import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { PeopleTable } from '@/people/table/components/PeopleTable';
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
Expand All @@ -28,6 +29,7 @@ export function People() {
const [insertOnePerson] = useInsertOnePersonMutation();
const upsertEntityTableItem = useUpsertEntityTableItem();
const upsertTableRowIds = useUpsertTableRowId();
const { triggerOptimisticEffects } = useOptimisticEffect();

async function handleAddButtonClick() {
const newPersonId: string = v4();
Expand All @@ -50,10 +52,11 @@ export function People() {
createdAt: '',
},
},
update: (cache, { data }) => {
update: (_cache, { data }) => {
if (data?.createOnePerson) {
upsertTableRowIds(data?.createOnePerson.id);
upsertEntityTableItem(data?.createOnePerson);
triggerOptimisticEffects('Person', [data?.createOnePerson]);
}
},
});
Expand Down

0 comments on commit a46210b

Please sign in to comment.