Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/overridable.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,30 @@ export function parametrize(Component, extraProps) {
/**
* React component to enable overriding children when rendering.
*/
function Overridable({id, children, ...restProps}) {
const Overridable = React.forwardRef(({id, children, ...restProps}, ref) => {
const overriddenComponents = useContext(OverridableContext);
const child = children ? React.Children.only(children) : null;
const childProps = child ? child.props : {};

if (id in overriddenComponents) {
// If there's an override, we replace the component's content with the override + props
const Overridden = overriddenComponents[id];
const element = React.createElement(Overridden, {...childProps, ...restProps});
const props = {...childProps, ...restProps};
if (ref) {
props.ref = ref;
}
const element = React.createElement(Overridden, props);
return <DevModeWrapper id={id}>{element}</DevModeWrapper>;
} else if (child) {
// No override? Clone the Overridable component's original children
const element = React.cloneElement(child, childProps);
const element = ref ? React.cloneElement(child, {ref}) : React.cloneElement(child, childProps);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't childProps be included in the cloneElement as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see in the React Docs cloneElement preserves the element's existing props and merges the provided props, so passing {ref} will retain the childProps. And now that I think about it, it shouldn't be needed in the other cloneElement either 🤔

return <DevModeWrapper id={id}>{element}</DevModeWrapper>;
} else {
return null;
}
}
});

Overridable.displayName = 'Overridable';

Overridable.propTypes = {
/** The children of the component */
Expand Down
39 changes: 39 additions & 0 deletions src/overridable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,42 @@ describe('Tests for Overridable.component', () => {
expect(NewCmp.find('ul')).toHaveLength(0);
});
});

describe('Tests for ref forwarding', () => {
class RefChild extends Component {
render() {
return <div className="ref-child" />;
}
}

test('it should forward a ref to the cloned child', () => {
const ref = React.createRef();
mount(
<Overridable id="NotOverridden.container" ref={ref}>
<RefChild />
</Overridable>
);
expect(ref.current).toBeInstanceOf(RefChild);
});

test('it should forward a ref to the overridden component', () => {
const ref = React.createRef();
mount(
<OverridableContext.Provider value={{'Overridden.container': RefChild}}>
<Overridable id="Overridden.container" ref={ref}>
<div />
</Overridable>
</OverridableContext.Provider>
);
expect(ref.current).toBeInstanceOf(RefChild);
});

test('it should render normally when no ref is given', () => {
const mounted = mount(
<Overridable id="NotOverridden.container">
<RefChild />
</Overridable>
);
expect(mounted.find('.ref-child')).toHaveLength(1);
});
});