UNPKG

2.68 kBPlain TextView Raw
1/*
2 * Copyright 2020 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13import {useCallback, useEffect, useRef, useState} from 'react';
14import {useLayoutEffect} from './useLayoutEffect';
15import {useSSRSafeId} from '@react-aria/ssr';
16import {useValueEffect} from './';
17
18let idsUpdaterMap: Map<string, (v: string) => void> = new Map();
19
20/**
21 * If a default is not provided, generate an id.
22 * @param defaultId - Default component id.
23 */
24export function useId(defaultId?: string): string {
25 let [value, setValue] = useState(defaultId);
26 let nextId = useRef(null);
27
28 let res = useSSRSafeId(value);
29
30 let updateValue = useCallback((val) => {
31 nextId.current = val;
32 }, []);
33
34 idsUpdaterMap.set(res, updateValue);
35
36 useLayoutEffect(() => {
37 let r = res;
38 return () => {
39 idsUpdaterMap.delete(r);
40 };
41 }, [res]);
42
43 // This cannot cause an infinite loop because the ref is updated first.
44 // eslint-disable-next-line
45 useEffect(() => {
46 let newId = nextId.current;
47 if (newId) {
48 nextId.current = null;
49 setValue(newId);
50 }
51 });
52
53 return res;
54}
55
56/**
57 * Merges two ids.
58 * Different ids will trigger a side-effect and re-render components hooked up with `useId`.
59 */
60export function mergeIds(idA: string, idB: string): string {
61 if (idA === idB) {
62 return idA;
63 }
64
65 let setIdA = idsUpdaterMap.get(idA);
66 if (setIdA) {
67 setIdA(idB);
68 return idB;
69 }
70
71 let setIdB = idsUpdaterMap.get(idB);
72 if (setIdB) {
73 setIdB(idA);
74 return idA;
75 }
76
77 return idB;
78}
79
80/**
81 * Used to generate an id, and after render, check if that id is rendered so we know
82 * if we can use it in places such as labelledby.
83 * @param depArray - When to recalculate if the id is in the DOM.
84 */
85export function useSlotId(depArray: ReadonlyArray<any> = []): string {
86 let id = useId();
87 let [resolvedId, setResolvedId] = useValueEffect(id);
88 let updateId = useCallback(() => {
89 setResolvedId(function *() {
90 yield id;
91
92 yield document.getElementById(id) ? id : null;
93 });
94 }, [id, setResolvedId]);
95
96 useLayoutEffect(updateId, [id, updateId, ...depArray]);
97
98 return resolvedId;
99}