Source

src/contexts/Viewport.jsx

import React, { useState, useContext } from 'react'
import throttle from 'lodash.throttle'

const ViewportContext = React.createContext()
const SetViewportContext = React.createContext()

const updateSharedView = throttle((viewport) => {
    window.localStorage.setItem('SHARED_VIEW', JSON.stringify(viewport))
}, 25)

/**
 * Wrapper component for the viewport context.
 *
 * @category Contexts
 * @example
 *     function MyApp() {
 *         return (
 *             <ViewportProvider>
 *                 <MyChildComponent />
 *             </ViewportProvider>
 *         )
 *     }
 *
 * @param {Object} props
 * @param {React.ReactNode} props.children - Child components
 * @param {Viewport} props.defaultViewport - Initial viewport
 * @returns {React.Component} - ViewportContext.Provider
 */
export const ViewportProvider = ({ defaultViewport = {}, children }) => {
    const [viewport, setViewport] = useState(defaultViewport)
    document.hasFocus() && updateSharedView(viewport)

    return (
        <ViewportContext.Provider value={viewport}>
            <SetViewportContext.Provider value={setViewport}>
                {children}
            </SetViewportContext.Provider>
        </ViewportContext.Provider>
    )
}

/**
 * A hook that returns the current viewport. Separated from `useSetViewport` to
 * avoid unnecessary re-renders.
 *
 * @category Hooks
 */
export const useViewport = () => {
    const ctx = useContext(ViewportContext)
    if (!ctx) throw Error('Not wrapped in <ViewportProvider />.')
    return ctx
}

/**
 * A hook that returns the current viewport. Separated from `useViewport` to
 * avoid unnecessary re-renders.
 *
 * @category Contexts
 */
export const useSetViewport = () => {
    const ctx = useContext(SetViewportContext)
    if (!ctx) throw Error('Not wrapped in <ViewportProvider />.')
    return ctx
}

/**
 * @typedef {Object} Viewport A standard Mapbox/Deck/GoogleMaps viewport object.
 * @property {number} latitude - In WGS84
 * @property {number} longitude - In WGS84
 * @property {number} zoom - 0-22 (0 is the whole world)
 * @property {number} bearing - In degrees, 0 is north
 * @property {number} pitch - In degrees, 0 is straight down, 45 is max with
 *   Mapbox tiles
 */