hooks#useIsMounted TypeScript Examples

The following examples show how to use hooks#useIsMounted. 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: index.tsx    From exevo-pan with The Unlicense 5 votes vote down vote up
AuctionsProvider = ({
  initialPage,
  initialPageData,
  children,
}: AuctionsProviderProps) => {
  const [state, dispatch] = useReducer(AuctionsReducer, {
    loading: DEFAULT_STATE.loading,
    nickname: DEFAULT_STATE.nickname,
    page: initialPage,
    pageData: initialPageData,
  })

  const {
    nickname,
    pageData: { pageIndex },
  } = state

  const previousNickname = useRef(DEFAULT_STATE.nickname)
  const fetchData = useCallback(
    async (newPageIndex: number, newNickname: string) => {
      dispatch({ type: 'SET_LOADING' })

      const nicknameChanged = previousNickname.current !== newNickname
      const data = await AuctionsClient.fetchAuctionPage({
        paginationOptions: {
          pageIndex: nicknameChanged ? 0 : newPageIndex,
          pageSize: PAGE_SIZE,
        },
        filterOptions: {
          ...DEFAULT_FILTER_OPTIONS,
          nicknameFilter: newNickname,
        },
        endpoint: endpoints.CURRENT_AUCTIONS,
      })

      previousNickname.current = newNickname
      dispatch({ type: 'STORE_DATA', data })
    },
    [],
  )

  const isMounted = useIsMounted()
  useEffect(() => {
    if (isMounted) {
      fetchData(pageIndex, nickname)
    }
  }, [pageIndex, nickname, fetchData])

  const handlePaginatorFetch = useCallback((newPageIndex: number) => {
    dispatch({ type: 'SET_PAGE_INDEX', value: newPageIndex - 1 })
  }, [])

  const handleNicknameFetch = useCallback((newNickname: string) => {
    dispatch({ type: 'SET_NICKNAME', value: newNickname })
  }, [])

  return (
    <AuctionsContext.Provider
      value={{ ...state, handlePaginatorFetch, handleNicknameFetch }}
    >
      {children}
    </AuctionsContext.Provider>
  )
}
Example #2
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
Drawer = ({
  isOpen,
  onClose,
  children,
  className,
  ...props
}: DrawerProps) => {
  const initialDrag = useRef<number | null>(null)
  const [drawerOffset, setDrawerOffset] = useState<number>(0)
  const [shouldBeRendered, setShouldBeRendered] = useState<boolean>(isOpen)

  useLockBody(isOpen)

  const { elementToFocusRef, onKeyDown } = useEscToClose({
    open: isOpen,
    onClose,
  })

  const { binders, isMousePressed, position } = useDrag()

  useEffect(() => {
    if (!initialDrag.current && isMousePressed) {
      initialDrag.current = position.x
    } else if (initialDrag.current && !isMousePressed) {
      initialDrag.current = null
      onClose()
      setTimeout(() => setDrawerOffset(0), 200)
    }
  }, [isMousePressed, onClose, position.x])

  useEffect(() => {
    if (initialDrag.current) {
      const offset = position.x - initialDrag.current
      if (offset < 0) {
        setDrawerOffset(position.x - initialDrag.current)
      } else {
        setDrawerOffset(0)
      }
    }
  }, [position.x, initialDrag])

  const isMounted = useIsMounted()

  useEffect(() => {
    if (!isOpen) {
      setTimeout(() => setShouldBeRendered(false), 200)
    } else {
      setShouldBeRendered(true)
    }
  }, [isOpen])

  return isMounted && shouldBeRendered
    ? createPortal(
        <FocusLock>
          <div
            tabIndex={0}
            aria-hidden={!isOpen}
            aria-modal="true"
            role="dialog"
            ref={elementToFocusRef}
            onKeyDown={onKeyDown}
            className={clsx(
              'animate-slideIn z-75 bg-surface fixed top-0 left-0 flex h-screen w-[90vw] max-w-[600px] flex-col shadow-lg outline-none',
              !isOpen && 'invisible opacity-0',
              className,
            )}
            style={{
              marginLeft: `${drawerOffset}px`,
              transform: `translateX(${isOpen ? '0' : '-100%'})`,
              transition: '0.2s ease-out',
              transitionProperty: 'opacity, transform, visibility',
            }}
            {...props}
          >
            {children}
          </div>

          <div
            className={clsx(
              'z-74 animate-fadeIn bg-backdrop fixed top-0 left-0 h-screen w-screen transition-all',
              !isOpen && 'pointer-events-none opacity-0',
            )}
            style={{ cursor: isMousePressed ? 'grabbing' : 'unset' }}
            aria-hidden={!isOpen}
            {...binders}
          />
        </FocusLock>,
        document.body,
      )
    : null
}
Example #3
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
RangeSliderInput = ({
  className,
  min,
  max,
  onChange,
  value: propValue = [min, max],
  ...props
}: RangeSliderInputProps) => {
  const {
    translations: { common },
  } = useTranslations()

  const [cursorAValue, setCursorAValue] = useState(propValue[0])
  const [cursorBValue, setCursorBValue] = useState(propValue[1])

  const [currentCursor, setCurrentCursor] = useState<'A' | 'B'>('A')
  const [currentTrackCursor, setCurrentTrackCursor] = useState<
    'A' | 'B' | null
  >(null)

  const isMounted = useIsMounted()
  const track = useDrag()

  const trackRef = useRef<HTMLDivElement>(null)
  const trackWidth: number = trackRef.current?.offsetWidth ?? 1

  const positionToValue = useCallback(
    (position: number): number =>
      Math.round((max - min) * (position / trackWidth) + min),
    [min, max, trackWidth],
  )

  useEffect(() => {
    if (track.isMousePressed) {
      const { x } = track.position
      const trackValue = clampValue(positionToValue(x), [min, max])

      const newCurrentCursor =
        Math.abs(trackValue - cursorAValue) <=
        Math.abs(trackValue - cursorBValue)
          ? 'A'
          : 'B'

      setCurrentCursor(newCurrentCursor)

      if (currentTrackCursor) {
        const updateCurrentCursorValue = {
          A: setCursorAValue,
          B: setCursorBValue,
        }[currentTrackCursor]
        updateCurrentCursorValue(trackValue)
      } else {
        const updateCurrentCursorValue = {
          A: setCursorAValue,
          B: setCursorBValue,
        }[newCurrentCursor]
        setCurrentTrackCursor(newCurrentCursor)
        updateCurrentCursorValue(trackValue)
      }
    } else {
      setCurrentTrackCursor(null)
    }
  }, [
    track.position.x,
    track.isMousePressed,
    cursorAValue,
    cursorBValue,
    max,
    min,
    positionToValue,
    currentTrackCursor,
  ])

  const handleKeyPress = (event: React.KeyboardEvent) => {
    const { ctrlKey, shiftKey } = event
    const increment = 1 * (+!ctrlKey || 10) * (+!shiftKey || 100)
    const action = {
      ArrowUp: (value: number) => value + increment,
      ArrowRight: (value: number) => value + increment,
      ArrowDown: (value: number) => value - increment,
      ArrowLeft: (value: number) => value - increment,
    }[event.code]

    if (!action) return

    event.nativeEvent.preventDefault()

    if (currentCursor === 'A') {
      setCursorAValue((prev) => clampValue(action(prev), [min, max]))
    } else {
      setCursorBValue((prev) => clampValue(action(prev), [min, max]))
    }
  }

  const dispatchOnChange = useMemo(
    () =>
      debounce((aValue, bValue) => {
        onChange?.([aValue, bValue].sort((a, b) => a - b) as [number, number])
      }, 500),
    [onChange],
  )

  useEffect(() => {
    if (isMounted) dispatchOnChange(cursorAValue, cursorBValue)
  }, [dispatchOnChange, cursorAValue, cursorBValue])

  useLayoutEffect(() => {
    const [newMin, newMax] = propValue
    setCursorAValue(newMin)
    setCursorBValue(newMax)
  }, [propValue])

  const trackFillLeft =
    normalize(Math.min(cursorAValue, cursorBValue), [min, max]) * 100
  const trackFillRight =
    normalize(Math.max(cursorAValue, cursorBValue), [min, max]) * 100

  const cursorAPosition =
    normalize(clampValue(cursorAValue, [min, max]), [min, max]) * 100
  const cursorBPosition =
    normalize(clampValue(cursorBValue, [min, max]), [min, max]) * 100

  return (
    <div
      className={clsx('flex w-[270px] items-center gap-3', className)}
      {...props}
    >
      <ValueDisplay>
        {clampValue(Math.min(cursorAValue, cursorBValue), [min, max])}
      </ValueDisplay>
      <div className="w-full">
        <TrackFill
          ref={trackRef}
          tabIndex={0}
          onKeyDown={(event) => handleKeyPress(event)}
          isMousePressed={track.isMousePressed}
          {...track.binders}
        >
          <Cursor
            role="slider"
            aria-label={common.ChangeValueLabel}
            aria-valuenow={cursorAValue}
            aria-valuemax={max}
            aria-valuemin={min}
            style={{ left: `${cursorAPosition}%` }}
          />
          <Cursor
            role="slider"
            aria-label={common.ChangeValueLabel}
            aria-valuenow={cursorBValue}
            aria-valuemax={max}
            aria-valuemin={min}
            style={{ left: `${cursorBPosition}%` }}
          />

          <div
            className="bg-primary absolute top-0 h-full opacity-70"
            style={{
              left: `${trackFillLeft}%`,
              width: `${trackFillRight - trackFillLeft}%`,
            }}
          />
        </TrackFill>
      </div>
      <ValueDisplay>
        {clampValue(Math.max(cursorAValue, cursorBValue), [min, max])}
      </ValueDisplay>
    </div>
  )
}
Example #4
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
SliderInput = ({
  className,
  style,
  min,
  max,
  onChange,
  value: propValue = min,
  ...props
}: SliderInputProps) => {
  const {
    translations: { common },
  } = useTranslations()

  const [value, setValue] = useState<number>(propValue)
  const [sliderInputValue, setSliderInputValue] = useState<string>(
    propValue.toString(),
  )

  const inputRef = useRef<HTMLInputElement>(null)
  const trackRef = useRef<HTMLDivElement>(null)
  const trackWidth: number = trackRef.current?.offsetWidth ?? 1

  const positionToValue = useCallback(
    (position: number): number =>
      Math.round((max - min) * (position / trackWidth) + min),
    [min, max, trackWidth],
  )

  const valueToTrackPercentage = (currentValue: number): string =>
    `${clampValue(normalize(currentValue, [min, max]), [0, 1]) * 100}%`

  const isMounted = useIsMounted()
  const { binders, isMousePressed, position } = useDrag()

  const cursorPosition = clampValue(position.x, [0, trackWidth])
  const intSliderInputValue = strToInt(sliderInputValue)
  const isValid =
    sliderInputValue === '-' ||
    sliderInputValue === clampValue(intSliderInputValue, [min, max]).toString()

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const matches = event.target.value.match(/^-?[0-9]*/) ?? ['']
    const sanitized = matches[0]
    const newValue = strToInt(sanitized)

    if (Number.isNaN(newValue) || newValue < min) {
      setSliderInputValue(sanitized)
    } else {
      const boundedValue = clampValue(newValue, [min, max])
      setValue(boundedValue)
      setSliderInputValue(boundedValue.toString())
    }
  }

  const handleInputBlur = (event: React.FocusEvent) => {
    if (!isValid || (event.target as HTMLInputElement).value === '-') {
      setSliderInputValue(min.toString())
      setValue(min)
    }
  }

  const handleInputKeyPress = (event: React.KeyboardEvent) => {
    const { ctrlKey, shiftKey } = event
    const increment = 1 * (+!ctrlKey || 10) * (+!shiftKey || 100)
    const action = {
      ArrowUp: (prev: number) => prev + increment,
      ArrowDown: (prev: number) => prev - increment,
    }[event.code]

    if (!action) return

    event.nativeEvent.preventDefault()
    setValue((prev) => clampValue(action(prev), [min, max]))
  }

  const handleTrackKeyPress = (event: React.KeyboardEvent) => {
    const { ctrlKey, shiftKey } = event
    const increment = 1 * (+!ctrlKey || 10) * (+!shiftKey || 100)
    const action = {
      ArrowUp: (prev: number) => prev + increment,
      ArrowRight: (prev: number) => prev + increment,
      ArrowDown: (prev: number) => prev - increment,
      ArrowLeft: (prev: number) => prev - increment,
    }[event.code]

    if (!action) return

    event.nativeEvent.preventDefault()
    setValue((prev) => clampValue(action(prev), [min, max]))
  }

  useEffect(() => {
    if (isMousePressed) {
      const newValue = positionToValue(cursorPosition)
      setValue(newValue)
    }
  }, [isMousePressed, positionToValue, cursorPosition])

  const dispatchSyntheticEvent = useMemo(
    () =>
      debounce(() => {
        const event = new Event('input', { bubbles: true })
        inputRef.current?.dispatchEvent(event)
      }, 250),
    [],
  )

  useEffect(() => {
    setSliderInputValue(value.toString())
    if (isMounted) dispatchSyntheticEvent()
  }, [value, dispatchSyntheticEvent])

  useLayoutEffect(() => {
    setValue(propValue)
  }, [propValue])

  return (
    <div
      className={clsx('flex w-[270px] items-center gap-3 pl-2', className)}
      style={style}
    >
      <div style={{ width: '100%' }}>
        <TrackFill
          ref={trackRef}
          isMousePressed={isMousePressed}
          tabIndex={0}
          onKeyDown={handleTrackKeyPress}
          {...binders}
        >
          <Cursor
            role="slider"
            aria-label={common.ChangeValueLabel}
            aria-valuenow={value}
            aria-valuemax={max}
            aria-valuemin={min}
            style={{ left: valueToTrackPercentage(value) }}
          />
          <div
            className="bg-primary after:bg-primary pointer-events-none absolute top-0 left-0 h-full after:pointer-events-none after:absolute after:right-full after:top-0 after:h-full after:w-[7px]"
            style={{ width: valueToTrackPercentage(value) }}
          />
        </TrackFill>
      </div>
      <input
        aria-label={props['aria-label']}
        aria-labelledby={props['aria-labelledby']}
        aria-invalid={!isValid}
        value={sliderInputValue}
        onChange={handleInputChange}
        onBlur={handleInputBlur}
        onKeyDown={handleInputKeyPress}
        className={clsx(
          'text-tsm selection:bg-primary selection:text-onPrimary w-10 shrink-0 rounded-lg border-none py-[7px] text-center outline-none transition-colors',
          isValid
            ? 'bg-primaryVariant text-onSurface'
            : 'bg-red text-onPrimary selection',
        )}
      />
      <input
        hidden
        aria-invalid={!isValid}
        value={value}
        onInput={(event) =>
          onChange?.(event as React.ChangeEvent<HTMLInputElement>)
        }
        ref={inputRef}
        {...props}
      />
    </div>
  )
}
Example #5
Source File: index.tsx    From exevo-pan with The Unlicense 4 votes vote down vote up
AuctionsProvider = ({
  endpoint,
  highlightedAuctions,
  initialPage,
  initialPageData,
  defaultSortingMode,
  defaultDescendingOrder,
  children,
}: AuctionsProviderProps) => {
  const {
    translations: { common },
  } = useTranslations()

  const {
    current: { isCurrentlyDefaultValues, getUrlValues, setUrlValues },
  } = useRef(
    urlParametersState(buildSchema(defaultSortingMode, defaultDescendingOrder)),
  )
  const initialUrlState = useRef(getUrlValues())

  const [state, dispatch] = useReducer(AuctionsReducer, {
    loading: false,
    page: initialPage,
    pageData: {
      ...initialPageData,
      pageIndex: initialUrlState.current.currentPage - 1,
    },
    sortingMode: initialUrlState.current.orderBy,
    descendingOrder: initialUrlState.current.descending,
    shouldDisplayHighlightedAuctions:
      DEFAULT_STATE.shouldDisplayHighlightedAuctions,
  })

  const {
    pageData: { pageIndex },
    sortingMode,
    descendingOrder,
  } = state

  const { filterState, activeFilterCount } = useFilters()
  const lastFilterState = useRef(filterState)

  const fetchData = useCallback(
    async (
      newPageIndex: number,
      newSortingMode: number,
      newDescendingOrder: boolean,
      filterOptions: FilterOptions,
      newFilterCount: number,
    ) => {
      dispatch({ type: 'SET_LOADING' })

      lastFilterState.current = filterOptions
      const paginationOptions = {
        pageIndex: newPageIndex,
        pageSize: PAGE_SIZE,
      }
      const sortOptions = {
        sortingMode: newSortingMode,
        descendingOrder: newDescendingOrder,
      }

      const data = await AuctionsClient.fetchAuctionPage({
        paginationOptions,
        sortOptions,
        filterOptions,
        endpoint,
      })

      const isDefaultGridState =
        newPageIndex === 0 &&
        newSortingMode === defaultSortingMode &&
        newDescendingOrder === defaultDescendingOrder
      const noFilterApplied = newFilterCount === 0

      dispatch({
        type: 'STORE_DATA',
        data,
        shouldDisplayHighlightedAuctions: isDefaultGridState && noFilterApplied,
      })
    },
    [endpoint],
  )

  const isMounted = useIsMounted()
  useEffect(() => {
    if (isMounted) {
      const filterChanged = !dequal(filterState, lastFilterState.current)
      fetchData(
        filterChanged ? 0 : pageIndex,
        sortingMode,
        descendingOrder,
        filterState,
        activeFilterCount,
      )
    }
  }, [
    pageIndex,
    sortingMode,
    descendingOrder,
    filterState,
    activeFilterCount,
    fetchData,
  ])

  /* Detecting and fetching new data if there are url parameters */
  useEffect(() => {
    if (!isMounted) {
      if (!isCurrentlyDefaultValues() || activeFilterCount > 0) {
        const { currentPage, orderBy, descending } = initialUrlState.current
        fetchData(
          currentPage - 1,
          orderBy,
          descending,
          filterState,
          activeFilterCount,
        )
      }
    }
  }, [])

  useEffect(
    () =>
      setUrlValues({
        currentPage: pageIndex + 1,
        descending: descendingOrder,
        orderBy: sortingMode,
      }),
    [pageIndex, descendingOrder, sortingMode],
  )

  const handlePaginatorFetch = useCallback((newPageIndex: number) => {
    dispatch({ type: 'SET_PAGE_INDEX', value: newPageIndex - 1 })
  }, [])

  return (
    <AuctionsContext.Provider
      value={{
        ...state,
        highlightedAuctions,
        handlePaginatorFetch,
        dispatch,
      }}
    >
      {state.loading && <LoadingAlert>{common.LoadingState}</LoadingAlert>}
      {children}
    </AuctionsContext.Provider>
  )
}