React Recompose call a bound method of an enhaced component - reactjs

Using recompose is it possible to call a bound method of an enhaced component? For instance the onClick on the example below on "SomeOtherComponent"
class BaseComponent extends Component {
constructor (props) {
super(props)
this.myBoundMethod = this._myBoundMethod.bind(this)
}
_myBoundMethod () {
return this.something
}
render () {
return (<h1>{'Example'}</h1>)
}
}
const Enhaced = compose(
/* Any number of HOCs ...
lifecycle,
withProps,
withStateHandlers
*/
)(BaseComponent)
class SomeOtherComponent extends Component {
constructor (props) {
super(props)
this.handleClick = this._handleClick.bind(this)
}
_handleClick () {
console.log(this._enhacedComponent.myBoundMethod())
}
render () {
<div>
<Enhaced ref={(c) => {this._enhacedComponent = c}} />
<button onClick={this.handleClick}>Click</Button>
</div>
}
}
I'm aware of hoistStatics but I don't know how to make it for a bound method.

hoistStatics only hoists static properties, but what you need is instance methods.
Here is a way to achieve what you want in Recompose. First, rename ref callback to, for example, forwardedRef:
<Enhaced fowardedRef={(c) => { this._enhacedComponent = c }} />
Then, use withProps as the last HOC to rename fowardedRef to ref:
const Enhaced = compose(
/* ... other HOCs ... */
withProps(({ fowardedRef }) => ({ ref: fowardedRef }))
)(BaseComponent)
Now, the ref callback is passed to BaseComponent.
The whole running example is here https://codesandbox.io/s/6y0513xpxk

Related

Call child function in parent with i18next react

