Possible to forward a ref using React.Suspense and React.Lazy? - reactjs

I wondering if it would be possible to use React 16's lazy, Suspense, createRef and forwardRef features together in order to attach a reference to a dynamically imported component. Does anyone know if this is possible. I tried to follow the example (https://github.com/facebook/react/pull/13446/commits/4295ad8e216e0747a22eac3eed73c66b153270d4#diff-e7eafbb41b012aba463f5a2f8fc00f65R1614), however it doesn't quite seem to work with dynamically imported components.
I have been able to get similar desired behavior by setting custom props in the parent component, passing it down to the child component and then setting the ref to that prop in the child's render function(Example below). My only issue with this approach is that it may not be the cleanest solution and it may be difficult to keep the implementation consistent across a large team. It will also make it easier when attempting to place the ref on the actual component.
// Working (Undesired implementation)
// Parent.js
const LazyClass = lazy(() => { return import('./Child') };
class Parent extends React.Component {
this.childRef = React.createRef();
render() {
return (
<Suspense fallback={<div>Loading</div>}>
<Child forwardRef={this.childRef} />
</Suspense>
);
}
}
// Child.js
class Child extends React.Component {
render() {
return (<div>I am the Child!</div>);
}
}
export default React.forwardRef(({forwardRef, ...props}/*, ref*/) =>
<Child {...props} ref={forwardRef} />
);
//////////////////////////////////////////////////////
// Desired Implementation with React.forwardRef()
//Parent.js
const LazyClass = lazy(() => { return import('./Child') };
class Parent extends React.Component {
this.childRef = React.createRef();
render() {
return (
<Suspense fallback={<div>Loading</div>}>
<Child ref={this.childRef} />
</Suspense>
);
}
}
// Child.js
class Child extends React.Component {
render() {
return (<div>I am the child</div>);
}
}
export default React.forwardRef((props, ref) =>
<Child { ...props } ref={ref} />
);
Setting the ref directly on a Lazily loaded component always returns null. It would be nice if it returned the value from React.createRef().

The "ref as a props" approach is the smaller one but as you said you have to consider prop collision. The approach you linked is still correct. The test only changed slightly:
Update: Not sure if this was a bug previously but lazy now forwards refs automatically. Just be sure you export a component that can hold a ref.
See in action:
https://codesandbox.io/s/v8wmpvqnk0

Related

get props from children component through hoc

I have a component which is going through an hoc, but i want to get some props of this component inside the hoc. All works fine but i can not find out how to get the props out of this child component into the hoc.
here is the component which is going through the hoc, and that is this 'getAction' props i want to extract in the hoc
class ProjectPage2 extends Component {
render() {
return (
<Project2 getAction="getAction"/>
);
};
};
export default PageHandler(ProjectPage2)
here is the hoc component (imported as PageHandler in the ProjectPage2)
export default (ChildComponent) => {
class ComposedComponent extends Component {
render() {
// here i want to get the 'getAction' props, which is inside this ChildComponent
// because i need to use it into this hoc logic
return <ChildComponent {...this.props} />;
}
}
const mapStateToProps = (state) => {
return {
comments: state.project2
}
};
const loadData = (store) => {
return store.dispatch(getProject2());
};
return {
loadData,
component: connect(mapStateToProps, { getProject2 })(ComposedComponent)
}
};
if some one have an idea it would be great. Thanks.
I think you're very close already. It looks to me like you want the final result to be something like:
<Project2 getAction="getAction" comments={...} />
But what ends up getting rendered is just:
<Project2 getAction="getAction" />
See, the custom props of your HOC are passed to your child component via the child's props. You aren't using those, so the child just completely ignores the HOC. You can fix this with:
class ProjectPage2 extends Component {
render() {
return (
<Project2 getAction="getAction" {...this.props} />
);
};
}
Thank you for the answer. But it s not what i was looking for. I wanted to get into the HOC some children component's props passing through. But i finally get the idea which solved it. In fact it s so simple......
i wanted to pass the "getAction" string into the HOC. But i didn t find any solution to extract it (from the passing through component) there.
The solution is simply to pass it into the export default
class ProjectPage2 extends Component {
render() {
return (
// i was trying to use the component props
// <Project2 getAction="getAction"/>
// but no need
<Project2 />
);
};
};
// pass it into the fonction fix it
export default PageHandler(ProjectPage2, "getAction")
then get it in the HOC
export default (ChildComponent, varAction) => {
class ComposedComponent extends Component {
console.log(varAction) // return getAction

Exporting in React higher order components

function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return LogProps;
}
class FancyButton extends React.Component {
focus() {
// ...
}
// ...
}
// Rather than exporting FancyButton, we export LogProps.
// It will render a FancyButton though.
export default logProps(FancyButton);
I have taken these pieces of code from the react documentation, however i am confused about what actually happens during
export default logProps(FancyButton);
My thoughts are that it probably calls the function logProps which in React is considered a higher order component which in that case it should being with capital letter to avoid ambiguity.
The logProp function defines a class component LogProps, the class component LogProps renders an argument component FancyButton. The class component LogProps is then returned from the function.
import FancyButton from './FancyButton';
const ref = React.createRef();
// The FancyButton component we imported is the LogProps HOC.
// Even though the rendered output will be the same,
// Our ref will point to LogProps instead of the inner FancyButton component!
// This means we can't call e.g. ref.current.focus()
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>;
The returnedcomponent(LogProps, FancyButton) from the function logProps is then imported and instantiated at
<FancyButton
label="Click Me"
handleClick={handleClick}
ref={ref}
/>
Is this correct?
Is this correct?
Almost.
As stated in docs, Higher Order Components:
is a function that takes a component and returns a new component.
HOCs name don't start with capital letter.
There's no need to name the returned component.
To simplify, the HOC function basically just returns a new and enhanced component. That's it.
This will also work:
// camel case
function logProps(WrappedComponent) {
// no name
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} {...this.someEnhancements} />;
}
}
}

