Search code examples
reactjstypescriptformsformiktsx

Get Inputs From Child Component Form That Doesn't Pass Props?


In the following Parent component, I have a Dropdown with two options. If you select "TOP LEVEL", then a form called Form1 will show. If I choose "MAKE ITEM" then a form called Form2 will show. If nothing is selected then both forms are hidden. I have a Button in my Parent component and I want to submit whatever inputs are in the current form that is shown. So if "TOP LEVEL" was selected, and the Button is clicked, I want to log the input's from Form1. Is this possible to do? Or do I need to put everything into one component? If this is possible, do I need to use Formik? If I am approaching this wrong please advise a better way.

Parent Component

import * as React from "react";
import { Dropdown, DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { PrimaryButton } from 'office-ui-fabric-react/lib/';
import Form1 from './Form1';
import Form2 from './Form2';
export interface ParentProps {
};

export interface ParentState  {
  selectedItem?: { key: string | number | undefined };
  operationType?;
  formName?
};

export default class ParentComponent extends React.Component<ParentProps, ParentState> {
  constructor(props, context) {
    super(props, context);
    this.state = {
      operationType: '',
      formName: '',
    };
  }
  addToExcel = async () => {
    try {
      await Excel.run(async context => {
        const range = context.workbook.getSelectedRange();
        range.load("address");
        await context.sync();
        console.log(`The range address was ${range.address}.`);
      });
    } catch (error) {
      console.error(error);
    }
    this.setState({
      operationType: '',
    })
  };
render(){
  const { selectedItem } = this.state;
  const options: IDropdownOption[] = [
      { key: 'blank', text: '' },
      { key: 'topLevelMake', text: 'Parents', itemType: DropdownMenuItemType.Header },
      { key: 'topLevel', text: 'TOP LEVEL' },
      { key: 'make', text: 'MAKE ITEM' },
    ];
    return(
    <div>
        <Dropdown
            label="Operation"
            selectedKey={selectedItem ? selectedItem.key : undefined}
            onChange={this._onChange}
            placeholder={"Select an option"}
            options={options}
            styles={{ dropdown: { width: 300 } }}
        />
        {this.state.formName}  
        <p></p>
        <PrimaryButton 
            text="Enter"
            onClick={this.addToExcel}
        />
    </div>
      );
   }
   private _onChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
    this.setState({ selectedItem: item });
    this.setState({operationType: item.text})
    console.log(event);
    let Form = <div />;
    switch (item.text) {

      case "TOP LEVEL":
        Form = <Form1 /> ;
        this.setState({formName: Form});
        break;

      case "MAKE ITEM":
        Form = <Form2 /> ;
        this.setState({formName: Form});
      break;


      default:
        Form = <div></div>
      break;
    }
  };
}

Form1 (Form2 is similar so I am not including)

import * as React from "react";
import { TextField } from 'office-ui-fabric-react/lib/';
export interface Form1Props {
};
export interface Form1State  {
  dataGoToExcel?;
  dataGoToExcel2?;
};
export default class Form1 extends React.Component<Form1Props, Form1State> {
  constructor(props, context) {
    super(props, context);
    this.state = {
      dataGoToExcel: '',
      dataGoToExcel2: '',
    };
  }
    handleChange = (event) => {
        this.setState({
          dataGoToExcel: event.target.value,
        })
    };
    handleChange2 = (event) => {
        this.setState({
          dataGoToExcel2: event.target.value,
        })
    };
render(){
    return(
    <div>
      <TextField 
        label="TextField"
        type="text"
        value={this.state.dataGoToExcel}
        onChange={this.handleChange}
      />
      <TextField 
        label="Another TextField"
        type="text"
        value={this.state.dataGoToExcel2}
        onChange={this.handleChange2}
      />
    </div>
      );
   }
  };

EDIT:

Parent

import * as React from "react";
import { Dropdown, DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { PrimaryButton } from 'office-ui-fabric-react/lib/';
import Form1 from './Form1';
//import Form2 from './Form2';
export interface ParentProps {

};

export interface ParentState  {
  selectedItem?: { key: string | number | undefined };
  operationType?;
  formName?;
  formData;
  updateFormData?;
};

export default class ParentComponent extends React.Component<ParentProps, ParentState> {
  constructor(props, context) {
    super(props, context);
    this.state = {
      operationType: '',
      formName: '',
      formData: ''
    };
  }

  updateFormData = (data) => this.setState({ formData: data })

  addToExcel = async () => {
    try {
      await Excel.run(async context => {
        const range = context.workbook.getSelectedRange();
        range.load("address");
        await context.sync();
      console.log((`The range address was ${this.state.formData}.`))
      });
    } catch (error) {
      console.error(error);
    }
    this.setState({
      operationType: '',
      formData: '',
      formName: '',
    })
  };
  render() {
    const { selectedItem } = this.state;
    const options: IDropdownOption[] = [
      { key: 'blank', text: '' },
      { key: 'topLevelMake', text: 'Parents', itemType: DropdownMenuItemType.Header },
      { key: 'topLevel', text: 'TOP LEVEL' },
      { key: 'make', text: 'MAKE ITEM' },
    ];
    return (
      <div>
        <Dropdown
          label="Operation"
          selectedKey={selectedItem ? selectedItem.key : undefined}
          onChange={this._onChange}
          placeholder={"Select an option"}
          options={options}
          styles={{ dropdown: { width: 300 } }}
        />
        {this.state.formName}
        <p></p>
        <PrimaryButton
          text="Enter"
          onClick={this.addToExcel}
        />
      </div>
    );
  }
  private _onChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
    this.setState({ selectedItem: item });
    this.setState({ operationType: item.text })
    console.log(event);
    let Form = <div />;
    switch (item.text) {

      case "TOP LEVEL":
        Form = <Form1 formData={this.state.formData} onDataChange={this.updateFormData} />;
        this.setState({ formName: Form });
        break;

      //case "MAKE ITEM":
      //  Form = <Form2 formData={this.state.formName} onDataChange={this.updateFormData} />;
      //  this.setState({ formName: Form });
      //  break;


      default:
        Form = <div></div>
        break;
    }
  };
}

