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.
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:
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.
Props can change over time but this won't be reflected automatically in derived Instance properties/fields
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With