Skip to content
This repository has been archived by the owner on Dec 5, 2024. It is now read-only.

Allow ref objects to be passed #308

Merged
merged 3 commits into from
Nov 2, 2019
Merged
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
22 changes: 11 additions & 11 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
{
"dist/index.umd.js": {
"bundled": 66802,
"minified": 23724,
"gzipped": 7169
"bundled": 67132,
"minified": 23802,
"gzipped": 7191
},
"dist/index.umd.min.js": {
"bundled": 31438,
"minified": 12846,
"gzipped": 4182
"bundled": 31768,
"minified": 12924,
"gzipped": 4205
},
"dist/index.esm.js": {
"bundled": 12271,
"minified": 7288,
"gzipped": 2066,
"bundled": 12577,
"minified": 7364,
"gzipped": 2096,
"treeshaked": {
"rollup": {
"code": 3754,
"code": 3832,
"import_statements": 137
},
"webpack": {
"code": 4860
"code": 4938
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@
"babel-jest": "^24.0.0",
"cross-env": "^5.1.4",
"emotion": "^9.1.1",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "^3.3.3",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.4.0",
"eslint": "^4.19.1",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-flowtype": "^2.46.1",
Expand Down
14 changes: 7 additions & 7 deletions src/Popper.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ import PopperJS, {
} from 'popper.js';
import type { Style } from 'typed-styles';
import { ManagerContext } from './Manager';
import { safeInvoke, unwrapArray, shallowEqual } from './utils';
import { unwrapArray, setRef, shallowEqual } from './utils';
import { type Ref } from "./RefTypes";

type getRefFn = (?HTMLElement) => void;
type ReferenceElement = ReferenceObject | HTMLElement | null;
type StyleOffsets = { top: number, left: number };
type StylePosition = { position: 'absolute' | 'fixed' };

export type PopperArrowProps = {
ref: getRefFn,
ref: Ref,
style: StyleOffsets & Style,
};
export type PopperChildrenProps = {|
ref: getRefFn,
ref: Ref,
style: StyleOffsets & StylePosition & Style,
placement: Placement,
outOfBoundaries: ?boolean,
Expand All @@ -33,7 +33,7 @@ export type PopperChildren = PopperChildrenProps => React.Node;
export type PopperProps = {
children: PopperChildren,
eventsEnabled?: boolean,
innerRef?: getRefFn,
innerRef?: Ref,
modifiers?: Modifiers,
placement?: Placement,
positionFixed?: boolean,
Expand Down Expand Up @@ -76,7 +76,7 @@ export class InnerPopper extends React.Component<PopperProps, PopperState> {
setPopperNode = (popperNode: ?HTMLElement) => {
if (!popperNode || this.popperNode === popperNode) return;

safeInvoke(this.props.innerRef, popperNode);
setRef(this.props.innerRef, popperNode);
this.popperNode = popperNode;

this.updatePopperInstance();
Expand Down Expand Up @@ -200,7 +200,7 @@ export class InnerPopper extends React.Component<PopperProps, PopperState> {
}

componentWillUnmount() {
safeInvoke(this.props.innerRef, null);
setRef(this.props.innerRef, null)
this.destroyPopperInstance();
}

Expand Down
34 changes: 34 additions & 0 deletions src/Popper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,40 @@ describe('Popper component', () => {
).not.toThrow();
});

it('accepts a ref function', () => {
const myRef = jest.fn();
const referenceElement = document.createElement('div');
mount(
<InnerPopper referenceElement={referenceElement} innerRef={myRef}>
{({ ref, style, placement}) => (
<div
ref={ref}
style={style}
data-placement={placement}
/>
)}
</InnerPopper>
)
expect(myRef).toBeCalled();
});

it('accepts a ref object', () => {
const myRef = (React: any).createRef();
const referenceElement = document.createElement('div');
mount(
<InnerPopper referenceElement={referenceElement} innerRef={myRef}>
{({ ref, style, placement}) => (
<div
ref={ref}
style={style}
data-placement={placement}
/>
)}
</InnerPopper>
)
expect(myRef.current).toBeDefined();
});

it('accepts a `referenceElement` property', () => {
class VirtualReference {
getBoundingClientRect() {
Expand Down
4 changes: 4 additions & 0 deletions src/RefTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type RefHandler = (?HTMLElement) => void;
type RefObject = { current?: HTMLElement};

export type Ref = RefHandler | RefObject;
Copy link
Member

Choose a reason for hiding this comment

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

Why can't we use React$Ref<HTMLElement> here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

React$Ref<HTMLElement> includes string refs which are legacy and react-popper does not support.

11 changes: 6 additions & 5 deletions src/Reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
import * as React from 'react';
import warning from 'warning';
import { ManagerContext } from './Manager';
import { safeInvoke, unwrapArray } from './utils';
import { safeInvoke, unwrapArray, setRef } from './utils';
import { type Ref } from "./RefTypes";

export type ReferenceChildrenProps = { ref: (?HTMLElement) => void };
export type ReferenceChildrenProps = { ref: Ref };
export type ReferenceProps = {
children: ReferenceChildrenProps => React.Node,
innerRef?: (?HTMLElement) => void,
innerRef?: Ref,
};

type InnerReferenceProps = {
Expand All @@ -18,12 +19,12 @@ class InnerReference extends React.Component<
ReferenceProps & InnerReferenceProps
> {
refHandler = (node: ?HTMLElement) => {
safeInvoke(this.props.innerRef, node);
setRef(this.props.innerRef, node)
safeInvoke(this.props.setReferenceNode, node);
};

componentWillUnmount() {
safeInvoke(this.props.innerRef, null);
setRef(this.props.innerRef, null)
}

render() {
Expand Down
16 changes: 16 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @flow

import { type Ref } from "./RefTypes";

/**
* Takes an argument and if it's an array, returns the first item in the array,
* otherwise returns the argument. Used for Preact compatibility.
Expand Down Expand Up @@ -38,3 +40,17 @@ export const shallowEqual = (objA: { [key: string]: any}, objB: { [key: string]:

return true;
}

/**
* Sets a ref using either a ref callback or a ref object
*/
export const setRef = (ref: ?Ref, node: ?HTMLElement) => {
// if its a function call it
if (typeof ref === "function") {
return safeInvoke(ref, node);
}
// otherwise we should treat it as a ref object
else if (ref != null) {
ref.current = node;
}
}
15 changes: 8 additions & 7 deletions typings/react-popper.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,37 @@ interface ManagerProps {
}
export class Manager extends React.Component<ManagerProps, {}> { }

type RefHandler = (ref: HTMLElement | SVGElement | null) => void;

interface ReferenceChildrenProps {
ref: RefHandler;
// React refs are supposed to be contravariant (allows a more general type to be passed rather than a more specific one)
// However, Typescript currently can't infer that fact for refs
// See https://github.com/microsoft/TypeScript/issues/30748 for more information
ref: React.Ref<any>;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is very unfortunate, but this issue with contravariance vs covariance with refs is a very long standing issue with TS that doesn't look like it will be resolved soon.

}

interface ReferenceProps {
children: (props: ReferenceChildrenProps) => React.ReactNode;
innerRef?: RefHandler;
innerRef?: React.Ref<any>;
}
export class Reference extends React.Component<ReferenceProps, {}> { }

export interface PopperArrowProps {
ref: RefHandler;
ref: React.Ref<any>;
style: React.CSSProperties;
}

export interface PopperChildrenProps {
arrowProps: PopperArrowProps;
outOfBoundaries: boolean | null;
placement: PopperJS.Placement;
ref: RefHandler;
ref: React.Ref<any>;
scheduleUpdate: () => void;
style: React.CSSProperties;
}

export interface PopperProps {
children: (props: PopperChildrenProps) => React.ReactNode;
eventsEnabled?: boolean;
innerRef?: RefHandler;
innerRef?: React.Ref<any>;
modifiers?: PopperJS.Modifiers;
placement?: PopperJS.Placement;
positionFixed?: boolean;
Expand Down
Loading