Form1 (commented out Form2)

import * as React from "react";
import { TextField } from 'office-ui-fabric-react/lib/';

export interface Form1Props {
  formData: any /* Type of form data */
  onDataChange: (data: any) => void 
};

export default class Form1 extends React.Component<Form1Props> {

  handleChange = (event: any)  => {
    const data = this.props.formData & event.target.value; /* merge props.formdata and event.target.value*/
    this.props.onDataChange(data)
  };

  handleChange2 = (event: any)  => {
    const data = this.props.formData & event.target.value;/* merge props.formdata and event.target.value*/
    this.props.onDataChange(data)
  };

  render(){
    return (
      <div>
        <TextField
          label="TextField"
          type="text"
          value={this.props.formData.dataGoToExcel}
          onChange={this.handleChange}
        />
        <TextField
          label="Another TextField"
          type="text"
          value={this.props.formData.dataGoToExcel2}
          onChange={this.handleChange2}
        />
      </div>
    );
  }
};

Solution

  • One way to do this is to

    • Have a function which updates the form data into Parent state
    • Pass this function to both Form 1 and 2 as props.
    • For any update in the forms, this function must be called to update parent state
    • These updated values must flow to children as props as well.

    Finally, when the button in Parent is clicked, the contents from the state can be submitted.

    You'll have to move up local states from Form1 and Form2 into parent and send pass necessary form values as props to child components and no local states for the child components would be needed

    EDIT:

    Parent Component

    export default class ParentComponent extends React.Component<ParentProps, ParentState> {
      constructor(props, context) {
        super(props, context);
        this.state = {
          operationType: '',
          formName: '',
          formData: {/*data as object*/ }
        };
      }
    
      updateFormData = (data) => this.setState({ formData: data })
    
      addToExcel = async () => {
        try {
          await Excel.run(async context => {
            const range = context.workbook.getSelectedRange();
            range.load("address");
            await context.sync();
            console.log(`The range address was ${range.address}.`);
          });
        } catch (error) {
          console.error(error);
        }
        this.setState({
          operationType: '',
        })
      };
      render() {
        const { selectedItem } = this.state;
        const options: IDropdownOption[] = [
          { key: 'blank', text: '' },
          { key: 'topLevelMake', text: 'Parents', itemType: DropdownMenuItemType.Header },
          { key: 'topLevel', text: 'TOP LEVEL' },
          { key: 'make', text: 'MAKE ITEM' },
        ];
        return (
          <div>
            <Dropdown
              label="Operation"
              selectedKey={selectedItem ? selectedItem.key : undefined}
              onChange={this._onChange}
              placeholder={"Select an option"}
              options={options}
              styles={{ dropdown: { width: 300 } }}
            />
            {this.state.formName}
            <p></p>
            <PrimaryButton
              text="Enter"
              onClick={this.addToExcel}
            />
          </div>
        );
      }
      private _onChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
        this.setState({ selectedItem: item });
        this.setState({ operationType: item.text })
        console.log(event);
        let Form = <div />;
        switch (item.text) {
    
          case "TOP LEVEL":
            Form = <Form1 formData={this.state.formData} onDataChange={this.updateFormData} />;
            this.setState({ formName: Form });
            break;
    
          case "MAKE ITEM":
            Form = <Form2 formData={this.state.formData} onDataChange={this.updateFormData} />;
            this.setState({ formName: Form });
            break;
    
    
          default:
            Form = <div></div>
            break;
        }
      };
    }
    
    

    Form 1 Component

    import * as React from "react";
    import { TextField } from 'office-ui-fabric-react/lib/';
    
    export interface Form1Props {
      formData: any /* Type of form data */
      onDataChange: (data: any) => void
    };
    
    export default class Form1 extends React.Component<Form1Props> {
    
      handleChange = (event) => {
        const data = /* merge props.formdata and event.target.value*/
        this.props.onDataChange(data)
      };
    
      handleChange2 = (event) => {
        const data = /* merge props.formdata and event.target.value*/
        this.props.onDataChange(data)
      };
    
      render(){
        return (
          <div>
            <TextField
              label="TextField"
              type="text"
              value={this.props.formData.dataGoToExcel}
              onChange={this.handleChange}
            />
            <TextField
              label="Another TextField"
              type="text"
              value={this.props.formData.dataGoToExcel2}
              onChange={this.handleChange2}
            />
          </div>
        );
      }
    };