fix: reduce useState and useEffect (#2223)
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import 'fake-indexeddb/auto';
|
import 'fake-indexeddb/auto';
|
||||||
|
|
||||||
|
import { UNTITLED_WORKSPACE_NAME } from '@affine/env';
|
||||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
@@ -10,7 +11,7 @@ import { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
|||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { useBlockSuiteWorkspacePageIsPublic } from '@toeverything/hooks/use-block-suite-workspace-page-is-public';
|
import { useBlockSuiteWorkspacePageIsPublic } from '@toeverything/hooks/use-block-suite-workspace-page-is-public';
|
||||||
import { useBlockSuiteWorkspacePageTitle } from '@toeverything/hooks/use-block-suite-workspace-page-title';
|
import { useBlockSuiteWorkspacePageTitle } from '@toeverything/hooks/use-block-suite-workspace-page-title';
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test, vitest } from 'vitest';
|
||||||
import { beforeEach } from 'vitest';
|
import { beforeEach } from 'vitest';
|
||||||
|
|
||||||
import { useBlockSuiteWorkspaceName } from '../use-block-suite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '../use-block-suite-workspace-name';
|
||||||
@@ -30,9 +31,9 @@ beforeEach(async () => {
|
|||||||
const frameId = page.addBlock('affine:frame', {}, pageBlockId);
|
const frameId = page.addBlock('affine:frame', {}, pageBlockId);
|
||||||
page.addBlock('affine:paragraph', {}, frameId);
|
page.addBlock('affine:paragraph', {}, frameId);
|
||||||
};
|
};
|
||||||
initPage(blockSuiteWorkspace.createPage('page0'));
|
initPage(blockSuiteWorkspace.createPage({ id: 'page0' }));
|
||||||
initPage(blockSuiteWorkspace.createPage('page1'));
|
initPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
|
||||||
initPage(blockSuiteWorkspace.createPage('page2'));
|
initPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('useBlockSuiteWorkspaceName', () => {
|
describe('useBlockSuiteWorkspaceName', () => {
|
||||||
@@ -48,6 +49,17 @@ describe('useBlockSuiteWorkspaceName', () => {
|
|||||||
workspaceNameHook.result.current[1]('test 3');
|
workspaceNameHook.result.current[1]('test 3');
|
||||||
expect(blockSuiteWorkspace.meta.name).toBe('test 3');
|
expect(blockSuiteWorkspace.meta.name).toBe('test 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('null', () => {
|
||||||
|
const workspaceNameHook = renderHook(() =>
|
||||||
|
useBlockSuiteWorkspaceName(null)
|
||||||
|
);
|
||||||
|
vitest.spyOn(globalThis.console, 'warn');
|
||||||
|
expect(workspaceNameHook.result.current[0]).toBe(UNTITLED_WORKSPACE_NAME);
|
||||||
|
workspaceNameHook.result.current[1]('test');
|
||||||
|
expect(globalThis.console.warn).toHaveBeenCalledTimes(2);
|
||||||
|
expect(workspaceNameHook.result.current[0]).toBe(UNTITLED_WORKSPACE_NAME);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('useBlockSuiteWorkspacePageTitle', () => {
|
describe('useBlockSuiteWorkspacePageTitle', () => {
|
||||||
|
|||||||
@@ -1,32 +1,51 @@
|
|||||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env';
|
import { UNTITLED_WORKSPACE_NAME } from '@affine/env';
|
||||||
import type { Workspace } from '@blocksuite/store';
|
import type { Workspace } from '@blocksuite/store';
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import type { Atom, WritableAtom } from 'jotai';
|
||||||
|
import { atom, useAtom } from 'jotai';
|
||||||
|
|
||||||
|
const weakMap = new WeakMap<
|
||||||
|
Workspace,
|
||||||
|
WritableAtom<string, [string], void> & Atom<string>
|
||||||
|
>();
|
||||||
|
|
||||||
|
const emptyWorkspaceNameAtom = atom(UNTITLED_WORKSPACE_NAME, () => {
|
||||||
|
console.warn('you cannot set the name of an null workspace.');
|
||||||
|
console.warn('this is a bug in the code.');
|
||||||
|
});
|
||||||
|
|
||||||
export function useBlockSuiteWorkspaceName(
|
export function useBlockSuiteWorkspaceName(
|
||||||
blockSuiteWorkspace: Workspace | null
|
blockSuiteWorkspace: Workspace | null
|
||||||
) {
|
) {
|
||||||
const [name, set] = useState(
|
let nameAtom:
|
||||||
() => blockSuiteWorkspace?.meta.name ?? UNTITLED_WORKSPACE_NAME
|
| (WritableAtom<string, [string], void> & Atom<string>)
|
||||||
);
|
| undefined;
|
||||||
useEffect(() => {
|
if (!blockSuiteWorkspace) {
|
||||||
if (blockSuiteWorkspace) {
|
nameAtom = emptyWorkspaceNameAtom;
|
||||||
set(blockSuiteWorkspace.meta.name ?? '');
|
} else if (!weakMap.has(blockSuiteWorkspace)) {
|
||||||
|
const baseAtom = atom<string>(
|
||||||
|
blockSuiteWorkspace.meta.name ?? UNTITLED_WORKSPACE_NAME
|
||||||
|
);
|
||||||
|
const writableAtom = atom(
|
||||||
|
get => get(baseAtom),
|
||||||
|
(get, set, name: string) => {
|
||||||
|
blockSuiteWorkspace.meta.setName(name);
|
||||||
|
set(baseAtom, name);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
baseAtom.onMount = set => {
|
||||||
const dispose = blockSuiteWorkspace.meta.commonFieldsUpdated.on(() => {
|
const dispose = blockSuiteWorkspace.meta.commonFieldsUpdated.on(() => {
|
||||||
set(blockSuiteWorkspace.meta.name ?? '');
|
set(blockSuiteWorkspace.meta.name ?? '');
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
dispose.dispose();
|
dispose.dispose();
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
}, [blockSuiteWorkspace]);
|
weakMap.set(blockSuiteWorkspace, writableAtom);
|
||||||
const setName = useCallback(
|
nameAtom = writableAtom;
|
||||||
(name: string) => {
|
} else {
|
||||||
assertExists(blockSuiteWorkspace);
|
nameAtom = weakMap.get(blockSuiteWorkspace);
|
||||||
blockSuiteWorkspace.meta.setName(name);
|
assertExists(nameAtom);
|
||||||
set(name);
|
}
|
||||||
},
|
return useAtom(nameAtom);
|
||||||
[blockSuiteWorkspace]
|
|
||||||
);
|
|
||||||
return [name, setName] as const;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,36 @@
|
|||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { assertExists } from '@blocksuite/store';
|
||||||
|
import type { Atom, WritableAtom } from 'jotai';
|
||||||
|
import { atom, useAtom } from 'jotai';
|
||||||
|
|
||||||
|
const weakMap = new WeakMap<
|
||||||
|
Page,
|
||||||
|
WritableAtom<boolean, [boolean], void> & Atom<boolean>
|
||||||
|
>();
|
||||||
|
|
||||||
export function useBlockSuiteWorkspacePageIsPublic(page: Page) {
|
export function useBlockSuiteWorkspacePageIsPublic(page: Page) {
|
||||||
const [isPublic, set] = useState<boolean>(() => page.meta.isPublic ?? false);
|
if (!weakMap.has(page)) {
|
||||||
useEffect(() => {
|
const baseAtom = atom<boolean>(page.meta.isPublic ?? false);
|
||||||
const disposable = page.workspace.meta.pageMetasUpdated.on(() => {
|
const writableAtom = atom(
|
||||||
set(page.meta.isPublic ?? false);
|
get => get(baseAtom),
|
||||||
});
|
(get, set, isPublic: boolean) => {
|
||||||
return () => {
|
page.workspace.setPageMeta(page.id, {
|
||||||
disposable.dispose();
|
isPublic,
|
||||||
};
|
});
|
||||||
}, [page]);
|
set(baseAtom, isPublic);
|
||||||
const setIsPublic = useCallback(
|
}
|
||||||
(isPublic: boolean) => {
|
);
|
||||||
set(isPublic);
|
baseAtom.onMount = set => {
|
||||||
page.workspace.setPageMeta(page.id, {
|
const disposable = page.workspace.meta.pageMetasUpdated.on(() => {
|
||||||
isPublic,
|
set(page.meta.isPublic ?? false);
|
||||||
});
|
});
|
||||||
},
|
return () => {
|
||||||
[page.id, page.workspace]
|
disposable.dispose();
|
||||||
);
|
};
|
||||||
return [isPublic, setIsPublic] as const;
|
};
|
||||||
|
weakMap.set(page, writableAtom);
|
||||||
|
}
|
||||||
|
const isPublicAtom = weakMap.get(page);
|
||||||
|
assertExists(isPublicAtom);
|
||||||
|
return useAtom(isPublicAtom);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,40 @@
|
|||||||
import type { Workspace } from '@blocksuite/store';
|
import type { Workspace } from '@blocksuite/store';
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
import { useEffect, useState } from 'react';
|
import type { Atom } from 'jotai';
|
||||||
|
import { atom, useAtomValue } from 'jotai';
|
||||||
|
|
||||||
|
const weakMap = new WeakMap<Workspace, Map<string, Atom<string>>>();
|
||||||
|
|
||||||
|
function getAtom(w: Workspace, pageId: string): Atom<string> {
|
||||||
|
if (!weakMap.has(w)) {
|
||||||
|
weakMap.set(w, new Map());
|
||||||
|
}
|
||||||
|
const map = weakMap.get(w);
|
||||||
|
assertExists(map);
|
||||||
|
if (!map.has(pageId)) {
|
||||||
|
const baseAtom = atom<string>(w.getPage(pageId)?.meta.title || 'Untitled');
|
||||||
|
baseAtom.onMount = set => {
|
||||||
|
const disposable = w.meta.pageMetasUpdated.on(() => {
|
||||||
|
const page = w.getPage(pageId);
|
||||||
|
assertExists(page);
|
||||||
|
set(page?.meta.title || 'Untitled');
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
disposable.dispose();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
map.set(pageId, baseAtom);
|
||||||
|
return baseAtom;
|
||||||
|
} else {
|
||||||
|
return map.get(pageId) as Atom<string>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useBlockSuiteWorkspacePageTitle(
|
export function useBlockSuiteWorkspacePageTitle(
|
||||||
blockSuiteWorkspace: Workspace,
|
blockSuiteWorkspace: Workspace,
|
||||||
pageId: string
|
pageId: string
|
||||||
) {
|
) {
|
||||||
const page = blockSuiteWorkspace.getPage(pageId);
|
const titleAtom = getAtom(blockSuiteWorkspace, pageId);
|
||||||
const [title, setTitle] = useState(() => page?.meta.title || 'AFFiNE');
|
assertExists(titleAtom);
|
||||||
useEffect(() => {
|
return useAtomValue(titleAtom);
|
||||||
const page = blockSuiteWorkspace.getPage(pageId);
|
|
||||||
setTitle(page?.meta.title || 'Untitled');
|
|
||||||
const dispose = blockSuiteWorkspace.meta.pageMetasUpdated.on(() => {
|
|
||||||
const page = blockSuiteWorkspace.getPage(pageId);
|
|
||||||
assertExists(page);
|
|
||||||
setTitle(page?.meta.title || 'Untitled');
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
dispose.dispose();
|
|
||||||
};
|
|
||||||
}, [blockSuiteWorkspace, pageId]);
|
|
||||||
|
|
||||||
return title;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,55 @@
|
|||||||
import type { Page, Workspace } from '@blocksuite/store';
|
import type { Page, Workspace } from '@blocksuite/store';
|
||||||
import { useEffect, useState } from 'react';
|
import { assertExists, DisposableGroup } from '@blocksuite/store';
|
||||||
|
import type { Atom } from 'jotai';
|
||||||
|
import { atom, useAtomValue } from 'jotai';
|
||||||
|
|
||||||
|
const weakMap = new WeakMap<Workspace, Map<string, Atom<Page | null>>>();
|
||||||
|
|
||||||
|
const emptyAtom = atom<Page | null>(null);
|
||||||
|
|
||||||
|
function getAtom(w: Workspace, pageId: string | null): Atom<Page | null> {
|
||||||
|
if (!pageId) {
|
||||||
|
return emptyAtom;
|
||||||
|
}
|
||||||
|
if (!weakMap.has(w)) {
|
||||||
|
weakMap.set(w, new Map());
|
||||||
|
}
|
||||||
|
const map = weakMap.get(w);
|
||||||
|
assertExists(map);
|
||||||
|
if (!map.has(pageId)) {
|
||||||
|
const baseAtom = atom(w.getPage(pageId));
|
||||||
|
baseAtom.onMount = set => {
|
||||||
|
const group = new DisposableGroup();
|
||||||
|
group.add(
|
||||||
|
w.slots.pageAdded.on(id => {
|
||||||
|
if (pageId === id) {
|
||||||
|
set(w.getPage(id));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
group.add(
|
||||||
|
w.slots.pageRemoved.on(id => {
|
||||||
|
if (pageId === id) {
|
||||||
|
set(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
group.dispose();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
map.set(pageId, baseAtom);
|
||||||
|
return baseAtom;
|
||||||
|
} else {
|
||||||
|
return map.get(pageId) as Atom<Page | null>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useBlockSuiteWorkspacePage(
|
export function useBlockSuiteWorkspacePage(
|
||||||
blockSuiteWorkspace: Workspace,
|
blockSuiteWorkspace: Workspace,
|
||||||
pageId: string | null
|
pageId: string | null
|
||||||
): Page | null {
|
): Page | null {
|
||||||
const [page, setPage] = useState(() => {
|
const pageAtom = getAtom(blockSuiteWorkspace, pageId);
|
||||||
if (pageId === null) {
|
assertExists(pageAtom);
|
||||||
return null;
|
return useAtomValue(pageAtom);
|
||||||
}
|
|
||||||
return blockSuiteWorkspace.getPage(pageId);
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
if (pageId) {
|
|
||||||
setPage(blockSuiteWorkspace.getPage(pageId));
|
|
||||||
}
|
|
||||||
}, [blockSuiteWorkspace, pageId]);
|
|
||||||
useEffect(() => {
|
|
||||||
const disposable = blockSuiteWorkspace.slots.pageAdded.on(id => {
|
|
||||||
if (pageId === id) {
|
|
||||||
setPage(blockSuiteWorkspace.getPage(id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
disposable.dispose();
|
|
||||||
};
|
|
||||||
}, [blockSuiteWorkspace, pageId]);
|
|
||||||
return page;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user