mirror of
https://github.com/zhigang1992/styled-components.git
synced 2026-01-12 17:52:45 +08:00
Implement shouldForwardProp for React Native/Primitives (#3107)
* implement shouldForwardProp for React Native and primitives #3093 * return true in native default prop validator function * Update CHANGELOG.md
This commit is contained in:
@@ -6,6 +6,8 @@ _The format is based on [Keep a Changelog](http://keepachangelog.com/) and this
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Implement `shouldForwardProp` API for native and primitive platforms (see [#3093](https://github.com/styled-components/styled-components/pull/3107))
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Added `useTheme` hook to named exports for react-primitives entrypoint (see [#2982](https://github.com/styled-components/styled-components/pull/2982)) thanks @jladuval!
|
||||
|
||||
@@ -16,6 +16,9 @@ import type { Attrs, RuleSet, Target } from '../types';
|
||||
// NOTE: no hooks available for react-native yet;
|
||||
// if the user makes use of ThemeProvider or StyleSheetManager things will break.
|
||||
|
||||
// Validator defaults to true if not in HTML/DOM env
|
||||
const validAttr = () => true;
|
||||
|
||||
class StyledNativeComponent extends Component<*, *> {
|
||||
root: ?Object;
|
||||
|
||||
@@ -35,7 +38,7 @@ class StyledNativeComponent extends Component<*, *> {
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
const { defaultProps, target } = forwardedComponent;
|
||||
const { defaultProps, target, shouldForwardProp } = forwardedComponent;
|
||||
const elementToBeRendered =
|
||||
this.attrs.$as || this.attrs.as || transientAsProp || renderAs || target;
|
||||
|
||||
@@ -44,15 +47,20 @@ class StyledNativeComponent extends Component<*, *> {
|
||||
this.props
|
||||
);
|
||||
|
||||
const isTargetTag = isTag(elementToBeRendered);
|
||||
const computedProps = this.attrs !== props ? { ...props, ...this.attrs } : props;
|
||||
const propFilterFn = shouldForwardProp || (isTargetTag && validAttr);
|
||||
const propsForElement = {};
|
||||
let key;
|
||||
|
||||
for (key in props) {
|
||||
if (key[0] !== '$') propsForElement[key] = props[key];
|
||||
}
|
||||
|
||||
for (key in this.attrs) {
|
||||
if (key[0] !== '$') propsForElement[key] = this.attrs[key];
|
||||
for (key in computedProps) {
|
||||
if (key[0] === '$' || key === 'as') continue;
|
||||
else if (key === 'forwardedAs') {
|
||||
propsForElement.as = props[key];
|
||||
} else if (!propFilterFn || propFilterFn(key, validAttr)) {
|
||||
// Don't pass through filtered tags through to native elements
|
||||
propsForElement[key] = computedProps[key];
|
||||
}
|
||||
}
|
||||
|
||||
propsForElement.style = [generatedStyles].concat(style);
|
||||
@@ -145,6 +153,22 @@ export default (InlineStyle: Function) => {
|
||||
? Array.prototype.concat(target.attrs, attrs).filter(Boolean)
|
||||
: attrs;
|
||||
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
let shouldForwardProp = options.shouldForwardProp;
|
||||
|
||||
// $FlowFixMe
|
||||
if (isTargetStyledComp && target.shouldForwardProp) {
|
||||
if (shouldForwardProp) {
|
||||
// compose nested shouldForwardProp calls
|
||||
shouldForwardProp = (prop, filterFn) =>
|
||||
// $FlowFixMe
|
||||
target.shouldForwardProp(prop, filterFn) && options.shouldForwardProp(prop, filterFn);
|
||||
} else {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
shouldForwardProp = target.shouldForwardProp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* forwardRef creates a new interim component, which we'll take advantage of
|
||||
* instead of extending ParentComponent to create _another_ interim class
|
||||
@@ -155,6 +179,9 @@ export default (InlineStyle: Function) => {
|
||||
|
||||
WrappedStyledNativeComponent.displayName = displayName;
|
||||
|
||||
// $FlowFixMe
|
||||
WrappedStyledNativeComponent.shouldForwardProp = shouldForwardProp;
|
||||
|
||||
// $FlowFixMe
|
||||
WrappedStyledNativeComponent.inlineStyle = new InlineStyle(
|
||||
// $FlowFixMe
|
||||
@@ -197,6 +224,7 @@ export default (InlineStyle: Function) => {
|
||||
// all SC-specific things should not be hoisted
|
||||
attrs: true,
|
||||
displayName: true,
|
||||
shouldForwardProp: true,
|
||||
inlineStyle: true,
|
||||
styledComponentId: true,
|
||||
target: true,
|
||||
|
||||
@@ -183,7 +183,7 @@ Object {
|
||||
});
|
||||
|
||||
it('passes simple props on', () => {
|
||||
const Comp = styled.View.attrs(p => ({
|
||||
const Comp = styled.View.attrs(() => ({
|
||||
test: true,
|
||||
}))``;
|
||||
|
||||
@@ -432,6 +432,92 @@ Object {
|
||||
expect(TestRenderer.create(<Comp $color="red" />).toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('allows for custom prop filtering for elements', () => {
|
||||
const Comp = styled('View').withConfig({
|
||||
shouldForwardProp: prop => !['filterThis'].includes(prop)
|
||||
})`
|
||||
color: red;
|
||||
`;
|
||||
const wrapper = TestRenderer.create(<Comp filterThis="abc" passThru="def" />);
|
||||
const { props } = wrapper.root.findByType('View');
|
||||
expect(props.style).toEqual([{ color: 'red' }]);
|
||||
expect(props.passThru).toBe('def');
|
||||
expect(props.filterThis).toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows custom prop filtering for components', () => {
|
||||
const InnerComp = props => <View {...props} />
|
||||
const Comp = styled(InnerComp).withConfig({
|
||||
shouldForwardProp: prop => !['filterThis'].includes(prop)
|
||||
})`
|
||||
color: red;
|
||||
`;
|
||||
const wrapper = TestRenderer.create(<Comp filterThis="abc" passThru="def" />);
|
||||
const { props } = wrapper.root.findByType('View');
|
||||
expect(props.style).toEqual([{ color: 'red' }]);
|
||||
expect(props.passThru).toBe('def');
|
||||
expect(props.filterThis).toBeUndefined();
|
||||
});
|
||||
|
||||
it('composes shouldForwardProp on composed styled components', () => {
|
||||
const StyledView = styled.View.withConfig({
|
||||
shouldForwardProp: prop => prop === 'passThru'
|
||||
})`
|
||||
color: red;
|
||||
`;
|
||||
const ComposedView = styled(StyledView).withConfig({
|
||||
shouldForwardProp: () => true
|
||||
})``;
|
||||
const wrapper = TestRenderer.create(<ComposedView filterThis passThru />);
|
||||
const { props } = wrapper.root.findByType('View');
|
||||
expect(props.passThru).toBeDefined();
|
||||
expect(props.filterThis).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should filter out props when using "as" to a custom component', () => {
|
||||
const AsComp = props => <View {...props} />
|
||||
const Comp = styled.View.withConfig({
|
||||
shouldForwardProp: prop => !['filterThis'].includes(prop)
|
||||
})`
|
||||
color: red;
|
||||
`;
|
||||
const wrapper = TestRenderer.create(<Comp as={AsComp} filterThis="abc" passThru="def" />);
|
||||
const { props } = wrapper.root.findByType(AsComp);
|
||||
|
||||
expect(props.style).toEqual([{ color: 'red' }]);
|
||||
expect(props.passThru).toBe('def');
|
||||
expect(props.filterThis).toBeUndefined();
|
||||
});
|
||||
|
||||
it('can set computed styles based on props that are being filtered out', () => {
|
||||
const AsComp = props => <View {...props} />
|
||||
const Comp = styled.View.withConfig({
|
||||
shouldForwardProp: prop => !['filterThis'].includes(prop)
|
||||
})`
|
||||
color: ${props => props.filterThis === 'abc' ? 'red' : undefined};
|
||||
`;
|
||||
const wrapper = TestRenderer.create(<Comp as={AsComp} filterThis="abc" passThru="def" />);
|
||||
const { props } = wrapper.root.findByType(AsComp);
|
||||
|
||||
expect(props.style).toEqual([{ color: 'red' }]);
|
||||
expect(props.passThru).toBe('def');
|
||||
expect(props.filterThis).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should filter our props when using "as" to a different element', () => {
|
||||
const Comp = styled.View.withConfig({
|
||||
shouldForwardProp: prop => !['filterThis'].includes(prop)
|
||||
})`
|
||||
color: red;
|
||||
`;
|
||||
const wrapper = TestRenderer.create(<Comp as="a" filterThis="abc" passThru="def" />);
|
||||
const { props } = wrapper.root.findByType("a");
|
||||
|
||||
expect(props.style).toEqual([{ color: 'red' }]);
|
||||
expect(props.passThru).toBe('def');
|
||||
expect(props.filterThis).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should prefer transient $as over as', () => {
|
||||
const OtherText = props => <Text {...props} foo />;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user