I used React.createRef() to call child method, like that
import Child from 'child';
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.getAlert();
};
render() {
return (
<div>
<Child ref={this.child} />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
Child class like that
export default class Child extends Component {
getAlert() {
alert('getAlert from Child');
}
render() {
return <h1>Hello</h1>;
}
}
It works well. But when I want to use i18next to translate child component, I have to add withTranslation() to use HOC.
import { useTranslation } from 'react-i18next';
class Child extends Component {
getAlert() {
alert('getAlert from Child');
}
render() {
const { t } = this.props;
return <h1>{t('Hello')}</h1>;
}
}
export default withTranslation()(Child);
Then return error: Function components cannot be given refs.
Means cannot use ref in <Child /> tag. Is there any way to call child function after add i18next?
This is a problem since the withTranslation HOC is using a function component. By wrapping your Child component with a HOC you essentially are placing the ref on the withTranslation component (by default).
There are multiple ways to fix this problem, here are the two easiest:
Using withRef: true >= v10.6.0
React-i18n has a built in option to forward the ref to your own component. You can enable this by using the withRef: true option in the HOC definition:
export default withTranslation({ withRef: true })(Child);
Proxy the ref using a named prop
Instead of using <Child ref={this.child} />, choose a different prop to "forward" the ref to the correct component. One problem though, you want the ref to hold the component instance, so you will need to assign the ref manually in the lifecycle methods.
import Child from 'child';
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.getAlert();
};
render() {
return (
<div>
<Child innerRef={this.child} />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
import { useTranslation } from 'react-i18next';
class Child extends Component {
componentDidMount() {
this.props.innerRef.current = this;
}
componentWillUnmount() {
this.props.innerRef.current = null;
}
getAlert() {
alert('getAlert from Child');
}
render() {
const { t } = this.props;
return <h1>{t('Hello')}</h1>;
}
}
export default withTranslation()(Child);

How to access a ref using React-Redux >= v6.0?

in React-Redux >= v6.0 connnect() options support a new parameter, forwardRef: boolean.
If {forwardRef : true} has been passed to connect, adding a ref to the connected wrapper component will actually return the instance of the wrapped component.
So, in my HoC called 'WithFields' I write:
[...]
import Form from '.../components/form';
const WithFields = (arg1, arg2) => (WrappedComponent) => connect(mapStateToProps, {someMethod}, null, {forwardRef: true})(class extends React.Component {
[...]
render(
return(<WrappedComponent ref={ref => this.wrappedComponent = ref }/>)
)
}
[...]
let Customer = WithFields('a', 'b')(Form);
export default Customer;
Now, in a Ticket component, I would to get the Customer ref with a method, but how?
import Customer from '....';
class Ticket extends Component {
SOME_METHOD_TO_GET_THE_REF_OF_THE_HOC_COMPONENT() {
?????????
}
render() {
[....]
<Customer/>
}
}
Ok, re-checked also the React docs (https://reactjs.org/docs/refs-and-the-dom.html), this is how to implement ref to the wrapped component of an HoC that uses React-Redux for the state management.
hoc.js
[...]
import Form from '.../components/form';
const WithFields = (arg1, arg2) =>
(WrappedComponent) =>
connect(mapStateToProps, {someMethod}, null, {forwardRef: true})(class extends React.Component {
[...]
render(
return(<WrappedComponent ref={ref => this.formComponent = ref }/>)
)
}
[...]
let Customer = WithFields('a', 'b')(Form);
export default Customer;
ticket.js
import Customer from '....';
class Ticket extends Component {
constructor(props) {
super(props);
this.customer = React.createRef();
}
triggerCustomerMethod (e) {
let form = ref.current.formComponent; // THIS IS HOW YOU GET THE <Form/> component wrapped in <Customer/>
// example: get the <Form/> state and use it as argument for Hoc onSubmit() method (that updates the Redux store)
let state = form.state;
form.props.onSubmit(state, e);
};
render() {
[....]
<Customer ref={this.customer}/>
<Button onClick={this.triggerCustomerMethod.bind(this)} text="Save"/> // clicking on button we launch triggerCustomerMethod()
}
}
Pass this function from your Ticket component to the HOC as props
getRef = (refvalue) => { setState({value: refValue})}
and inside the HOC consume props like this
props.getRef(value of ref here)
Ticket component
const Ticket = () => {
const getRef = (value) => {
//do something with the value
}
return <Customer handleRef={getRef}/>
}
Customer Component
const Customer = props => {
OnChange = (refvalue) => {
props.handleRef(refvalue)
}
return {....}
}
For more detailed answer check this How to pass data from child component to its parent in ReactJS?

Pass dynamic value to HOC in react

I write some HOC and I need to pass to this HOC a dynamic object that I create on some life cycle level and I did not get him as a prop.
If I try to pass some static value ( for example initialize myObj from start) it works as expected and I get the correct value.
Let's say this is my component class :
let myObj = {};
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'};
return ( ... )
}
}
export default withHOC(Test, myObj);
And this is my HOC:
const withHOC = (Component, test) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = test; // ---->test is empty object always !!
...
}
}
return Hoc;
}
My 'Dynamic' object that I create on my 'test' class is always empty on my HOC class.
It's happend also when I try to pass some value from my props directly, in this case the page is stuck(without errors in console).
Does someone have any idea how to resolve that? Thanks!
When you compose a component that way, composition only happens at compile time (static composition). This means that withHOC runs only once and is receiving an empty myObj argument, as it is using the one defined on declaration.
export default withHOC(Test, myObj); //myObj = {}
If you want that value to be dynamic, the withHOC composition should be runned when that value changes.
You can't send data up from the WrappedComponent (Test) to the HOC (withHOC), so even if you change myObj value in Test.render, the HOC would never know.
What you could do, if you really need it, is do the composition on the Test.render
render(){
const Hoc = withHOC(this.state.myObj, WrappedComponent);//WrappedComponent can be anything
return(
<Hoc/>
)
}
This way, every time the component renders, Hoc is composed using as myObj a value from the component state, wich is not the preferable way to do it, because this.state.myObj might have the same value as it did at the previous render, and you would be re-composing with the same data as before.
A better way to do it is checking for changes in myObj at Test.componentDidUpdate, and if it did change, then compose Hoc again.
You are passing an empty object to the withHOC function
let myObj = {}; // <- your myObj is empty
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
myObj = {test:'test'}; // <- You're doing this in the render method of your Test component, so until the component is rendered myObj is empty
return ( ... )
}
}
export default withHOC(Test, myObj);
Some explanation about what's happening here, by order:
import Comp from '.../Test.js'
the withHOC function is triggered, with the params of Test (which is defined above the call) and myObj (which is defined above the call but is empty)
Test component is returned, and nobody used the logic of myObj = {test:'test'}
Suggested solution:
Make the HOC get the logic from the props with another hoc:
const withProps = newProps => BaseComponent => props => {
const propsToAdd = typeof newProps === 'function' ? newProps(props) : newProps
return <BaseComponent {...props} {...propsToAdd} />
}
Usage:
class Test extends React.Component
{
constructor(props) {
super(props);
.....
}
render() {
return ( ... )
}
}
export default withProps({test:'test'})(withHOC(Test));
// or: export default withProps(props => {test:'test'})(withHOC(Test));
const withHOC = (Component) => {
class Hoc extends React.Component
{
constructor(props)
{
super(props);
const s = this.props.test;
...
}
}
return Hoc;
}
you can use recompose, a library which has many hocs and utils, and for better readability:
import { compose, withProps } from "recompose"
class Test extends React.Component {...}
const enhance = compose(
withProps({test:'test'}),
withHOC
)
export default enhance(Test);
I can't say with confidence this is optimal but I solved a similar problem by having a function within the HOC that updates state that you can then invoke with any data in the wrapped component.
HOC:
func = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
return ({ <WrappedComponent func={this.func} /> })
Wrapped Component:
this.props.func(anythingA, anythingB);
You can then access the data through state in the HOC.
To elaborate:
const withHOC = (WrappedComponent) => {
class withHOC extends React.Component {
constructor(props) {
super(props)
this.state = {
stateA: 1,
stateB: 2
}
*use state however you want in this HOC, including pass it through to another component*
*the following is just a function*
*when it's invoked in the wrapped component state will update here in the
HOC*
changeState = (a, b) => {
this.setState({
stateA: a,
stateB: b
)}
}
render() {
return (
<div>
<p>this.state.stateA</p>
<p>this.state.stateB</p>
<WrappedComponent changeState={this.changeState} />
</div>
)
}
}
}
}
In wrappedComponent, after importing:
class aComponent extends Component {
constructor(props) {
super(props)
this.state = {
}
*you can now invoke the function from the HOC in this wrapped component*
}
}
You can use react-redux and store your object in redux state. Change the object wherever you need (in your case it's in Test) and access it in component inside your HOC from redux state, it'll be always up to date.

HOC pass through properties

I'm lately struggling with complex HOC and how I can pass through only the new props defined in it and not any other.
More precisely, suppose my HOC makes use of other HOCs which extends its properties, for instance
const withSession = (WrappedComponent) => {
class SessionProvider extends React.PureComponent {
constructor(props) {
super(props);
this.login = this.login.bind(this);
}
login() {
console.log('login'); // this will dispatch some action
// this.props.dispatch...
}
render() {
return (
<WrappedComponent
doLogin={this.login}
{...this.props}
/>
);
}
}
const mapStateToProps = null;
function mapDispatchToProps(dispatch) {
return {
dispatch,
};
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
return compose(
withConnect,
withRouter,
)(injectIntl(SessionProvider));
};
Here the SessionProvider makes use of dispatch and injectIntl which attach properties to its props. However, I don't want to pass those props down to the wrapped component. The idea is to have a SessionProvider HOC which has some API call but only extends the wrapped component with login.
I noticed that if keep {...this.props}, the wrapped component will also get all the props used by the HOC which I don't want to pass through.
So I thought to explicitly define which properties to pass through by decomposing this.props by changing the HOC render method:
render() {
const { dispatch, intl, ...otherProps } = this.props;
return <WrappedComponent doLogin={this.login} { ...otherProps} />;
}
However what happens with this is that if the WrappedComponent itself has dispach or intl props, those are not passed-through the HOC.
Is there anything wrong in what I'm doing? Any better approach? Am I missing anything?
There's nothing wrong in what you're doing. Prop name conflicts is a known issue when using HOCs. So, as far as I can tell, the best alternative you could use is Render Props pattern, which helps to keep components render as declarative as possible. For your case, consider something like this:
class Session extends React.PureComponent {
constructor(props) {
super(props);
this.login = this.login.bind(this);
}
login() {
console.log("login"); // this will dispatch some action
// this.props.dispatch...
}
// ...
render() {
return (
<React.Fragment>
{this.props.children({
doLogin: this.login
doLogout: this.logout
// ...
})}
</React.Fragment>
);
}
}
// ...
return compose(
withConnect,
withRouter
)(injectIntl(Session));
And use it from another components:
// ...
render() {
return (
<Session>
{({ doLogin, doLogout }) => (
<React.Fragment>
<SomeComponent doLogin={doLogin} />
<button onClick={doLogout}>Logout</button>
</React.Fragment>
)}
</Session>
)
}
UPDATE:
There's a pretty promising Hooks Proposal available in v16.7.0-alpha. I'm not quite familiar with them yet, but they tend to solve components reusability more efficiently.
You need to copy static properties, for that i use below code.. you can add more properties as per your need
export const REACT_STATICS = {
childContextTypes: true,
contextTypes: true,
defaultProps: true,
displayName: true,
getDefaultProps: true,
mixins: true,
propTypes: true,
type: true
};
export const KNOWN_STATICS = {
name: true,
length: true,
prototype: true,
caller: true,
arguments: true,
arity: true
};
export function hoistStatics(targetComponent, sourceComponent) {
var keys = Object.getOwnPropertyNames(sourceComponent);
for (var i = 0; i < keys.length; ++i) {
const key = keys[i];
if (!REACT_STATICS[key] && !KNOWN_STATICS[key]) {
try {
targetComponent[key] = sourceComponent[key];
} catch (error) {}
}
}
return targetComponent;
}
// in HOC
const hoistedSessionProvider = hoistStatics(SessionProvider, WrappedComponent);
// use hoistedSessionProvider in compose

React Higher Order Component that detects dom events that takes functional components as arg

I have a scenario where I want to create an HOC that detects mouse events (e.g. mouseenter, mouseleave) when they occur on the HOC's WrappedComponent, then pass the WrappedComponent a special prop (e.g. componentIsHovered). I got this working by using a ref callback to get the wrapped component instance, then adding event listeners to the wrapped instance in my HOC.
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
export default (WrappedComponent) => {
return class DetectHover extends Component {
constructor(props) {
super(props)
this.handleMouseEnter = this.handleMouseEnter.bind(this)
this.handleMouseLeave = this.handleMouseLeave.bind(this)
this.bindListeners = this.bindListeners.bind(this)
this.state = {componentIsHovered: false}
this.wrappedComponent = null
}
componentWillUnmount() {
if (this.wrappedComponent) {
this.wrappedComponent.removeEventListener('mouseenter', this.handleMouseEnter)
this.wrappedComponent.removeEventListener('mouseleave', this.handleMouseLeave)
}
}
handleMouseEnter() {
this.setState({componentIsHovered: true})
}
handleMouseLeave() {
this.setState({componentIsHovered: false})
}
bindListeners(wrappedComponentInstance) {
console.log('wrappedComponentInstance', wrappedComponentInstance)
if (!wrappedComponentInstance) {
return
}
this.wrappedComponent = ReactDOM.findDOMNode(wrappedComponentInstance)
this.wrappedComponent.addEventListener('mouseenter', this.handleMouseEnter)
this.wrappedComponent.addEventListener('mouseleave', this.handleMouseLeave)
}
render() {
const props = Object.assign({}, this.props, {ref: this.bindListeners})
return (
<WrappedComponent
componentIsHovered={this.state.componentIsHovered}
{...props}
/>
)
}
}
}
The problem is that this only seems to work when WrappedComponent is a class component — with functional components the ref is always null. I would just as soon place the WrappedComponent inside <div></div> tags in my HOC and carry out the event detection on that div wrapper, but the problem is that even plain div tags will style the WrappedComponent as a block element, which doesn’t work in my use case where the HOC should work on inline elements, too. Any suggestions are appreciated!
You can pass the css selector and the specific styles you need to the Higher Order Component like this:
import React, {Component} from 'react';
const Hoverable = (WrappedComponent, wrapperClass = '', hoveredStyle=
{}, unhoveredStyle={}) => {
class HoverableComponent extends Component {
constructor(props) {
super(props);
this.state = {
hovered: false,
}
}
onMouseEnter = () => {
this.setState({hovered: true});
};
onMouseLeave = () => {
this.setState({hovered: false});
};
render() {
return(
<div
className={wrapperClass}
onMouseEnter= { this.onMouseEnter }
onMouseLeave= { this.onMouseLeave }
>
<WrappedComponent
{...this.props}
hovered={this.state.hovered}
/>
</div>
);
}
}
return HoverableComponent;
};
export default Hoverable;
And use Fragment instead of div to wrap your component:
class SomeComponent extends React.Component {
render() {
return(
<Fragment>
<h1>My content</h1>
</Fragment>
)
}
And then wrap it like this
const HoverableSomeComponent = Hoverable(SomeComponent, 'css-selector');

Resources