React - How to add conditionally rendered extra options from child components (2 levels down) into state?

Can anyone help, i’m really stuck.

I have created a form as per the sandbox link: https://codesandbox.io/s/rlmv6ojjyn

In the SOW Type section there are three checkboxes. On clicking the checkboxes the user has access to further options (have not created these yet but the first option will require a set of drop-downs, the third option exposes a new checkbox (on clicking the first), which will (on click) will then expose some textarea boxes for the user to submit text.

I have got to this point as per the code in ‘SOWType.js’ (shown below) which is a child of ‘PdfGenFormContainer.js’.

I have an object ‘componentList’ (inside SOWType.js) which is used with conditional rendering to display the relevant sub-component on relevant checkbox selection)

My question is, how do I include the user selections from these sub-components in state. The object ‘componentList’ is in the child component so how does all this get communicated to the state in the parent? (so it is all included when the form is submitted)?. Does ‘componentList’ need to sit in the parent somehow? (how would this work?)

Is my only option to use redux to get this done or can it be simply done in just React?

I would be grateful if anyone could help.

SOWType.js:

import React from "react";
import ProdSOWExtOptions from "./ExtendedOptions/ProdSOWExtOptions";
import TeradataExtOptions from "./ExtendedOptions/TeradataExtOptions";
import CustomProfExtOptions from "./ExtendedOptions/CustomProfExtOptions";

class SOWType extends React.Component {
  componentList = {
    ProductSow: <ProdSOWExtOptions type={this.props} />,
    "Teradata Customer SOW": <TeradataExtOptions />,
    "Custom Professional Services SOW": <CustomProfExtOptions />
  };
  render() {
    // console.log(this.props);
    return (
      <div className="form-group">
        <label htmlFor={this.props.name} className="form-label">
          {this.props.title}
          <h6>{this.props.subtitle}</h6>
        </label>
        <div className="checkbox-group">
          {this.props.options.map(option => {
            return (
              <label key={option}>
                <input
                  className="form-checkbox"
                  name={this.props.setName}
                  onChange={this.props.controlFunc}
                  value={option}
                  checked={this.props.selectedOptions.indexOf(option) > -1}
                  type={this.props.type}
                />
                {option}
                {this.props.selectedOptions.indexOf(option) > -1 ? (
                  <h5>{this.componentList[option]}</h5>
                ) : (
                  " "
                )}
              </label>
            );
          })}
        </div>
      </div>
    );
  }
}

export default SOWType;

There are two ways to do this in React:

  1. Use Redux, so all user actions, no matter where they occur on the component tree, are dispatched straight the store, and your <App> re-renders itself based on the store’s new state. This is the uni-directional information flow for which Facebook initially designed Flux, and later improved by switching to Dan Abramov’s Redux, so there was a single source of truth (which, AFAICT, wasn’t true for Flux).
  2. “Bubble the data up” by passing event handlers down the component tree from parent to child. What I mean by this is that the main Stateful component (PdfGenFormContainer, in your case) defines functions that handle the events on the (grand^n-)children of itself. These functions are passed down the component tree as props until they arrive at the relevant component, which defines them as the handler of a JSX element, as in <input OnEvent={this.props.eventHandlerFunction}> (assuming a class component and not a SFC)

Great, thanks I appreciate your response and for outlining possible options to try, I was thinking i’d have to go with Redux but am a bit reticent as i still find it quite confusing (a good opportunity to get better i guess) but i hadn’t considered bubbling (didn’t even know what that was!) so it’s good to have another option to look into before diving into Redux, i’ll do some more research and see how i get on…Thanks again

Well, I did both in the FCC Front-end projects. I prefer Redux because the concept of a “traffic circle” of info flow made a lot of sense to me. It looked to me like your project has already been updated to use Redux.

BTW, if you want the person to whom you’re replying to be notified, you should hit the “reply” button on their post. I only saw this because I noticed the thread was in “latest”, and that you and I were the only participants still.

1 Like
// In the file with the top level component:

// This gives you two components: a Provider, which is a store for some values,
// and a Consumer, which is what you use to get those values:
export const SOWContext = React.createContext();

class SOWType extends React.Component {
  doSomething = () => {
    // some logic to update the state
    this.setState({/* yadda yadda, normal React stuff */})
  }

  state = {
    foo: 'bar',
    doSomething: this.doSomething.bind(this) // this is going to be the callback you pass down
    /* + Whatever other state you need w/r/t the components you want to conditionally render, normal React stuff */
  }

  // ...

  render() {
    return (
      <SOWContext.Provider value={this.state}>
        <div className="form-group">
          {/* etc etc */}
        </div>
      </SOWContext.Provider>
    )
  }
}

