Skip to content

Commit

Permalink
fix(blur): Migrate web to a function component and fix reanimated err…
Browse files Browse the repository at this point in the history
…ors (#27721)

# Why

- [`setNativeProps` was
removed](necolas/react-native-web@e68c327)
so we need to set the style props directly. This can be reproduced by
opening NCL and going to the blur route.
- Tested the change in both safari and chrome.
- This PR also drops support for older browsers in favor of the
unsupported style warning.

---------

Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com>
  • Loading branch information
2 people authored and marklawlor committed Mar 18, 2024
1 parent b81d115 commit 4acfdaa
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 119 deletions.
2 changes: 2 additions & 0 deletions packages/expo-blur/CHANGELOG.md
Expand Up @@ -10,6 +10,8 @@

### 🐛 Bug fixes

- Migrate web to a function component and fix reanimated errors related to [`setNativeProps` being removed](https://github.com/necolas/react-native-web/commit/e68c32770757194af103cca0095c0c204995505b). ([#27721](https://github.com/expo/expo/pull/27721) by [@EvanBacon](https://github.com/EvanBacon))

### 💡 Others

## 12.9.2 - 2024-02-16
Expand Down
20 changes: 9 additions & 11 deletions packages/expo-blur/build/BlurView.web.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-blur/build/BlurView.web.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 34 additions & 45 deletions packages/expo-blur/build/BlurView.web.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-blur/build/BlurView.web.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 49 additions & 50 deletions packages/expo-blur/src/BlurView.web.tsx
@@ -1,65 +1,64 @@
// Copyright © 2024 650 Industries.

'use client';

import * as React from 'react';
import { forwardRef, useImperativeHandle, useRef } from 'react';
import { View } from 'react-native';

import { BlurViewProps } from './BlurView.types';
import getBackgroundColor from './getBackgroundColor';

// TODO: Class components are not supported with React Server Components.
export default class BlurView extends React.Component<BlurViewProps> {
private blurViewRef = React.createRef<View>();
const BlurView = forwardRef<{ setNativeProps: (props: BlurViewProps) => void }, BlurViewProps>(
({ tint = 'default', intensity = 50, style, ...props }, ref) => {
const blurViewRef = useRef<HTMLDivElement>(null);
const blurStyle = getBlurStyle({ tint, intensity });

/**
* Reanimated will detect and call this function with animated styles passed as props on every
* animation frame. We want to extract intensity from the props, then create and apply new styles,
* which create the blur based on the intensity and current tint.
*/
setNativeProps(nativeProps) {
const { style, tint, intensity: standardIntensity } = this.props;
const intensity = nativeProps.style.intensity ?? standardIntensity;
const blurStyle = getBlurStyle({ intensity, tint });
this.blurViewRef?.current?.setNativeProps({
...nativeProps,
style: [style, blurStyle, nativeProps.style],
});
}
useImperativeHandle(
ref,
() => ({
setNativeProps: (nativeProps: BlurViewProps) => {
if (!blurViewRef.current?.style) {
return;
}

render() {
const { tint = 'default', intensity = 50, style, ...props } = this.props;
const blurStyle = getBlurStyle({ tint, intensity });
return <View {...props} style={[style, blurStyle]} ref={this.blurViewRef} />;
}
}
// @ts-expect-error: `style.intensity` is not defined in the types
const nextIntensity = nativeProps.style?.intensity ?? intensity;
const blurStyle = getBlurStyle({ intensity: nextIntensity, tint: tint ?? 'default' });
if (nativeProps.style) {
for (const key in nativeProps.style) {
if (key !== 'intensity') {
blurViewRef.current.style[key] = nativeProps.style[key];
}
}
}

function isBlurSupported(): boolean {
// TODO: Replace with CSS or static extraction to ensure hydration errors cannot happen.
// Enable by default in Node.js
if (typeof window === 'undefined') {
return true;
blurViewRef.current.style.backgroundColor = blurStyle.backgroundColor;
blurViewRef.current.style.backdropFilter = blurStyle.backdropFilter;
blurViewRef.current.style['webkitBackdropFilter'] = blurStyle.WebkitBackdropFilter;
},
}),
[intensity, tint]
);

return (
<View
{...props}
style={[style, blurStyle]}
/** @ts-expect-error: mismatch in ref type to support manually setting style props. */
ref={blurViewRef}
/>
);
}
// https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports
// https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility
return (
typeof CSS !== 'undefined' &&
(CSS.supports('-webkit-backdrop-filter', 'blur(1px)') ||
CSS.supports('backdrop-filter', 'blur(1px)'))
);
}
);

function getBlurStyle({ intensity, tint }): Record<string, string> {
const style: Record<string, string> = {
function getBlurStyle({
intensity,
tint,
}: Required<Pick<BlurViewProps, 'intensity' | 'tint'>>): Record<string, string> {
const blur = `saturate(180%) blur(${Math.min(intensity, 100) * 0.2}px)`;
return {
backgroundColor: getBackgroundColor(Math.min(intensity, 100), tint),
backdropFilter: blur,
WebkitBackdropFilter: blur,
};

if (isBlurSupported()) {
const blur = `saturate(180%) blur(${Math.min(intensity, 100) * 0.2}px)`;
style.backdropFilter = blur;
// Safari support
style.WebkitBackdropFilter = blur;
}

return style;
}

export default BlurView;
11 changes: 0 additions & 11 deletions packages/expo-blur/src/__tests__/BlurView-test.web.tsx
Expand Up @@ -32,17 +32,6 @@ it(`prefers filters to background color`, () => {
expect(view.style['backdropFilter']).toBeDefined();
});

it(`uses a transparent background color when filters aren't supported`, () => {
// @ts-ignore
global.CSS = undefined;

const withoutNativeBlur = render(<BlurView tint="light" testID="blur" />);
const view = getByTestId(withoutNativeBlur.container, 'blur');

expect(view.style['backdropFilter']).toBeUndefined();
expect(view.style['backgroundColor']).toBeDefined();
});

it(`supports Animated API`, () => {
// react-native-web 0.11 doesn't support createAnimatedComponent with functional components.
// This test ensures that the current version of RNW in Expo works with expo-blur.
Expand Down

0 comments on commit 4acfdaa

Please sign in to comment.