import React = require('react');
import ReactDOM = require('react-dom');
import matchQueries from 'container-query-toolkit/lib/matchQueries';
import {Props, State, Params, Query, Size} from './interfaces';
import ContainerQueryCore from './ContainerQueryCore';
import isShallowEqual from './isShallowEqual';
/*
* const MyComponent = () => {
* const [params, containerRef] = useContainerQuery(query);
* return
the box
;
* };
*/
export const useContainerQuery = (query: Query, initialSize: Size) => {
// setup a ref callback
// (end user) attaches that ref callback to a container element
// @ts-ignore
const [params, setParams] = React.useState(() => {
if (!initialSize) {
return {};
}
return matchQueries(query)(initialSize);
});
// @ts-ignore
const [containerRef, setContainerRef] = React.useState(null);
// @ts-ignore
const refCallback = React.useCallback((node) => {
setContainerRef(node); // on unmount, node would be set to null triggering cleanup
}, [setContainerRef]);
// @ts-ignore
React.useEffect(() => {
if (containerRef) {
let cqCore: ContainerQueryCore | null = new ContainerQueryCore(
query,
(params) => {
setParams(params);
}
);
cqCore.observe(containerRef);
return () => {
cqCore!.disconnect();
cqCore = null;
};
}
}, [query, containerRef, setParams]);
return [params, refCallback];
};
/**
*
* {(params) => {
*
* }}
*
*/
export class ContainerQuery extends React.Component {
private cqCore: ContainerQueryCore | null = null;
constructor(props: Props) {
super(props);
this.state = {
params: props.initialSize
? matchQueries(props.query)(props.initialSize)
: {},
};
}
componentDidMount() {
this._startObserving(this.props.query);
}
UNSAFE_componentWillReceiveProps(nextProps: Props) {
// componentWillReceiveProps and componentDidMount can potentially run out of order,
// so we need to consider the case where cqCore is not initialized yet.
if (this.cqCore && !isQueriesEqual(this.props.query, nextProps.query)) {
this.cqCore.disconnect();
this.cqCore = null;
this._startObserving(nextProps.query);
}
}
componentDidUpdate() {
this.cqCore!.observe(ReactDOM.findDOMNode(this));
}
componentWillUnmount() {
this.cqCore!.disconnect();
this.cqCore = null;
}
render() {
return this.props.children(this.state.params);
}
_startObserving(query: Query) {
this.cqCore = new ContainerQueryCore(query, (params) => {
this.setState({ params });
});
this.cqCore.observe(ReactDOM.findDOMNode(this));
}
}
/**
* applyContainerQuery(BoxComponent, query, initialSize);
*/
export type Component = React.ComponentClass | React.StatelessComponent;
export interface QueryProps {
containerQuery: Params;
};
export function applyContainerQuery(
Component: Component,
query: Query,
initialSize?: Size
): React.ComponentClass {
return class ContainerQuery extends React.Component {
static displayName: string = Component.displayName
? `ContainerQuery(${Component.displayName})`
: 'ContainerQuery';
private cqCore: ContainerQueryCore | null = null;
constructor(props: T) {
super(props);
this.state = {
params: initialSize
? matchQueries(query)(initialSize)
: {},
};
}
componentDidMount() {
this.cqCore = new ContainerQueryCore(query, (params) => {
this.setState({params});
});
this.cqCore.observe(ReactDOM.findDOMNode(this));
}
componentDidUpdate() {
this.cqCore!.observe(ReactDOM.findDOMNode(this));
}
componentWillUnmount() {
this.cqCore!.disconnect();
this.cqCore = null;
}
render() {
return (
);
}
};
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
function isQueriesEqual(queryA: Query, queryB: Query): boolean {
const keysA = Object.keys(queryA);
const keysB = Object.keys(queryB);
if (keysA.length !== keysB.length) {
return false;
}
for (let i = 0; i < keysA.length; i++) {
if (!hasOwnProperty.call(queryB, keysA[i]) ||
!isShallowEqual(queryA[keysA[i]], queryB[keysA[i]])) {
return false;
}
}
return true;
}