hooks.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. * Copyright 2021-2024 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import { Dispatch, RefObject, useContext, useEffect, useMemo, useRef, useState } from "react";
  14. import { useMediaQuery, useTheme } from "@mui/material";
  15. import { getUpdateVars } from "../components/Taipy/utils";
  16. import { PageContext, TaipyContext } from "../context/taipyContext";
  17. import { createRequestUpdateAction, FormatConfig, TaipyBaseAction } from "../context/taipyReducers";
  18. import { TIMEZONE_CLIENT } from "../utils";
  19. /**
  20. * A React hook to manage a dynamic scalar property.
  21. *
  22. * A dynamic scalar property is defined by a default property and a bound property.
  23. * @typeParam T - The dynamic property type.
  24. * @param value - The bound value.
  25. * @param defaultValue - The default value.
  26. * @param defaultStatic - The default static value.
  27. * @returns The latest updated value.
  28. */
  29. export const useDynamicProperty = <T>(value: T, defaultValue: T, defaultStatic: T, checkType?: string, nullToDefault?: boolean): T => {
  30. return useMemo(() => {
  31. if (nullToDefault && value === null) {
  32. return defaultStatic;
  33. }
  34. if (value !== undefined && (!checkType || typeof value === checkType)) {
  35. return value;
  36. }
  37. if (nullToDefault && defaultValue === null) {
  38. return defaultStatic;
  39. }
  40. if (defaultValue !== undefined && (!checkType || typeof defaultValue === checkType)) {
  41. return defaultValue;
  42. }
  43. return defaultStatic;
  44. }, [value, defaultValue, defaultStatic, checkType, nullToDefault]);
  45. };
  46. /**
  47. * A React hook to manage a dynamic json property.
  48. *
  49. * A dynamic json property is defined by a default property and a bound property.
  50. * @typeParam T - The dynamic property type.
  51. * @param value - The bound value.
  52. * @param defaultValue - The default value.
  53. * @param defaultStatic - The default static value.
  54. * @returns The latest updated value.
  55. */
  56. export const useDynamicJsonProperty = <T>(value: string | undefined, defaultValue: string, defaultStatic: T): T => {
  57. const defaultJson = useMemo(() => {
  58. if (defaultValue && typeof defaultValue === "string") {
  59. try {
  60. return JSON.parse(defaultValue);
  61. } catch (e) {
  62. console.warn("useDynamicJsonProperty: defaultValue", e);
  63. }
  64. }
  65. return defaultStatic;
  66. }, [defaultValue, defaultStatic]);
  67. return useMemo(() => {
  68. if (value && typeof value === "string") {
  69. try {
  70. return JSON.parse(value);
  71. } catch (e) {
  72. console.warn("useDynamicJsonProperty: value", e);
  73. }
  74. }
  75. return defaultJson;
  76. }, [value, defaultJson]);
  77. };
  78. /**
  79. * A React hook that requests an update for every dynamic property of the element.
  80. * @param dispatch - The React dispatcher associated to `TaipyContext`.
  81. * @param id - The identifier of the element.
  82. * @param context - The execution context.
  83. * @param updateVars - The content of the property `updateVars`.
  84. * @param varName - The default property backend provided variable (through property `updateVarName`).
  85. * @param forceRefresh - Should Taipy re-evaluate the variables or use the current values.
  86. */
  87. export const useDispatchRequestUpdateOnFirstRender = (
  88. dispatch: Dispatch<TaipyBaseAction>,
  89. id?: string,
  90. context?: string,
  91. updateVars?: string,
  92. varName?: string,
  93. forceRefresh?: boolean
  94. ) => {
  95. useEffect(() => {
  96. const updateArray = getUpdateVars(updateVars).filter((uv) => !uv.includes(","));
  97. varName && updateArray.push(varName);
  98. updateArray.length && dispatch(createRequestUpdateAction(id, context, updateArray, forceRefresh));
  99. }, [updateVars, dispatch, id, context, varName, forceRefresh]);
  100. };
  101. export const useFormatConfig = (): FormatConfig => {
  102. const { state } = useContext(TaipyContext);
  103. return useMemo(
  104. () =>
  105. ({
  106. timeZone: state.timeZone || TIMEZONE_CLIENT,
  107. forceTZ: !!state.timeZone,
  108. dateTime: state.dateTimeFormat || "yyyy-MM-dd HH:mm:ss zzz",
  109. date: state.dateFormat || "yyyy-MM-dd",
  110. number: state.numberFormat || "%f",
  111. } as FormatConfig),
  112. [state.timeZone, state.dateFormat, state.dateTimeFormat, state.numberFormat]
  113. );
  114. };
  115. export const useIsMobile = () => {
  116. const theme = useTheme();
  117. return useMediaQuery(theme.breakpoints.down("sm"));
  118. };
  119. /**
  120. * A React hook that returns the *dispatch* function.
  121. *
  122. * The *dispatch* function allows to send Actions to the Store and initiate backend\
  123. * communications.
  124. * @returns The *dispatch* function.
  125. */
  126. export const useDispatch = () => {
  127. const { dispatch } = useContext(TaipyContext);
  128. return dispatch;
  129. };
  130. /**
  131. * A React hook that returns the page module.
  132. *
  133. * The *module* Needs to be added to all Actions to allow for the correct execution of backend functions.
  134. * @returns The page module.
  135. */
  136. export const useModule = () => {
  137. const { module } = useContext(PageContext);
  138. return module;
  139. };
  140. /**
  141. * A React hook to manage classNames (dynamic and static).
  142. * cf. useDynamicProperty
  143. *
  144. * @param libClassName - The default static className.
  145. * @param dynamicClassName - The bound className.
  146. * @param className - The default user set className.
  147. * @returns The complete list of applicable classNames.
  148. */
  149. export const useClassNames = (libClassName?: string, dynamicClassName?: string, className?: string) =>
  150. ((libClassName || "") + " " + (useDynamicProperty(dynamicClassName, className, undefined) || "")).trim();
  151. export const useWhyDidYouUpdate = (name: string, props: Record<string, unknown>): void => {
  152. // Get a mutable ref object where we can store props ...
  153. // ... for comparison next time this hook runs.
  154. const previousProps = useRef<Record<string, unknown>>();
  155. useEffect(() => {
  156. if (previousProps.current) {
  157. // Get all keys from previous and current props
  158. const allKeys = Object.keys({ ...previousProps.current, ...props });
  159. // Use this object to keep track of changed props
  160. const changesObj = {} as Record<string, unknown>;
  161. // Iterate through keys
  162. allKeys.forEach((key) => {
  163. // If previous is different from current
  164. if (previousProps.current && previousProps.current[key] !== props[key]) {
  165. // Add to changesObj
  166. changesObj[key] = {
  167. from: previousProps.current[key],
  168. to: props[key],
  169. };
  170. }
  171. });
  172. // If changesObj not empty then output to console
  173. if (Object.keys(changesObj).length) {
  174. console.log("[why-did-you-update]", name, changesObj);
  175. }
  176. }
  177. // Finally update previousProps with current props for next hook call
  178. previousProps.current = props;
  179. });
  180. };
  181. export const useElementVisible = (ref: RefObject<HTMLElement>) => {
  182. const observerRef = useRef<IntersectionObserver | null>(null);
  183. const [isOnScreen, setIsOnScreen] = useState(false);
  184. useEffect(() => {
  185. observerRef.current = new IntersectionObserver(([entry]) => setIsOnScreen(entry.isIntersecting));
  186. }, []);
  187. useEffect(() => {
  188. observerRef.current && ref.current && observerRef.current.observe(ref.current);
  189. return () => {
  190. observerRef.current && observerRef.current.disconnect();
  191. };
  192. }, [ref]);
  193. return isOnScreen;
  194. };
  195. export const useUniqueId = (id?: string) => useMemo(() => (id ? id : new Date().toISOString() + Math.random()), [id]);