Skip to content

Commit

Permalink
fix: Suspense will make cache counter not sync (#120)
Browse files Browse the repository at this point in the history
* fix: cache counter

* test: add StrictMode test

* fix: StrictMode broken

* chore: useEffect

* refactor: merg
  • Loading branch information
zombieJ authored Jul 3, 2023
1 parent 97c8016 commit 34c1d7b
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 45 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@emotion/unitless": "^0.7.5",
"classnames": "^2.3.1",
"csstype": "^3.0.10",
"rc-util": "^5.27.0",
"rc-util": "^5.34.1",
"stylis": "^4.0.13"
},
"devDependencies": {
Expand Down
53 changes: 32 additions & 21 deletions src/hooks/useGlobalCache.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import StyleContext from '../StyleContext';
import type { KeyType } from '../Cache';
import StyleContext from '../StyleContext';
import useHMR from './useHMR';

export default function useClientCache<CacheType>(
Expand All @@ -14,32 +14,44 @@ export default function useClientCache<CacheType>(

const HMRUpdate = useHMR();

// Create cache
React.useMemo(
() => {
globalCache.update(fullPath, (prevCache) => {
const [times = 0, cache] = prevCache || [];
type UpdaterArgs = [times: number, cache: CacheType];

// HMR should always ignore cache since developer may change it
let tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove?.(tmpCache, HMRUpdate);
tmpCache = null;
}
const buildCache = (updater?: (data: UpdaterArgs) => UpdaterArgs) => {
globalCache.update(fullPath, (prevCache) => {
const [times = 0, cache] = prevCache || [];

const mergedCache = tmpCache || cacheFn();
// HMR should always ignore cache since developer may change it
let tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove?.(tmpCache, HMRUpdate);
tmpCache = null;
}

return [times + 1, mergedCache];
});
},
const mergedCache = tmpCache || cacheFn();

const data: UpdaterArgs = [times, mergedCache];

// Call updater if need additional logic
return updater ? updater(data) : data;
});
};

// Create cache
React.useMemo(
() => buildCache(),
/* eslint-disable react-hooks/exhaustive-deps */
[fullPath.join('_')],
/* eslint-enable */
);

// Remove if no need anymore
React.useEffect(
() => () => {
React.useEffect(() => {
// It's bad to call build again in effect.
// But we have to do this since StrictMode will call effect twice
// which will clear cache on the first time.
buildCache(([times, cache]) => [times + 1, cache]);

return () => {
globalCache.update(fullPath, (prevCache) => {
const [times = 0, cache] = prevCache || [];
const nextCount = times - 1;
Expand All @@ -51,9 +63,8 @@ export default function useClientCache<CacheType>(

return [times - 1, cache];
});
},
fullPath,
);
};
}, fullPath);

return globalCache.get(fullPath)![1];
}
61 changes: 38 additions & 23 deletions tests/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,31 +91,46 @@ describe('csssinjs', () => {

// We will not remove style immediately,
// but remove when second style patched.
it('remove old style to ensure style set only exist one', () => {
const getBox = (props?: BoxProps) => <Box {...props} />;

const { rerender } = render(getBox());
expect(document.head.querySelectorAll('style')).toHaveLength(1);
describe('remove old style to ensure style set only exist one', () => {
function test(
name: string,
wrapperFn?: (node: React.ReactElement) => React.ReactElement,
) {
it(name, () => {
const getBox = (props?: BoxProps) => {
const box: React.ReactElement = <Box {...props} />;

return wrapperFn?.(box) || box;
};

const { rerender } = render(getBox());
expect(document.head.querySelectorAll('style')).toHaveLength(1);

// First change
rerender(
getBox({
propToken: {
primaryColor: 'red',
},
}),
);
expect(document.head.querySelectorAll('style')).toHaveLength(1);

// Second change
rerender(
getBox({
propToken: {
primaryColor: 'green',
},
}),
);
expect(document.head.querySelectorAll('style')).toHaveLength(1);
});
}

// First change
rerender(
getBox({
propToken: {
primaryColor: 'red',
},
}),
);
expect(document.head.querySelectorAll('style')).toHaveLength(1);
test('normal');

// Second change
rerender(
getBox({
propToken: {
primaryColor: 'green',
},
}),
);
expect(document.head.querySelectorAll('style')).toHaveLength(1);
test('StrictMode', (ele) => <React.StrictMode>{ele}</React.StrictMode>);
});

it('remove style when unmount', () => {
Expand Down

0 comments on commit 34c1d7b

Please sign in to comment.