2

Suppose I have a container component for handling app logic which has a lot of methods:

class ScreenContainer extends React.Component
{
    state = {
        inputs: { /* various properties for input values */ },
        thingyActive: false,
        someList: ["thing1", "thing2"],
        // ...etc.
    };

    handleInputChange = e => {
        const { name, value } = e.target;
        this.setState(prevState => ({
            inputs: { ...prevState.inputs, [name]: value }
        }));
    };

    toggleThingy = () => this.setState(prevState => ({
        thingyActive: !prevState.thingyActive
    }));

    coolMethod = () => { /* Do cool stuff */ };

    boringMethod = () => { /* Do boring stuff */ };

    // ...more methods...
}

I need ALL of these methods to be accessible to inner components. I'll use a Context provider in this example, and we'll just say that the context gets consumed by various nested presentational components making up a screen in the application.

const ScreenContext = React.createContext();

To pass methods either down to a child component or into a context provider value, it seems you always end up having to do something like below (note that I'm lifting the "actions" into state in this example per the advice given in the React documentation).

class ScreenContainer extends React.Component
{
    constructor()
    {
        super();
        this.state = {
            // ...same state as before, plus:
            actions: {
                handleInputChange: this.handleInputChange,
                toggleThingy: this.toggleThingy,
                coolMethod: this.coolMethod,
                boringMethod: this.boringMethod,
                everySingleOtherMethod: this.everySingleOtherMethod,
                // ...on and on
            }
        };
    }

    // ...same methods as before...

    render()
    {
        return (
            <ScreenContext.Provider value={this.state}>
                {this.props.children}
            </ScreenContext.Provider>
        );
    }

I was looking for a way to avoid passing them all one by one. A possible solution I found involves using a getter and looping through the class instance properties like so:

    get allMethods()
    {
        let output = {};

        for (var prop in this)
        {
            if (this.hasOwnProperty(prop) && typeof this[prop] === "function")
                output[prop] = this[prop];
        }

        return output;
    }

Then I can just do:

    // (in ScreenContainer constructor)

    this.state = {
        // ...state,
        actions: this.allMethods
    };

The getter code could also be extracted out into a utility function for reuse in other container-type components if needed. Obviously, this is only worthwhile if there are a ton of methods to be passed down.

It seems simple enough and appears to work just fine as long as it's done in the contructor. Is there anything crazy about this? Is it bad practice in any way, or does it have any potential side effects I'm not aware of? Is there maybe a better way I'm missing?

EDIT

I've updated the example to be closer to my real code; it now shows what kinds of things the methods might do and uses a Context setup rather than passing the methods down as props to a single child component.

ReeseyCup
  • 23
  • 6
  • I hope that you are aware that you most likely have a problem with your architecture if you need to pass down tons of methods to a single child component – Daniel Hilgarth Oct 04 '18 at 16:47
  • Well the proper design would be to "separate the concern". Donot define functions in parent if only the child uses it. Its better to define it in the child component. And please donot loop through the component to find the function. My suggestion would be the first one. And if there are a lot of nested children, then try to use react redux. – Skaranjit Oct 04 '18 at 17:03
  • I have a similar answer to this problem on another question, thought I'd share: "Correct way to share functions between components in React" https://stackoverflow.com/a/51661103/2430549 – HoldOffHunger Oct 04 '18 at 19:04
  • @DanielHilgarth I think you're right, but I've made some edits that hopefully better clarify my scenario. – ReeseyCup Oct 05 '18 at 14:36

2 Answers2

1

If a class doesn't maintain a state, and class methods are supposed to be used separately as helper functions, they shouldn't be a part of the class, let alone class component. A class acts as namespace in this case. In modern JavaScript, modules are used as namespaces. It can be:

export const coolMethod = () => { /* Do cool stuff */ };

export const coolerMethod = () => { /* Do even cooler stuff */ };

export const boringMethod = () => { /* Do boring but necessary stuff */ };

ScreenContainer component is an example of 'smart' container component. It's always preferable to list passed functions explicitly rather than pass them all automatically. ScreenContainer may get private methods at some point. And there should be a guarantee that lifecycle hooks won't be passed accidentally, too.

If it is supposed to have a single child, it can be applied as higher-order component:

const withScreen(Comp) => {
  return class ScreenContainer extends React.Component {
    ...
    render() {
      return <Comp handleInputChange={this.handleInputChange} /* ... */ />;
    }
  }
}

In this particular case render can be distinguished from passed functions because the latter are instance methods (arrow functions). While this kind of magic generally isn't recommended because it may cause problems and won't work properly for private methods, it can be shortened to:

    render() {
      const fns = {};

      for (const method of Object.keys(this)) {
        if (typeof this[method] === 'function')
          fns[method] = this[method];
      }

      return <Comp {...fns} {...this.props} />;
    }

For multiple children, ScreenContainer children could be traversed to add props in a similar way.

For indirect children, context API can be used to pass functions.

While it's possible to pass ScreenContainer this to children, this isn't recommended because this breaks the encapsulation and contradicts the principle of least privilege.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • This is a good point, but I should have clarified that the "cool stuff" the methods are doing could include manipulating state. I think the comments under the other answer are right - my issue is probably architectural and indicates I should be using Redux or something similar. – ReeseyCup Oct 04 '18 at 19:26
  • Consider updating the question with your actual code (or simplified version of it), the solution may depend on it. It could be solved with HOC or somehow else. Redux is indeed a solid way but isn't the only one. – Estus Flask Oct 04 '18 at 19:32
  • I have edited the question to be closer to my actual code. – ReeseyCup Oct 05 '18 at 14:33
  • I tried to address all concerns. I'm still not sure what's the use case for this container. But of you need to make functions available for all nested component then yes, this should be either Redux or React context. – Estus Flask Oct 05 '18 at 17:36
0

One way I've done this is to instantiate a new instance in the constructor of the child component like this:

class ChildComponent extends Component {
  constructor(props) {
  super(props);
  this.Container = new MyContainer();
}

Then you can use any methods like:

this.Container.coolMethod()

Edit

I misunderstood. I've only done this by creating a helper class that you instantiate, not a component. It is helpful when you have methods you want to use in multiple components without having to pass all your methods as props through the component tree.

seanulus
  • 885
  • 4
  • 8