-
Notifications
You must be signed in to change notification settings - Fork 2.9k
fix: Replaced custom style hooks defaultValue with proxy #27404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Replaced custom style hooks defaultValue with proxy #27404
Conversation
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 07e0d30:
|
Asset size changesSize Auditor did not detect a change in bundle size for any component! Baseline commit: 34d618882a79b1cd65b2d4903f77caf9d5d83891 (build) |
📊 Bundle size reportUnchanged fixtures
|
Perf Analysis (
|
| Scenario | Render type | Master Ticks | PR Ticks | Iterations | Status |
|---|---|---|---|---|---|
| Avatar | mount | 881 | 907 | 5000 | |
| Button | mount | 590 | 583 | 5000 | |
| Field | mount | 1540 | 1544 | 5000 | |
| FluentProvider | mount | 1091 | 1079 | 5000 | |
| FluentProviderWithTheme | mount | 292 | 297 | 10 | |
| FluentProviderWithTheme | virtual-rerender | 278 | 284 | 10 | |
| FluentProviderWithTheme | virtual-rerender-with-unmount | 290 | 288 | 10 | |
| InfoButton | mount | 200 | 203 | 5000 | |
| MakeStyles | mount | 1407 | 1409 | 50000 | |
| Persona | mount | 2126 | 2103 | 5000 | |
| SpinButton | mount | 1833 | 1851 | 5000 |
| /** | ||
| * @internal | ||
| */ | ||
| export const CustomStyleHooksContext = React.createContext<CustomStyleHooksContextValue | undefined>(undefined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🕵 fluentuiv9 Open the Visual Regressions report to inspect the 1 screenshots
✅ There was 0 screenshots added, 0 screenshots removed, 1925 screenshots unchanged, 0 screenshots with different dimensions and 1 screenshots with visible difference.
unknown 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| FluentProvider CustomStyleHooks.SplitButton.chromium.png | 1605723 | Changed |
| const preProxyDefaultValue: Partial<CustomStyleHooksContextValue> = {}; | ||
|
|
||
| const defaultValueProxyHandler: ProxyHandler<CustomStyleHooksContextValue> = { | ||
| get(_target, _prop, _receiver) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| get(_target, _prop, _receiver) { | |
| get() { |
| @@ -0,0 +1,7 @@ | |||
| { | |||
| "type": "patch", | |||
| "comment": "Added proxy for default custom hook noop", | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| "comment": "Added proxy for default custom hook noop", | |
| "comment": "fix: Use proxy for default custom hook noop", |
| }, | ||
| }; | ||
|
|
||
| const customStyleHooksContextDefaultValue = new Proxy( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeoffCoxMSFT I don't think that it works as expected as after a shallow merge in FluentProvider we are losing the proxy functionality. An example is below:
const noop = () => { };
const preProxyDefaultValue = {};
const defaultValueProxyHandler = {
get() {
return noop;
},
};
const customStyleHooksContextDefaultValue = new Proxy(
preProxyDefaultValue,
defaultValueProxyHandler
);
// ---
function shallowMerge(a, b) {
// Merge impacts perf: we should like to avoid it if it's possible
if (a && b) {
return { ...a, ...b };
}
if (a) {
return a;
}
return b;
}
// ---
const hooksFromProps = {
useButton: 'hook',
useImage: 'hook',
}
const result = shallowMerge(customStyleHooksContextDefaultValue, hooksFromProps) ⬇️⬇️⬇️
result.useButton // string 'hook' ✅
customStyleHooksContextDefaultValue.useFoo // function ✅
result.useFoo // undefined 🟥 We lost the proxy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@layershifter Have you done much work with proxies in the past? I'm not sure what the fix would be here if spread breaks the proxy. Should we do object.assign instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GeoffCoxMSFT FYI: I just merged your PR with tests #27086, so we have a good base to test against 🎉
Option 1
There are few to be aware:
- There is no
isProxy()in JS, so we don't know with what we are dealing - Object spread will not work in in this case
To workaround that we can expose merge() function on the context:
// packages/react-components/react-shared-contexts/src/CustomStyleHooksContext/CustomStyleHooksContext.ts
useDataGridHeaderCellStyles_unstable: CustomStyleHook;
useDataGridSelectionCellStyles_unstable: CustomStyleHook;
+ merge: (overrides: CustomStyleHooksContextValue | Partial<CustomStyleHooksContextValue> | undefined) => void;
};And then modify the handler:
const defaultValueProxyHandler: ProxyHandler<CustomStyleHooksContextValue> = {
get(_target, _prop, _receiver) {
if (_prop === 'merge') {
return (overrides: CustomStyleHooksContextValue | Partial<CustomStyleHooksContextValue> | undefined) => {
if (overrides) {
Object.assign(preProxyDefaultValue, overrides);
}
};
}
if (_target[_prop]) {
return _target[_prop];
}
return noop;
},
};(in this case we mutate target object + we are considering target as a source of hooks)
Then in useFluentProvider:
// packages/react-components/react-provider/src/components/FluentProvider/useFluentProvider.ts
- // parentCustomStyleHooks will not be a partial
- const mergedCustomStyleHooks = shallowMerge(
- parentCustomStyleHooks,
- customStyleHooks_unstable,
- ) as CustomStyleHooksContextValue;
+. parentCustomStyleHooks.merge(customStyleHooks_unstable);
// ...
- customStyleHooks_unstable: mergedCustomStyleHooks,
+ customStyleHooks_unstable: parentCustomStyleHooks,Option 2
I talked with @ling1726 and his a better idea that does not require Proxy and IMO is clearer.
// The list of hooks is built from the exports from react-components/src/index
type ComponentNames = 'Button' | 'Image' // etc.
type CustomStyleHooksContextValue = {
(componentName: ComponentNames): CustomStyleHook;
current: Partial<Record<ComponentNames, CustomStyleHook>>;
};
/**
* @internal
*/
export const CustomStyleHooksContext = React.createContext<CustomStyleHooksContextValue | undefined>(undefined);
const noop = () => {};
const customStyleHooksContextDefaultValue: CustomStyleHooksContextValue = componentName => {
return customStyleHooksContextDefaultValue.current[componentName] || noop;
};
customStyleHooksContextDefaultValue.current = {};In this case merging will be just:
const parentCustomStyleHooks = useCustomStyleHooks();
parentCustomStyleHooks.current = {
...parentCustomStyleHooks,
...props.customStyleHooks,
}But it will require changes in components, for example:
-const { useButtonStyles_unstable: useCustomStyles } = useCustomStyleHooks_unstable();
-useCustomStyles(state);
+const useCustomStyles = useCustomStyleHooks_unstable();
+useCustomStyles('Button')(state)I would say that this approach clearer, but it's up to you to decide 🐱
|
Closing in favor of #27491 |

Problem
The large number of no-op implementation for the default value of the custom hooks is preventing bundling minification.
Changes
Issues
Fixes #27139