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; }