Skip to content

Commit

Permalink
useDirectory.fetch migrated to useEffect and cleanup fn to prevent me…
Browse files Browse the repository at this point in the history
…mory leaks
  • Loading branch information
nelsonni committed Mar 6, 2021
1 parent a9edfd7 commit b440fe6
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 19 deletions.
6 changes: 4 additions & 2 deletions __test__/useDirectory.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ describe('useDirectory', () => {
jest.spyOn(metafiles, 'filterDirectoryContainsTypes').mockResolvedValue({ directories: [], files: ['foo/bar.js'] });
const { result, waitForNextUpdate } = renderHook(() => useDirectory('foo'), { wrapper });

result.current.fetch();
await waitForNextUpdate();
await act(async () => {
result.current.fetch();
await waitForNextUpdate();
})

expect(result.current.root).toEqual(
expect.objectContaining({ id: '28', name: 'foo' })
Expand Down
47 changes: 30 additions & 17 deletions src/store/hooks/useDirectory.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { useCallback, useState } from 'react'
import { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import { ThunkDispatch } from 'redux-thunk'
import { PathLike } from 'fs-extra'

import type { Metafile } from '../../types'
import { RootState } from '../root'
import { Action } from '../actions'
import { getMetafile, filterDirectoryContainsTypes, ContainsRequiredMetafile } from '../../containers/metafiles'
import { getMetafile, filterDirectoryContainsTypes, MetafileWithContains } from '../../containers/metafiles'

type useDirectoryHook = {
root: Metafile | undefined,
directories: string[],
files: string[],
fetch: () => Promise<void>
fetch: () => void
};

/**
Expand All @@ -29,32 +29,45 @@ export const useDirectory = (initialRoot: Metafile | PathLike): useDirectoryHook
const [root, setRoot] = useState<Metafile | undefined>();
const [directories, setDirectories] = useState<string[]>([]);
const [files, setFiles] = useState<string[]>([]);
const [trigger, setTrigger] = useState(false);

// Type guard to verify and return a Metafile type predicate
const isMetafile = (untypedRoot: unknown): untypedRoot is Metafile => (untypedRoot as Metafile).id ? true : false;

const fetch = useCallback(async () => {
/**
useEffect(() => {
const fetchData = async () => {
/**
* Calling `setState` on a React useState hook does not immediately update the state, and instead enqueues a re-render of
* the component that will update state after the rerender. Therefore, we cannot use the `root` state directly on the same
* tick as it is set (via `setRoot`) and have to carry `rootMetafile` between the steps in this callback.
*/
let rootMetafile = root;
let rootMetafile = root;

if (!root) {
// since not root exists, use `initialRoot` to get a metafile and update `root`
rootMetafile = isMetafile(initialRoot) ? await dispatch(getMetafile({ id: initialRoot.id })) :
await dispatch(getMetafile({ filepath: initialRoot }));
setRoot(rootMetafile);
if (!root) {
// since not root exists, use `initialRoot` to get a metafile and update `root`
rootMetafile = isMetafile(initialRoot) ? await dispatch(getMetafile({ id: initialRoot.id })) :
await dispatch(getMetafile({ filepath: initialRoot }));
setRoot(rootMetafile);
}

if (rootMetafile && rootMetafile.contains) {
// update the `directories` and `files` states only if there are changes
const updates = await filterDirectoryContainsTypes(rootMetafile as MetafileWithContains, false);
if (JSON.stringify(directories) !== JSON.stringify(updates.directories)) setDirectories(updates.directories);
if (JSON.stringify(files) !== JSON.stringify(updates.files)) setFiles(updates.files);
}
}

if (rootMetafile && rootMetafile.contains) {
// update the `directories` and `files` states only if there are changes
const updates = await filterDirectoryContainsTypes(rootMetafile as ContainsRequiredMetafile, false);
if (JSON.stringify(directories) !== JSON.stringify(updates.directories)) setDirectories(updates.directories);
if (JSON.stringify(files) !== JSON.stringify(updates.files)) setFiles(updates.files);
if (trigger) fetchData();

return () => {
// cleanup function to disable additional calls to fetchData
setTrigger(false);
}
}, [root, dispatch, initialRoot, directories, files]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [trigger]);

const fetch = () => setTrigger(true);

return { root, directories, files, fetch };
}

0 comments on commit b440fe6

Please sign in to comment.