3

Similar to Pass props to parent component in React.js but I am only interested in getting the child component's default props into the parent.

So why would I need to do that?

Well, I have a parent component that renders a child component containing an HTML form. The parent's render() method passes its state data down to the child as props, as it should.

Initially, the child form should contain some default data only. And it makes sense for that data to be taken from the defaultProps defined in the child component. After all, I don't want to be setting up a load of default data in the parent's state for the sole purpose of passing it down to the child as default props. The parent should not need to define the child's default props. The child should know what its own default props are.

So in my initial parent render(), I leave my state as undefined. When this gets passed down to the child as undefined props, the child will use its defaultProps instead. This is just what I want.

Time passes...

Now the user changes a field on a child component's form. I pass that change back up the parent via a callback function for it to recalculates the parent's new state. I then call a setState() in the parent to re-render all the child components as normal.

The problem is that I need my parent's state to be an object, containing further nested objects, and am using the [React Immutability Helpers][1] to calculate the new state in the parent, like so:

handleFormFieldChange(e) {
// reactUpdate imported from 'react-addons-update'
 var newState = reactUpdate(this.state, {
    formData: {values: {formFieldValues: {[e.currentTarget.name]: {$set: e.currentTarget.value}}}}
    });
    this.setState(newState);
}

The first time this code runs, it will throw an error because it's attempting to update the state values and there is no state to update. (I left it undefined, remember!)

If I attempt to get around this by setting up a default initial state to an empty object (each with nested empty objects) I run into a catch-22. On the first render, these empty values will get passed down the child as empty props, so overriding the defaultProps that I set up for the child component. Bummer!

So as far as I can see, I need a sensible initial state to pass down to the child component, but without (re)defining the whole thing again in the parent. So in this case, I think it would make sense to pull the child's defaultProps into the parent, just in order to set the parent's initial state.

I have a way of doing this, which I will post later on. But before I do that, does anybody have a better way of doing this? One that would avoid having to pass the defaultProps from the child to the parent at all?

Community
  • 1
  • 1
ChillyPenguin
  • 1,150
  • 12
  • 18

2 Answers2

3

If you're using React.Component then defaultProps is a static object on the class so it's easy to get.

export default class Child extends React.Component {

  static defaultProps = { ... }

}

// If you don't have es7 static support you can add it after the class def:
Child.defaultProps = { ... }

The parent just needs to do:

Child.defaultProps

to access it.

Dominic
  • 62,658
  • 20
  • 139
  • 163
1

As threatened in the original post, here's how I'm getting the child's default props sent up to the parent.

I'm using ES6 Classes and the module pattern, with Webpack and Babel to handle the building and transpiling.

So I have two files, let's call them Parent.jsx and ChildForm.jsx. Here's how ChildForm.jsx might look (NB: not working code!):

class ChildForm extends React.Component {
    render() {
         var fieldValues= this.props.formData.formFieldValues;
         return(
             <form>
                  Field 1: <input type="text"
                              name="field1"
                              value={fieldValues.field1}
                              onChange={this.props.handleFieldChange} />
             </form>
         );
    }
}

export function getDefaultProps() {
   return {
        formData: {
           formFieldValues: {
               field1: "Apples",
               field2: "Bananas"
           }
        }
   };
}

ChildForm.defaultProps = getDefaultProps();

export default ChildForm;

The trick is to move the setting of defaultProps to a separate function, which is also exported from the ChildForm.jsx. Having done that, you can probably guess what I'm going to do in the Parent.jsx module! Let's take a look:

import reactUpdate from 'react-addons-update';
import ChildForm from "./ChildForm.jsx";
import {getDefaultProps as getChildFormDefaultProps} from "./ChildForm.jsx";

class Parent extends React.Component {
    constructor(props) {
        super(props);
        var formDefaultProps = getChildFormDefaultProps();
        this.state = {
            formData: formDefaultProps
        };
        this.handleFormFieldChange.bind(this);
    }

    handleFormFieldChange(e) {
        var newState = reactUpdate(this.state, {
            formData: {values: {formFieldValues: {[e.currentTarget.name]: {$set: e.currentTarget.value}}}}
        });
        this.setState(newState);
    }

    render() {
        return (
            <div>
                <ChildForm
                    formData={this.state.formData}
                    handleFieldChange={this.props.handleFormFieldChange} /}
            </div>
        );
    }
}

I import the getDefaultProps function from the ChildForm.jsx module and then use that to set the default state for ChildForm at the Parent.jsx level. That state is then passed down to ChildForm as props.

Although this works, I can't help feeling that it's rather clunky. Although I'm defining the defaultProps at the ChildForm.jsx level, where they should be, they're never actually used as defaultProps at that level! Rather, they're passed up the Parent, which uses them to set state, which in turn is passed down to ChildForm as real props.

Still, this is preferable to having to define a default state for the ChildForm component all over again in the Parent component. So it will do for now, unless any of you kind people can suggest something cleaner!

ChillyPenguin
  • 1,150
  • 12
  • 18