Implement Optimistic Effects (#1415)
* 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
This commit is contained in:
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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: {},
|
||||||
|
});
|
||||||
@ -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>;
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { companiesAvailableColumnDefinitions } from '@/companies/constants/companiesAvailableColumnDefinitions';
|
import { companiesAvailableColumnDefinitions } from '@/companies/constants/companiesAvailableColumnDefinitions';
|
||||||
|
import { getCompaniesOptimisticEffect } from '@/companies/graphql/optimistic-effects/getCompaniesOptimisticEffect';
|
||||||
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
|
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
|
||||||
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
|
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
|
||||||
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
|
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
|
||||||
@ -53,6 +54,7 @@ export function CompanyTable() {
|
|||||||
<GenericEntityTableData
|
<GenericEntityTableData
|
||||||
getRequestResultKey="companies"
|
getRequestResultKey="companies"
|
||||||
useGetRequest={useGetCompaniesQuery}
|
useGetRequest={useGetCompaniesQuery}
|
||||||
|
getRequestOptimisticEffect={getCompaniesOptimisticEffect}
|
||||||
orderBy={orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]}
|
orderBy={orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]}
|
||||||
whereFilters={whereFilters}
|
whereFilters={whereFilters}
|
||||||
filterDefinitionArray={companiesFilters}
|
filterDefinitionArray={companiesFilters}
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { peopleAvailableColumnDefinitions } from '@/people/constants/peopleAvailableColumnDefinitions';
|
import { peopleAvailableColumnDefinitions } from '@/people/constants/peopleAvailableColumnDefinitions';
|
||||||
|
import { getPeopleOptimisticEffect } from '@/people/graphql/optimistic-effect-callback/getPeopleOptimisticEffect';
|
||||||
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
|
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
|
||||||
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
|
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
|
||||||
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
|
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
|
||||||
@ -52,6 +53,7 @@ export function PeopleTable() {
|
|||||||
<GenericEntityTableData
|
<GenericEntityTableData
|
||||||
getRequestResultKey="people"
|
getRequestResultKey="people"
|
||||||
useGetRequest={useGetPeopleQuery}
|
useGetRequest={useGetPeopleQuery}
|
||||||
|
getRequestOptimisticEffect={getPeopleOptimisticEffect}
|
||||||
orderBy={orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]}
|
orderBy={orderBy.length ? orderBy : [{ createdAt: SortOrder.Desc }]}
|
||||||
whereFilters={whereFilters}
|
whereFilters={whereFilters}
|
||||||
filterDefinitionArray={peopleFilters}
|
filterDefinitionArray={peopleFilters}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
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 { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||||
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
||||||
import { SortOrder } from '~/generated/graphql';
|
import { SortOrder } from '~/generated/graphql';
|
||||||
@ -7,6 +9,7 @@ import { SortOrder } from '~/generated/graphql';
|
|||||||
export function GenericEntityTableData({
|
export function GenericEntityTableData({
|
||||||
useGetRequest,
|
useGetRequest,
|
||||||
getRequestResultKey,
|
getRequestResultKey,
|
||||||
|
getRequestOptimisticEffect,
|
||||||
orderBy = [
|
orderBy = [
|
||||||
{
|
{
|
||||||
createdAt: SortOrder.Desc,
|
createdAt: SortOrder.Desc,
|
||||||
@ -19,6 +22,7 @@ export function GenericEntityTableData({
|
|||||||
}: {
|
}: {
|
||||||
useGetRequest: any;
|
useGetRequest: any;
|
||||||
getRequestResultKey: string;
|
getRequestResultKey: string;
|
||||||
|
getRequestOptimisticEffect: (variables: any) => OptimisticEffect<any, any>;
|
||||||
orderBy?: any;
|
orderBy?: any;
|
||||||
whereFilters?: any;
|
whereFilters?: any;
|
||||||
filterDefinitionArray: FilterDefinition[];
|
filterDefinitionArray: FilterDefinition[];
|
||||||
@ -26,11 +30,16 @@ export function GenericEntityTableData({
|
|||||||
setContextMenuEntries?: () => void;
|
setContextMenuEntries?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const setEntityTableData = useSetEntityTableData();
|
const setEntityTableData = useSetEntityTableData();
|
||||||
|
const { registerOptimisticEffect } = useOptimisticEffect();
|
||||||
|
|
||||||
useGetRequest({
|
useGetRequest({
|
||||||
variables: { orderBy, where: whereFilters },
|
variables: { orderBy, where: whereFilters },
|
||||||
onCompleted: (data: any) => {
|
onCompleted: (data: any) => {
|
||||||
const entities = data[getRequestResultKey] ?? [];
|
const entities = data[getRequestResultKey] ?? [];
|
||||||
setEntityTableData(entities, filterDefinitionArray);
|
setEntityTableData(entities, filterDefinitionArray);
|
||||||
|
registerOptimisticEffect(
|
||||||
|
getRequestOptimisticEffect({ orderBy, where: whereFilters }),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||||
import { CompanyTable } from '@/companies/table/components/CompanyTable';
|
import { CompanyTable } from '@/companies/table/components/CompanyTable';
|
||||||
import { SEARCH_COMPANY_QUERY } from '@/search/graphql/queries/searchCompanyQuery';
|
import { SEARCH_COMPANY_QUERY } from '@/search/graphql/queries/searchCompanyQuery';
|
||||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||||
@ -30,6 +31,7 @@ export function Companies() {
|
|||||||
const [insertCompany] = useInsertOneCompanyMutation();
|
const [insertCompany] = useInsertOneCompanyMutation();
|
||||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||||
const upsertTableRowIds = useUpsertTableRowId();
|
const upsertTableRowIds = useUpsertTableRowId();
|
||||||
|
const { triggerOptimisticEffects } = useOptimisticEffect();
|
||||||
|
|
||||||
async function handleAddButtonClick() {
|
async function handleAddButtonClick() {
|
||||||
const newCompanyId: string = v4();
|
const newCompanyId: string = v4();
|
||||||
@ -61,6 +63,7 @@ export function Companies() {
|
|||||||
if (data?.createOneCompany) {
|
if (data?.createOneCompany) {
|
||||||
upsertTableRowIds(data?.createOneCompany.id);
|
upsertTableRowIds(data?.createOneCompany.id);
|
||||||
upsertEntityTableItem(data?.createOneCompany);
|
upsertEntityTableItem(data?.createOneCompany);
|
||||||
|
triggerOptimisticEffects('Company', [data?.createOneCompany]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refetchQueries: [getOperationName(SEARCH_COMPANY_QUERY) ?? ''],
|
refetchQueries: [getOperationName(SEARCH_COMPANY_QUERY) ?? ''],
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||||
import { PeopleTable } from '@/people/table/components/PeopleTable';
|
import { PeopleTable } from '@/people/table/components/PeopleTable';
|
||||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||||
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
||||||
@ -28,6 +29,7 @@ export function People() {
|
|||||||
const [insertOnePerson] = useInsertOnePersonMutation();
|
const [insertOnePerson] = useInsertOnePersonMutation();
|
||||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||||
const upsertTableRowIds = useUpsertTableRowId();
|
const upsertTableRowIds = useUpsertTableRowId();
|
||||||
|
const { triggerOptimisticEffects } = useOptimisticEffect();
|
||||||
|
|
||||||
async function handleAddButtonClick() {
|
async function handleAddButtonClick() {
|
||||||
const newPersonId: string = v4();
|
const newPersonId: string = v4();
|
||||||
@ -50,10 +52,11 @@ export function People() {
|
|||||||
createdAt: '',
|
createdAt: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: (cache, { data }) => {
|
update: (_cache, { data }) => {
|
||||||
if (data?.createOnePerson) {
|
if (data?.createOnePerson) {
|
||||||
upsertTableRowIds(data?.createOnePerson.id);
|
upsertTableRowIds(data?.createOnePerson.id);
|
||||||
upsertEntityTableItem(data?.createOnePerson);
|
upsertEntityTableItem(data?.createOnePerson);
|
||||||
|
triggerOptimisticEffects('Person', [data?.createOnePerson]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user