@chakra-ui/react#usePrevious TypeScript Examples

The following examples show how to use @chakra-ui/react#usePrevious. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AsyncDualSelectWidget.tsx    From ke with MIT License 4 votes vote down vote up
AsyncDualSelectWidget = (props: DualSelectWidgetProps): JSX.Element => {
  const {
    name,
    style,
    helpText,
    notifier,
    provider,
    dataResourceUrl,
    selectedTitle = 'Selected Items',
    availableTitle = 'Available Items',
    selectButtonTitle = 'SELECT',
    unselectButtonTitle = 'UNSELECT',
    getOptionLabel,
    getOptionValue,
    containerStore,
    targetPayload,
    submitChange,
    copyValue,
    useClipboard,
  } = props

  const context = containerStore.getState()

  const { targetUrl, content, isRequired, widgetDescription } = useWidgetInitialization({
    ...props,
    context,
  })

  const handleChange = useCallback(
    (values: object[]): void => {
      const inputPayload = getPayload(values, name, targetPayload)
      submitChange({ url: targetUrl, payload: inputPayload })
    },
    [name, submitChange, targetPayload, targetUrl]
  )

  const [nextUrl, setNextUrl] = useState<string | null | undefined>('')
  const [options, setOptions] = useState<object[]>([])

  const getOptionsHandler = useCallback(
    async (url: string, searchQueryValue = ''): Promise<LoadOptionsType> => {
      const res = await provider
        .getPage(url, [
          {
            filterName: 'search',
            value: searchQueryValue,
          },
        ])
        .then(([data, , meta]: [object, object, Pagination]) => {
          const hasMore = !!meta.nextUrl
          setNextUrl(hasMore ? meta.nextUrl : '')
          return {
            options: data,
            hasMore,
          }
        })
      return res
    },
    [provider]
  )

  const loadOptions = useCallback(
    async ({ first = false, searchQueryValue = '' }): Promise<AsyncResult<LoadOptionsType>> => {
      let url = dataResourceUrl
      if (!first && !!nextUrl) {
        url = nextUrl
      }

      if (first || nextUrl) {
        const res = await getOptionsHandler(url, searchQueryValue)
        return res as AsyncResult<LoadOptionsType>
      }

      return Promise.resolve({
        options: [],
        hasMore: false,
      })
    },
    [dataResourceUrl, getOptionsHandler, nextUrl]
  )

  const [availableSelected, setAvailableSelected] = useState<string[]>([])
  const [selectedSelected, setSelectedSelected] = useState<string[]>([])

  const [selectedItems, setSelectedItems] = useState<string[] | null>(null)
  const selectedOptions =
    (selectedItems !== null && options.filter((o) => selectedItems.includes(getOptionValue(o)))) || []
  const availableOptions =
    (selectedItems !== null && options.filter((o) => !selectedItems.includes(getOptionValue(o)))) || []

  const isDisableSelect = availableSelected.length === 0
  const isDisableUnselect = selectedSelected.length === 0

  useEffect(() => {
    setSelectedItems([])
    loadOptions({ first: true }).then((res) => {
      setOptions(res.options)
    })
  }, [loadOptions])

  const allDeselect = useCallback(() => {
    setSelectedSelected([])
    setAvailableSelected([])
  }, [])

  const onChange = useCallback(() => {
    handleChange((selectedItems !== null && options.filter((o) => selectedItems.includes(getOptionValue(o)))) || [])
    allDeselect()
  }, [allDeselect, getOptionValue, handleChange, options, selectedItems])

  const selectButtonHandler = useCallback(() => {
    setSelectedItems(availableSelected.concat((selectedItems !== null && selectedItems) || []))
  }, [availableSelected, selectedItems])

  const unselectButtonHandler = useCallback(() => {
    setSelectedItems((selectedItems !== null && selectedItems.filter((si) => !selectedSelected.includes(si))) || [])
  }, [selectedItems, selectedSelected])

  const previousSelectedItems = usePrevious(selectedItems)
  useEffect(() => {
    if (
      selectedItems !== null &&
      previousSelectedItems !== null &&
      selectedItems.length !== previousSelectedItems.length
    ) {
      onChange()
    }
  }, [onChange, previousSelectedItems, selectedItems])

  const handleCopyValue = getCopyHandler(content, copyValue)

  const [searchActive, setSearchActive] = useState(false)
  const [searchValue, setSearchValue] = useState<string>('')
  const handleSearchToggle = useCallback(() => {
    if (searchActive) {
      setSearchValue('')
      loadOptions({ first: true }).then((res) => {
        setOptions(res.options)
      })
    }
    setSearchActive(!searchActive)
  }, [loadOptions, searchActive])

  return (
    <WidgetWrapper
      name={name}
      style={style}
      helpText={helpText || 'Items'}
      description={widgetDescription}
      required={isRequired}
      notifier={notifier}
      useClipboard={useClipboard}
      copyValue={handleCopyValue}
    >
      <Flex>
        <SideContainer data-testid="ds-left-list">
          <SideContainerTitle fontSize="md">
            {searchActive ? (
              <DebounceInput
                value={searchValue}
                onChange={(newValue) => {
                  loadOptions({ first: true, searchQueryValue: newValue }).then((res) => {
                    setOptions(res.options)
                  })
                  setSearchValue(newValue)
                }}
                style={{ paddingLeft: 5 }}
                borderWidth="1px"
                borderColor="gray.300"
                height="20px"
                debounceTimeout={700}
              />
            ) : (
              availableTitle
            )}
            {searchActive ? (
              <StyledCloseIcon onClick={handleSearchToggle} />
            ) : (
              <StyledSearchIcon
                style={{ ...(searchActive ? { color: 'dodgerblue' } : undefined) }}
                onClick={handleSearchToggle}
              />
            )}
          </SideContainerTitle>
          <SelectList
            values={availableOptions}
            selectedValues={availableSelected}
            disabledValues={[]}
            handleChange={(items) => {
              setAvailableSelected(items)
            }}
            handleScrollBottom={() => {
              loadOptions({}).then((res) => {
                setOptions(options.concat(res.options))
              })
            }}
            getOptionValue={getOptionValue}
            getOptionLabel={getOptionLabel}
          />
        </SideContainer>
        <CenterContainer>
          <SelectButton
            isDisabled={isDisableSelect}
            rightIcon={<ArrowRight />}
            variant="outline"
            onClick={selectButtonHandler}
          >
            {selectButtonTitle}
          </SelectButton>
          <SelectButton
            isDisabled={isDisableUnselect}
            leftIcon={<ArrowLeft />}
            variant="outline"
            onClick={unselectButtonHandler}
          >
            {unselectButtonTitle}
          </SelectButton>
        </CenterContainer>
        <SideContainer data-testid="ds-right-list">
          <SideContainerTitle fontSize="md">{selectedTitle}</SideContainerTitle>
          <SelectList
            values={selectedOptions}
            selectedValues={selectedSelected}
            handleChange={(items) => {
              setSelectedSelected(items)
            }}
            getOptionValue={getOptionValue}
            getOptionLabel={getOptionLabel}
          />
        </SideContainer>
      </Flex>
    </WidgetWrapper>
  )
}
Example #2
Source File: MapList.tsx    From ke with MIT License 4 votes vote down vote up
MapList = <T extends MapListItem>({
  containerStyle,
  items,
  clusters,
  initialCenter,
  initialZoom,
  initialOpenedKey,
  getKey,
  searchMarkerRadius = 2000,
  onViewChange,
  onLoad,
}: MapListProps<T>): ReactElement => {
  const [center, setCenter] = useState(initialCenter || items[0]?.position)
  const [zoom, setZoom] = useState(initialZoom)
  const [bounds, setBounds] = useState<LatLngBounds>()
  const [activeKey, setActiveKey] = useState<string | undefined>(initialOpenedKey)

  const prevCenter = usePrevious(initialCenter)

  useEffect(() => {
    if (prevCenter !== initialCenter) {
      setCenter(initialCenter)
    }
  }, [initialCenter, prevCenter])

  const prevValue = usePrevious(initialOpenedKey)

  useEffect(() => {
    if (prevValue !== initialOpenedKey) {
      setActiveKey(initialOpenedKey)
    }
  }, [initialOpenedKey, prevValue])

  const handleItemClick = (item: T): void => setActiveKey(getKey(item))
  const handleInfoClose = (): void => setActiveKey(undefined)

  const handleClusterClick = ({ bounds: clusterBounds, center: clusterCenter }: MapListCluster): void => {
    setZoom((prev) => {
      if (!clusterBounds) {
        return (prev || 1) + 2
      }
      const { south, east, west, north } = clusterBounds
      const calcZoom = getBoundsZoomLevel(
        { lat: north, lng: east },
        { lat: south, lng: west },
        { height: 400, width: 1000 }
      )
      return Math.max((prev || 0) + 1, calcZoom)
    })
    setCenter(clusterCenter)
  }

  const controls = useMemo(
    () => ({
      search: {
        marker: makePartial(SearchMarker, { radius: searchMarkerRadius }),
      },
    }),
    [searchMarkerRadius]
  )

  const handleZoomChange = useCallback(
    (z: number | undefined) => {
      setZoom(z)
      onViewChange?.({ zoom: z, bounds })
    },
    [bounds, onViewChange]
  )

  const handleBoundsChange = useCallback(
    (b: LatLngBounds | undefined) => {
      setBounds(b)
      onViewChange?.({ zoom, bounds: b })
    },
    [zoom, onViewChange]
  )

  return (
    <Map
      containerStyle={containerStyle}
      center={center}
      onCenterChange={setCenter}
      zoom={zoom}
      onZoomChange={handleZoomChange}
      controls={controls}
      onBoundsChange={handleBoundsChange}
      onLoad={onLoad}
    >
      {items.map((i) => (
        /*
         * TODO: Имеет смысл отказаться от makeUniqPosition для маркеров с одинаковыми координатами
         * гораздо приличнее выглядел бы маркер кластера с всплывающим блоком со списком вложенных item в
         * том или ином виде. Типа такого - https://yandex.ru/dev/maps/jsbox/2.1/clusterer_balloon_open/
         */
        <MapMarker
          key={getKey(i)}
          position={makeUniqPosition(i, items)}
          label={i.label}
          title={i.title}
          icon={i.icon}
          info={i.info}
          infoSize={i.infoSize}
          onClick={() => handleItemClick(i)}
          onInfoClose={handleInfoClose}
          isInfoOpened={activeKey !== undefined && getKey(i) === activeKey}
        />
      ))}
      {clusters?.map((c) => (
        <MapCluster
          key={`${c.center.lat}-${c.center.lng}`}
          center={c.center}
          label={c.label}
          onClick={() => handleClusterClick(c)}
        />
      ))}
    </Map>
  )
}