And then, somewhere deep down in the tree in your other component

import { SOWContext } from "./yourOtherFile";

class Foo extends React.Component {
  render() {
    return (
      <SOWContext.Consumer>
        {contextValue => (
          <button onClick={contextValue.doSomething}>Do something</button>
        )}
      </SOWContext.Consumer>
    )
  }
}

The Consumer that matches the Producer has access to all the state held in the the value property. If, in the top level component, you have callback funtions that update the state in that component, then you pass those in as the value in the Producer, you can then just pull them out using the Consumer anywhere in your app.

If it’s all really complex, and there’s lots and lots of state to manage, then maybe use Redux, but I’d hazard that you don’t need the big chunk of extra complexity it requires

Edit: if you are willing to use React 16.7.x-alpha (up to 16.7.2-alpha at the minute), which has hooks, and you can use function components for the components for the children you need to access the top level state, you can simplify the second step to

import React, {useContext} from "react";
import { SOWContext } from "./yourOtherFile";
,.
const Foo = (props) => {
  // This is the `value` object from the context
  const context = useContext(SOWContext)
  
  return (
    <button onClick={context.doSomething}>Do something</button>
  );
};
1 Like

Ah, i thought i had replied to you directly vipatron (i did hit the reply button directly under your post?) my apologies if that didn’t notify you of a response and you missed it, that was unintentional, i was very grateful that you replied with helpful suggestions.

Re Redux, I was reluctantly dabbling with Redux (and provisionally added some redux boilerplate because i thought there was no other option) but wondered if there were alternatives/whether i was missing something, i will definitely check out event bubbling (have heard of it but didn’t really know what it was) but think this would be a good opportunity to get Redux working as well. Again, thanks very much for your help.

1 Like

DanCouper - cool, this looks good thanks, i haven’t tried Context API in a project yet either

Just to double check, as per the first line of your comment, do you mean put :

'export const SOWContext = React.createContext()' 

in the parent (PdfGenFormContainer.js) or the child ‘‘SOWType.js’ I’m pretty sure you mean the parent (PdfGenFormContainer.js) but could you confirm?

Ah, yes, you’re going to wrap whatever you want to save state to in the YourContext.Provider, so it’s going to be in that top-level file (I’m sorry, didn’t look closely at the actual file structure you have). So you’d define it there (the export const YourContext = React.createContext()), wrap the component in the Provider in that file, then import it into any other files, wherever you need to access the state and use the YourContext.Consumer

Great, thanks for clarification.

Also, what does w/r/t mean?

One other thing if you don’t mind?

The componentList object (containing the conditional rendering options) currently sits in ‘SOWType.js’ (the child, not the parent) as that’s where the conditional rendering choices are required.

Do i need to move it to the parent ‘PdfGenFormContainer.js’? and include it in the state of the parent (can you put objects containing components in state?) in order to make it available to the child component ‘SOWType.js’? and save the choices (text inputs, dropdown boxes) that get passed in as props to these conditionally rendered components?

componentList = { 
     "ProductSow": <ProdSOWExtOptions />,
     Teradata Customer SOW: <TeradataExtOptions />,
     Custom Professional Services SOW: <CustomProfExtOptions />
}

Ah, sorry, w/r/t is “with respect to”, I shouldn’t have used that, it’s a bit confusing.

Regards where to put everything, I’ll take a look this evening — I need to look at exactly how the whole thing works before I offer any advice

Thanks, i would really appreciate it, I’ve been struggling with this for days

Hi DanCouper, Have you had a chance to look at this, are you able to advise/show a code example of how this would work?

Not in detail I’m afraid, due to work — just looking now, but can you explain how it works currently, how the conditional logic that you’re trying to put in place to render the correct options should work?

No Problem.

So, the sandbox is here: https://codesandbox.io/s/rlmv6ojjyn

In the (sandbox) render window there is a SOWType section with 3 checkboxes. The first checkbox on click, will expose another component containing a section of dropdown boxes (it currently just has a textbox for testing while i try to figure this out).

The second and third checkboxes expose different options on checking those boxes (i.e the third checkbox will expose some textboxes for entering information).

These extra components are described in an object called ‘componentList’ (which is currently sitting in the child component ‘‘SOWType.js’’ - line 7).

The problem is though I need the choices selected from these extra components available in the parent/in state so they can be included when the form is submitted.

So, I need to put the ‘componentList’ object (containing 3 components each requiring their own props/each needing certain parts of themselves in state) in the parent: ‘PdfGenFormContainer.js’ but how do i do this/wire it up properly?