Alan
Alan

Reputation: 46813

Providing external functionality to existing ReactJS components

Background

I have a simple ReactJS 3-step form which consumers use to request tech support from our partners.

Until now, the support request form was universal for all of our partners (name, email, problem)

Now, I'd like to for a specific partner, add additional form elements. Ideally this should be customizable on a per-partner basis. Example, Partner A, would like to also capture the serial number of the device. Partner B would like to have the customer agree to a terms of service.

To solve this, I am passing along an optional component to my form. If there, it is rendered. I have not figured out how to access the optional components data, and pass it along as an opaque data field as part of the request submission.

Question

In the code below, in Form how can I access the state of FormOverride?

Or, is there a more idomatic way to achieve what I am looking for?

Unlike a parent / child hierarchy where a parent can access the child state in a number of ways, the FormOverride component is instantiated outside of the Form hierarchy, and passed as a property.

import React from "react";

const Form = ({ override }) => {
  
  //universal form state
  
  const handleSubmit = () => {
    if (override) {
      //get the state value from override component
    }

    alert("Clicked");
  };

  return (
    <>
      <h3>Contact Info</h3>
      {override}
      <h3>Support Details</h3>
      <h3>Case Summary</h3>

      <button onClick={handleSubmit}>Submit</button>
    </>
  );
};

const FormOverride = () => {
  const [text, setText] = React.useState("");

  const handleChange = (e) => {
    setText(e.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={handleChange} />
    </div>
  );
};

export default function App() {

  const someValue = "composed";

  let toRender = null;

  if (someValue === "default") {
    toRender = <Form/>;
  }

  if (someValue === "composed") {
    toRender = <Form override=<FormOverride /> />;
  }

  return <div className="App">{toRender}</div>;
}

I also have a sandbox of the above.

Upvotes: 0

Views: 51

Answers (1)

nima
nima

Reputation: 8915

In such cases, a global state manager like Redux is helpful but you can implement it without a global state manager with the state lift up technique (as you mentioned in the comments) but in a tricky way:

Let's implement a state in the App component and pass it to the related components:

export default function App() {
  const [textOverride, setTextOverride] = React.useState("");

  const someValue = "composed";

  let toRender = null;

  if (someValue === "default") {
    toRender = <SomeComponent />;
  }

  if (someValue === "composed") {
    toRender = (
      <SomeComponent
        override={<ComposedValue getOverrideText={setTextOverride} />}
        textOverride={textOverride}
      />
    );
  }

  return <div className="App">{toRender}</div>;
}

As you see, getOvverideText props were passed to the ComposedValue, so we need to call it when the input changes.

const ComposedValue = ({ getOverrideText }) => {
  const [text, setText] = React.useState("");

  const handleChange = (e) => {
    const value = e.target.value;

    setText(value);
    getOverrideText(value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={handleChange} />
    </div>
  );
};

Now, the textOvveride value (which we defined in the App) is up to date with text input changes. so we can easily use the textOverride value in the SomeComponent as below:

const SomeComponent = ({ override, textOverride }) => {

  const handleSubmit = () => {
    if (override) {
      //get the state value from override component
    }

    alert(textOverride);
  };

  return (
    <>
      <h3>Contact Info</h3>
      {override}
      <h3>Support Details</h3>
      <h3>Case Summary</h3>
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
};

Note: I prefer using a Global State Manager like Redux to do such things within as the above implementation is hard to debug and change. The bad side of this implementation is SomeComponent will re-render with every single change in the input element.

Note: there is no way to get the overrideText as you call the handleSubmit function because there aren't child-parent components. But, with using such tools as Redux, you can ignore the extra re-render and get the overrideText at same the time as calling the handleSubmit function.

Note: I guess you simplified the components to demonstrate your purpose, otherwise using an independent input element inside of the SomeComponent will reduce the codes and the procedure to get the changes from another component.

Upvotes: 1

Related Questions