React Components - No Inheritance, how can pass ref or use HOC for my BaseComponent

I'm building a Web components library where I have a series of components which ideally inherit / takes advantage of BaseComponent. After some reading, inheritance isn't recommended in React and instead I could use forwardRef? or probably higher-order components? The only thing, I'm not very familiar with this concept and couldn't find good examples, tutorial specific for my case. Hopefully someone can tell me how to approach this, what's best-practice?
One of technique I have in mind for the BaseComponent is leveraging IntersectionObserver to trigger animations. As you can imagine, I don't want to put this logic in multiple places. For the sick of having a basic example, below I simply have a click event listener on the BaseComponent:
class Image extends React.Component {
render() {
return (
<div>
<div>
<img className={className} src={src} alt={alt} />
</div>
</div>
);
}
}
export default Image;
// export default withMagic(Image); ??
class BaseComponent extends React.Component {
withMagic() {
}
componentDidMount() {
{/* ref should be <img> DOM element */}
ref.addEventListener("click", this.handleClick);
}
}
export BaseComponent();
Thanks
HoC probably the better solution
// 2. WrappedComponent
export default WrappedComponent => {
// If u want to deal with class
class NewComponent extends React.Component {
//3
handleClick = () => {
// Your Events
}
render () {
//4
return <WrappedComponent {...this.props} handleClick={this.handleClick} />
}
}
// If u want to deal with functional Component
const NewComponent = props => {
const handleClick = () => // your events
return <WrappedComponent {...props} handleClick={handleClick} />
}
return NewComponent
}
How to use ?
import withClick from 'path/withClick'
const A = props => {
return (
//4
<button onClick={props.handleClick}>Click here</button>
)
}
// 1.
export default withClick(A)
How this magic work ?
U are using withClick method by passing A component as a params
A component is named as WrappedComponent inside withClick function
inside the withClick function, u create a new component with your desired handler, logic or even state, then pass them as a props into WrappedComponent
after that, ur current component will have these handler or logic
If u want to pass params, u can use Higher order Function that returns Higher Order Component like
export default (params1, params2, ...paramsn) => WrappedComponent => {
// remain like the same
}

How to use Higher Order function (or alternative) for wrapping a React component without the component continually unmounting?

I have a React component (Parent) within which there is another componet (Child) that is wrapped using a Higher Order function (HOWrapper). The problem I am having is that each time the Parent component renders, the Child component unmounts and then re-mounts. I need to find a way that prevents the Child component from unmounting, but still continue to wrap it in a component whose name is dynamically assigned. I also want the option to pass additional parameters to the HOWrapper function, that may also be dynamically generated in the render() function of Parent. Any ideas?
Parent component:
import { HOWrapper } from './somepath';
import Child from './someotherpath';
export default class Parent extends Component {
...
render(){
let ChildWrapper = HOWrapper(Child);
return (
<ChildWrapper {...this.props} />
);
}
}
Higher Order function:
export function HOWrapper(WrappedComponent) {
return class Blanket extends Component {
constructor(props) {
super(props);
this.state = {
...
};
}
...
render() {
return (
<WrappedComponent
{...this.props}
/>
);
}
}
}
Because an HOC returns a component, a more typical approach to using them is once when exporting them, not on every render call.
let YourComponent = props => <div />
export default HOC(YourComponent)
Then if you want to do anything dynamic pass new props to it.
render() { return <YourComponent dynamicProps={foo} /> }

reactjs base class for all components

I want to have a base component that all my higher order components extends it. Something along the lines of
<BaseComponent>
<App1>
<... Other Components>
</App1>
</BaseComponent>
where BaseComponent contains things such as
class BaseComponent extends React.Component {
render() {
return (
<Wrapper>
{this.props.children}
<ModalContainer>
</ModalContainer>
<Wrapper>);
}
}
The ultimate goal is to be able to, in any of the Apps pass lets say "message = 'Error'" and a modal dialog will display saying "Error" without having to put the modal component in every single app.
Is this possible? or am I in the realm of unicorns. I read a little about higher order compositions but at first glance, it doesn't seem like that's what i'm looking for.
Component composition via higher-order components is a way to do something like subclassing but with composition instead of inheritance. For example:
function wrapInBaseComponent(Component) {
// Return a new component that renders `Component`
// with all the same props, but also renders some
// other stuff
return (props) => {
const { message, ...otherProps } = props;
return (
<Wrapper>
<Component {...otherProps} />
<ModalContainer message={message}>
</ModalContainer>
</Wrapper>
);
};
}
Then you'd do something like this:
class MyComponent extends React.Component {
// ...
}
const WrappedComponent = wrapInBaseComponent(MyComponent);
Or, if you have ES7 decorators enabled, you can use it as a decorator:
#wrapInBaseComponent
class MyComponent extends React.Component {
// ...
}
This is how things like react-redux and react-dnd work; you don't inherit from ReactReduxBaseComponent or anything like that, you compose your component in a higher-order component that renders it, but adds additional functionality.

Resources