Просмотр исходного кода

form hook and update state in reducer correctly

Ivan Galakhov 4 лет назад
Родитель
Сommit
09301cc6a8

+ 5 - 4
dashboard/src/components/form-refactor/PorterForm.tsx

@@ -15,14 +15,14 @@ const PorterForm: React.FC<Props> = () => {
     formData.tabs.length > 0 ? formData.tabs[0].name : ""
   );
 
-  const renderSectionField = (field: FormField): JSX.Element => {
+  const renderSectionField = (field: FormField, id: string): JSX.Element => {
     switch (field.type) {
       case "heading":
         return <Heading>{field.label}</Heading>;
       case "subtitle":
         return <Helper>{field.label}</Helper>;
       case "string-input":
-        return <StringInput />;
+        return <StringInput id={id} />;
     }
     return <p>Not Implemented: {field.type}</p>;
   };
@@ -31,9 +31,10 @@ const PorterForm: React.FC<Props> = () => {
     return (
       <>
         {section.contents.map((field, i) => {
+          const id = `${section.name}-${field.type}-${i}`;
           return (
-            <React.Fragment key={`${section.name}-${field.type}-${i}`}>
-              {renderSectionField(field)}
+            <React.Fragment key={id}>
+              {renderSectionField(field, id)}
             </React.Fragment>
           );
         })}

+ 39 - 9
dashboard/src/components/form-refactor/PorterFormContextProvider.tsx

@@ -7,6 +7,7 @@ interface Props {
 
 interface ContextProps {
   formData: PorterFormData;
+  formState: PorterFormState;
   dispatchAction: (event: PorterFormAction) => void;
 }
 
@@ -16,17 +17,46 @@ export const PorterFormContext = createContext<ContextProps | undefined>(
 const { Provider } = PorterFormContext;
 
 export const PorterFormContextProvider: React.FC<Props> = (props) => {
-  const [state, dispatch] = useReducer(
-    (state: PorterFormState, action: PorterFormAction) => {
-      console.log(action);
-      return state;
-    },
-    {
-      components: [],
+  const handleAction = (
+    state: PorterFormState,
+    action: PorterFormAction
+  ): PorterFormState => {
+    switch (action.type) {
+      case "init-field":
+        if (!(action.id in state.components)) {
+          return {
+            ...state,
+            components: {
+              ...state.components,
+              [action.id]: action.initValue,
+            },
+          };
+        }
+        break;
+      case "update-field":
+        return {
+          ...state,
+          components: {
+            ...state.components,
+            [action.id]: action.updateFunc(state.components[action.id]),
+          },
+        };
     }
-  );
+    return state;
+  };
+
+  const [state, dispatch] = useReducer(handleAction, {
+    components: {},
+  });
+
   return (
-    <Provider value={{ formData: props.formData, dispatchAction: dispatch }}>
+    <Provider
+      value={{
+        formData: props.formData,
+        formState: state,
+        dispatchAction: dispatch,
+      }}
+    >
       {props.children}
     </Provider>
   );

+ 32 - 3
dashboard/src/components/form-refactor/field-components/StringInput.tsx

@@ -1,8 +1,37 @@
-import React from "react";
+import React, { useContext, useEffect } from "react";
 import InputRow from "../../values-form/InputRow";
+import useFormField from "../hooks/useFormField";
+import { StringInputFieldState } from "../types";
 
-const StringInput = () => {
-  return <InputRow width="100%" type="text" value="" />;
+interface Props {
+  id: string;
+}
+
+const StringInput: React.FC<Props> = ({ id }) => {
+  const { state, updateState } = useFormField<StringInputFieldState>(id, {
+    value: "",
+  });
+
+  // TODO: needs a loading wrapper
+  if (state == undefined) {
+    return <></>;
+  }
+
+  return (
+    <InputRow
+      width="100%"
+      type="text"
+      value={state.value}
+      setValue={(x: string) => {
+        updateState((prev) => {
+          return {
+            ...prev,
+            value: x,
+          };
+        });
+      }}
+    />
+  );
 };
 
 export default StringInput;

+ 38 - 0
dashboard/src/components/form-refactor/hooks/useFormField.tsx

@@ -0,0 +1,38 @@
+import { useContext, useEffect } from "react";
+import { PorterFormContext } from "../PorterFormContextProvider";
+import { PorterFormFieldFieldState } from "../types";
+
+interface FormFieldData<T> {
+  state: T;
+  updateState: (updateFunc: (prev: T) => T) => void;
+}
+
+const useFormField = <T extends PorterFormFieldFieldState>(
+  fieldId: string,
+  initValue: T
+): FormFieldData<T> => {
+  const { dispatchAction, formState } = useContext(PorterFormContext);
+
+  useEffect(() => {
+    dispatchAction({
+      type: "init-field",
+      id: fieldId,
+      initValue,
+    });
+  }, []);
+
+  const updateState = (updateFunc: (prev: T) => T) => {
+    dispatchAction({
+      type: "update-field",
+      id: fieldId,
+      updateFunc,
+    });
+  };
+
+  return {
+    state: formState.components[fieldId] as T,
+    updateState,
+  };
+};
+
+export default useFormField;

+ 28 - 6
dashboard/src/components/form-refactor/types.ts

@@ -3,15 +3,17 @@
   Will be merged with shared types later
 */
 
+// YAML Field interfaces
+
 export interface BasicFormField {
   type: string;
 }
 
-export interface HeadingField extends  BasicFormField {
+export interface HeadingField extends BasicFormField {
   label: string;
 }
 
-export interface SubtitleField extends  BasicFormField {
+export interface SubtitleField extends BasicFormField {
   label: string;
 }
 
@@ -34,12 +36,32 @@ export interface PorterFormData {
   tabs: Tab[];
 }
 
+// internal field state interfaces
+
+export interface StringInputFieldState {
+  value: string;
+}
+
+export type PorterFormFieldFieldState = StringInputFieldState;
+
+// reducer interfaces
+
 export interface PorterFormState {
-  components: string[];
+  components: {
+    [key: string]: PorterFormFieldFieldState
+  }
 }
 
-export interface PorterFormBaseAction {
-  type: string;
+export interface PorterFormInitFieldAction {
+  type: "init-field",
+  id: string;
+  initValue: PorterFormFieldFieldState;
+}
+
+export interface PorterFormUpdateFieldAction {
+  type: "update-field",
+  id: string;
+  updateFunc: (prev: PorterFormFieldFieldState) => PorterFormFieldFieldState;
 }
 
-export type PorterFormAction = PorterFormBaseAction;
+export type PorterFormAction = PorterFormInitFieldAction|PorterFormUpdateFieldAction;