TaipyRendered.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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 React, { useEffect, useState, useContext, ComponentType } from "react";
  14. import axios from "axios";
  15. import JsxParser from "react-jsx-parser";
  16. import { useLocation } from "react-router-dom";
  17. import { Helmet } from "react-helmet-async";
  18. import { ErrorBoundary } from "react-error-boundary";
  19. import { PageContext, TaipyContext } from "../../context/taipyContext";
  20. import { getRegisteredComponents } from "../Taipy";
  21. import { unregisteredRender, renderError } from "../Taipy/Unregistered";
  22. import { createPartialAction } from "../../context/taipyReducers";
  23. import ErrorFallback from "../../utils/ErrorBoundary";
  24. import { getBaseURL } from "../../utils";
  25. interface TaipyRenderedProps {
  26. path?: string;
  27. partial?: boolean;
  28. fromBlock?: boolean;
  29. }
  30. interface HeadProps {
  31. tag: string;
  32. props: Record<string, string>;
  33. content: string;
  34. }
  35. interface AxiosRenderer {
  36. jsx: string;
  37. style: string;
  38. head: HeadProps[];
  39. context: string;
  40. }
  41. // set global style the traditional way
  42. const setStyle = (id: string, styleString: string): void => {
  43. let style = document.getElementById(id);
  44. if (style && style.tagName !== "STYLE") {
  45. style = null;
  46. id = "TaiPy_" + id;
  47. }
  48. if (!style && styleString) {
  49. style = document.createElement("style");
  50. style.id = id;
  51. document.head.append(style);
  52. }
  53. if (style) {
  54. style.textContent = styleString;
  55. }
  56. };
  57. const emptyArray: string[] = [];
  58. interface PageState {
  59. jsx?: string;
  60. module?: string;
  61. }
  62. const TaipyRendered = (props: TaipyRenderedProps) => {
  63. const { partial, fromBlock } = props;
  64. const location = useLocation();
  65. const [pageState, setPageState] = useState<PageState>({});
  66. const [head, setHead] = useState<HeadProps[]>([]);
  67. const { state, dispatch } = useContext(TaipyContext);
  68. const baseURL = getBaseURL();
  69. const pathname = baseURL == "/" ? location.pathname : location.pathname.replace(baseURL, "/");
  70. const path =
  71. props.path || (state.locations && pathname in state.locations && state.locations[pathname]) || pathname;
  72. useEffect(() => {
  73. // Fetch JSX Flask Backend Render
  74. if (partial) {
  75. dispatch(createPartialAction(path.slice(1), false));
  76. } else {
  77. const searchParams = new URLSearchParams(location.search);
  78. const params = Object.fromEntries(searchParams.entries());
  79. axios
  80. .get<AxiosRenderer>(`taipy-jsx${path}`, {
  81. params: { ...params, client_id: state.id || "", v: window.taipyVersion },
  82. })
  83. .then((result) => {
  84. // set rendered JSX and CSS style from fetch result
  85. if (typeof result.data.jsx === "string") {
  86. setPageState({ module: result.data.context, jsx: result.data.jsx });
  87. }
  88. if (!fromBlock) {
  89. setStyle("Taipy_style", result.data.style || "");
  90. Array.isArray(result.data.head) && setHead(result.data.head);
  91. }
  92. })
  93. .catch((error) =>
  94. setPageState({
  95. jsx: `<h1>${
  96. error.response?.data ||
  97. `No data fetched from backend from ${
  98. path === "/TaiPy_root_page" ? baseURL : baseURL + path
  99. }`
  100. }</h1><br></br>${error}`,
  101. })
  102. );
  103. }
  104. // eslint-disable-next-line react-hooks/exhaustive-deps
  105. }, [path, state.id, dispatch, partial, fromBlock, baseURL]);
  106. return (
  107. <ErrorBoundary FallbackComponent={ErrorFallback}>
  108. {head.length ? (
  109. <Helmet>
  110. {head.map((v, i) => React.createElement(v.tag, { key: `head${i}`, ...v.props }, v.content))}
  111. </Helmet>
  112. ) : null}
  113. <PageContext.Provider value={pageState}>
  114. <JsxParser
  115. disableKeyGeneration={true}
  116. bindings={state.data}
  117. components={getRegisteredComponents() as Record<string, ComponentType>}
  118. jsx={pageState.jsx}
  119. renderUnrecognized={unregisteredRender}
  120. allowUnknownElements={false}
  121. renderError={renderError}
  122. blacklistedAttrs={emptyArray}
  123. />
  124. </PageContext.Provider>
  125. </ErrorBoundary>
  126. );
  127. };
  128. export default TaipyRendered;