Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"this" in react higher order component

Tags:

I want to move shared functionality from my react components to a higher order component like this:

function withListFunctions(WrappedComponent) {
    return class extends React.Component {
        constructor(props) {
            super(props);
        }
        // my shared functionality
        deleteItem() {
            // Do something, then ...
            this.setState({itemDeleted: true});
        }
        render() {
            return (
                <WrappedComponent
                    deleteItem={this.deleteItem}
                 />
            );
        }
    }

Using this kind of syntax requires to explicitely bind this in the constructor of the HOC:

this.deleteItem = this.deleteItem.bind(this);

.. but I wanted to bind the wrapped component instead. So what I tried in my wrapped component's constructor was

this.props.deleteItem = this.props.deleteItem.bind(this);

But this just resulted in a "Cannot assign to read only property" Error, as react props are meant to be read only.

I am aware that I could store the state item in the HOC and pass it down as a prop. But it would then be not accessible(writeable) anymore by other wrapped components functions, right? I wonder if there is a way to just share the unbound function and then bind it to the wrapped instance.

[Edit] I marked Abdul Rauf's answer as "accepted", nevertheless I want to state that Karen Grigoryan's answer is the solution I am actually using because it works and seems just appropiate for my app's complexity.

like image 839
Benni Avatar asked Nov 25 '18 10:11

Benni


2 Answers

I am aware that I could store the state item in the HOC and pass it down as a prop. But it would then be not accessible(writeable) anymore by other wrapped components functions, right?

You should store shared state in HOC. You can pass multiple state update methods to Wrapped components that they can call internally to update state indirectly.

If you don't want to pass multiple state update methods then we have 2 options:

Option 1: Create a single dispatch method in HOC that takes action and optional payload and pass it to Wrapped components.

// Do not mutate state in reducer. always return a new state
reducer(state, action) {
  switch (action.type) {
    case 'delete':
      // return final state after delete
    case 'add':
      // return final state after add using action.payload
    case 'update':
      // return final state after update using action.payload
    default:
      // A reducer must always return a valid state.
      // Alternatively you can throw an error if an invalid action is dispatched.
      return state;
  }
}


dispatch(action) {
    const updatedState = this.reducer(this.state, action)
    this.setState(updatedState);
}

Option 2: Use useReducer hook if you can use latest react with hooks support.

I wonder if there is a way to just share the unbound function and then bind it to the wrapped instance.

Technically you can do that (Check Karen Grigoryan's answer). But it is considered a bad practice because of many reasons. Few of those are:

  1. Its against encapsulation principles. (State is in Child component and state update logic is in parent component). Parent component shouldn't know anything about state of child components.

  2. Props can change over time but this won't be reflected automatically in derived Instance properties/fields

like image 123
Abdul Rauf Avatar answered Oct 04 '22 23:10

Abdul Rauf


props are read only by design.

React elements are immutable. Once you create an element, you can’t change its children or attributes. An element is like a single frame in a movie: it represents the UI at a certain point in time.

So technically one of the ways to bind deleteItem to WrappedComponent context, is to just bind it in WrappedComponent constructor:

codesandbox example

this.deleteItem = this.props.deleteItem.bind(this);

like image 32
Karen Grigoryan Avatar answered Oct 05 '22 01:10

Karen Grigoryan