Ad

How To Delegate Calculation To Child Elements On React

- 1 answer

I have a Stateless componenet which has many stateless childs. In order to decouple the parent calculations I wanted to delegate responsibilities to a dynamic number of childs as for example calculate their area. This way the parent can iterate over his childs and ask them for their respective area and sum for obtaining its total area.

I have read tons of docs about doing this, but they oftenly mislead me about how to implement this.

I know that refs provide a way to do this but the official docs say that if I'm using refs I might have something bad on my data flow or data model.

Is it possible to instantiate a react component ie called Shape as

class Canvas extends React.Component {
  constructor(props) {
    super(props)
    shapesArray.push(new Shape(props));
  }
  ...
}

in its parent constructor method in order to hold a reference?

Does someone has the answer to this. This case is a very basic class interaction (or data modeling) problem which seems to be badly documented on React.

Ad

Answer

[..] I wanted to delegate responsibilities to the childs as for example calculate they area. This way the parent can iterate over his childs and ask them for their respective area and sum for obtaining its total area.

This doesn't fit React's conceptual model. If the parent component needs to know the value of the area, and it already has everything it needs to know in order to calculate the area, then the parent component should perform the calculation.

The only time the child component would pass back information up the component hierarchy is if it holds information that the parent component doesn't. The typical case being user input.

UPDATE

(TL;DR - Beware, the following code snippets are example of how NOT to do it.)

This question has generated a lot of discussion. But I fear my explanations have not been very convincing. So let me demonstrate by way of example.

Say you have a shopping cart. The checkout page has a list of items, each with a unit price and quantity. The item's price is derived from the unit price and quantity.

The render method might look like this:

render() {

    return (
        <div>
            {this.props.products.map((product) => (
                <ProductItem
                    productCode={product.code}
                    unitPrice={product.unitPrice}
                    quantity={product.quantity}
                />
            ))}
        </div>
    )
}

But you also want to display the sum total of items. Since each individual price is determined by the ProductItem, one possibility is to expose this price as a method.

render() {

    let total = 0;

    return (
        <div>
            <div>
                {this.props.products.map((product) => {
                    let productItem = (
                        <ProductItem
                            productCode={product.code}
                            unitPrice={product.unitPrice}
                            quantity={product.quantity}
                        />
                    );
                    total += productItem.price();
                    return productItem;
                })}
            </div>
            <div>Total: ${total}</div>
        </div>
    )

}

If that looks clunky, well that's because React components aren't designed to be used in this way.

But it's worse than that. The parent component is now far too dependent on its child component. Say you wanted an option to hide the list of products, whilst still showing the total price. A naive solution would look like this:

render() {

    let total = 0;
    const showList = this.state.showList;

    return (
        <div>
            {showList ? (
                <div>
                    {this.props.products.map((product) => {
                        let productItem = (
                            <ProductItem
                                productCode={product.code}
                                unitPrice={product.unitPrice}
                                quantity={product.quantity}
                            />
                        );
                        total += productItem.price();
                        return productItem;
                    })}
                </div>
            )}
            <div>Total: ${total}</div>
        </div>
    )

}

Now whenever the products are hidden, the total is 0. A better solution would be:

render() {

    let total = 0;
    const showList = this.state.showList;

    return (
        <div>
            <div>
                {this.props.products.map((product) => {
                    let productItem = (
                        <ProductItem
                            productCode={product.code}
                            unitPrice={product.unitPrice}
                            quantity={product.quantity}
                        />
                    );
                    total += productItem.price();
                    return showList ? productItem : null;
                })}
            </div>
            <div>Total: ${total}</div>
        </div>
    )

}

That works. But when your list is hidden, you're creating and destroying the ProductItem solely for the purpose of a single calculation. That's a pretty clear indication that the logic doesn't belong there.

Remember React components are views, not models.

There are several possible solutions:

  • Have the parent class and the ProductItem class to each individually calculate the item price. However, I advise caution. If the price is simply the quantity multiplied by the unit price this might be OK. If it's more complicated than that (say involving bulk discounts, or a server request) then you don't want to be duplicating your logic.

  • Calculate the item price in the parent class, and tell the ProductItem what its price is (i.e. pass it as a prop).

  • Export the price calculation to a place outside the view layer. That way the parent component and the ProductItem component could both derive the price in the same manner from the same data.

  • Finally, if you want to write your own model classes, you are free to do that as well. React is not particularly opinionated about how the application is structured outside of the view layer.

Ad
source: stackoverflow.com
Ad