I have a small presentation component like this:
function MyList({data, uppercaseMe, lowercaseMe}) {
return <ul>
{data.map(item =>
<li>{item} -
<button onClick={() => uppercaseMe(item)}>Uppercase me!</button>
<button onClick={() => lowercaseMe(item)}>Lowercase me!</button>
</li>)}
</ul>;
}
And then I have three HOCs I would like to use to decorate MyList:
const WithData = (Component) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state= {data:['one', 'two', 'three', 'four', 'five']};
};
render() {
return <Component {...this.props} data={this.state.data} />;
}
}
};
const WithUppercase = (Component) => {
return class extends React.Component {
uppercaseMe = (item) => {
// how to access WithData state from here?
console.log(item.toUpperCase());
};
constructor(props) {
super(props);
};
render() {
return <Component {...this.props} uppercaseMe={this.uppercaseMe}/>;
}
}
};
const WithLowercase = (Component) => {
return class extends React.Component {
lowercaseMe = (item) => {
// how to access WithData state from here?
console.log(item.toLowerCase());
};
constructor(props) {
super(props);
};
render() {
return <Component {...this.props} lowercaseMe={this.lowercaseMe}/>;
}
}
};
Like this:
const ComposedList = WithLowercase(WithUppercase(WithData(MyList)));
Unfortunately, I don't know how to change the state of WithData from within WithLowercase or WithUppercase.
How do you expose the state of one HOC to the other HOCS in the chain?
We can invoke the HOC as follows: const SimpleHOC = higherOrderComponent(MyComponent); A higher-order component transforms a component into another component. Note that a HOC doesn't modify the input component.
Higher-Order Components enable us to apply functional programming principles on components by embracing composition. React Hooks, in contrast, transformed pure (in the sense of functional programming) function components to stateful/side-effect burdened beasts. Anyway, both have their right to exist.
A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React's compositional nature. Concretely, a higher-order component is a function that takes a component and returns a new component.
You don't want to expose the state directly. What you do want to do is pass down a function as a prop to the wrapped component that will allow you to update state.
So if we take the HOC and add a little bit of code, creating a function to update its state that can now be passed down to its child components:
const WithData = (Component) => {
return class extends React.Component {
constructor(props) {
super(props);
this.state= {data:['one', 'two', 'three', 'four', 'five']};
}
update (data) {
this.setState({ data });
}
render() {
return <Component onUpdate={this.update} {...this.props} data={this.state.data} />;
}
}
};
Now within one of the child components we can consume it:
const WithUppercase = (Component) => {
return class extends React.Component {
uppercaseMe = (item) => {
const itemIndex = this.props.data.indexOf(item);
const newData = this.props.data.slice();
newData[itemIndex] = item.toUpperCase();
this.props.onUpdate(newData); // update WithData state here
};
constructor(props) {
super(props);
};
render() {
// onUpdate function is once again passed to child components
// implicitly through props if you need to call it again
return <Component {...this.props} uppercaseMe={this.uppercaseMe}/>;
}
}
};
Passing data back up the chain of components essentially goes against the pattern that react uses, which is one way data flow. Data goes down but never goes back up. Like @FuriousD said you should make the lowerCase and upperCase methods available to components further down the chain. You can do this using context.
"In some cases, you want to pass data through the component tree without having to pass the props down manually at every level. You can do this directly in React with the powerful "context" API."
Here is an example using an updated version of your code.
const WithUpperCase = (Component) => {
class _WithUpperCase extends React.Component {
getChildContext() {
return {
uppercaseMe: this.uppercaseMe,
}
}
uppercaseMe = (item) => {
return item.toUpperCase()
};
render() {
return <Component {...this.props} />;
}
}
_WithUpperCase.childContextTypes = {
lowercaseMe: PropTypes.func,
}
return _WithUpperCase;
};
const WithLowercase = (Component) => {
class _WithLowercase extends React.Component {
getChildContext() {
return {
lowercaseMe: this.lowercaseMe,
}
}
lowercaseMe = (item) => {
return item.toLowerCase()
};
render() {
return <Component {...this.props} />;
}
}
_WithLowercase.childContextTypes = {
lowercaseMe: PropTypes.func,
}
return _WithLowercase;
};
function MyList({ data }, { uppercaseMe, lowercaseMe }) {
return (
<ul>
{
data.map(item => (
<li>{item} -
<button onClick={() => uppercaseMe(item)}>Uppercase me!</button>
<button onClick={() => lowercaseMe(item)}>Lowercase me!</button>
</li>
))
}
</ul>
);
}
MyList.contextTypes = {
// Need to import the PropTypes library
uppercaseMe: PropTypes.func,
lowercaseMe: PropTypes.func,
};
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