PorterFormContextProvider.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import React, { createContext, useReducer } from "react";
  2. import {
  3. PorterFormData,
  4. PorterFormState,
  5. PorterFormAction,
  6. PorterFormVariableList,
  7. } from "./types";
  8. import { ShowIf, ShowIfAnd, ShowIfNot, ShowIfOr } from "../../shared/types";
  9. interface Props {
  10. rawFormData: PorterFormData;
  11. initialVariables?: PorterFormVariableList;
  12. overrideVariables?: PorterFormVariableList;
  13. }
  14. interface ContextProps {
  15. formData: PorterFormData;
  16. formState: PorterFormState;
  17. dispatchAction: (event: PorterFormAction) => void;
  18. }
  19. export const PorterFormContext = createContext<ContextProps | undefined>(
  20. undefined!
  21. );
  22. const { Provider } = PorterFormContext;
  23. export const PorterFormContextProvider: React.FC<Props> = (props) => {
  24. const handleAction = (
  25. state: PorterFormState,
  26. action: PorterFormAction
  27. ): PorterFormState => {
  28. switch (action.type) {
  29. case "init-field":
  30. if (!(action.id in state.components)) {
  31. return {
  32. ...state,
  33. components: {
  34. ...state.components,
  35. [action.id]: {
  36. state: action.initValue,
  37. validation: {
  38. ...{
  39. error: false,
  40. loading: false,
  41. validated: false,
  42. touched: false,
  43. },
  44. ...action.initValidation,
  45. },
  46. },
  47. },
  48. };
  49. }
  50. break;
  51. case "update-field":
  52. return {
  53. ...state,
  54. components: {
  55. ...state.components,
  56. [action.id]: {
  57. ...state.components[action.id],
  58. state: action.updateFunc(state.components[action.id]),
  59. },
  60. },
  61. };
  62. case "mutate-vars":
  63. return {
  64. ...state,
  65. variables: action.mutateFunc(state.variables),
  66. ...props.overrideVariables,
  67. };
  68. }
  69. return state;
  70. };
  71. const [state, dispatch] = useReducer(handleAction, {
  72. components: {},
  73. variables: props.initialVariables || {},
  74. });
  75. console.log(state.variables);
  76. const evalShowIf = (
  77. vals: ShowIf,
  78. variables: PorterFormVariableList
  79. ): boolean => {
  80. if (!vals) {
  81. return false;
  82. }
  83. if (typeof vals == "string") {
  84. return !!variables[vals];
  85. }
  86. if ((vals as ShowIfOr).or) {
  87. vals = vals as ShowIfOr;
  88. for (let i = 0; i < vals.or.length; i++) {
  89. if (evalShowIf(vals.or[i], variables)) {
  90. return true;
  91. }
  92. }
  93. return false;
  94. }
  95. if ((vals as ShowIfAnd).and) {
  96. vals = vals as ShowIfAnd;
  97. for (let i = 0; i < vals.and.length; i++) {
  98. if (!evalShowIf(vals.and[i], variables)) {
  99. return false;
  100. }
  101. }
  102. return true;
  103. }
  104. if ((vals as ShowIfNot).not) {
  105. vals = vals as ShowIfNot;
  106. return !evalShowIf(vals.not, variables);
  107. }
  108. return false;
  109. };
  110. /*
  111. We don't want to have the actual <PorterForm> component to do as little form
  112. logic as possible, so this structures the form object based on show_if statements
  113. This computed structure also later lets us figure out which fields should be required
  114. */
  115. const computeFormStructure = (
  116. data: PorterFormData,
  117. variables: PorterFormVariableList
  118. ) => {
  119. return {
  120. ...data,
  121. tabs: data.tabs.map((tab) => {
  122. return {
  123. ...tab,
  124. sections: tab.sections.filter((section) => {
  125. return !section.show_if || evalShowIf(section.show_if, variables);
  126. }),
  127. };
  128. }),
  129. };
  130. };
  131. const formData = computeFormStructure(props.rawFormData, state.variables);
  132. return (
  133. <Provider
  134. value={{
  135. formData: formData,
  136. formState: state,
  137. dispatchAction: dispatch,
  138. }}
  139. >
  140. {props.children}
  141. </Provider>
  142. );
  143. };