React HOC can't pass props? - reactjs

I tried to pass description=Button props to Button Component using HOC.
so that, I expected to render like, `Button
But, Empty Button Elements is Rendered!
My codeSandBoxLink:enter link description here
Button.jsx
import React from 'react'
import withLoading from './withLoading'
function Button() {
return <button></button>
}
export default withLoading(Button)
withLoading.jsx
export default function withLoading(Component) {
const WithLoadingComponent = (props) => {
return <Component>{props.description}</Component>
);
};
return WithLoadingComponent;
App.jsx
return(
<div>
<Button description="button"><Button>
</div>
)
Thanks for any help.

At Button compnent, you need to use props and follow your code so that is props.description.
function Button(props) {
return <button>{props.description}</button>;
}
At withLoading HOC, you need to pass all props for Component.
//HOC Example
export default function withLoading(Component) {
const WithLoadingComponent = (props) => {
const [loading, setLoading] = React.useState(true);
console.log("props:", props.description);
//delay 3sec...
React.useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 3000);
return () => clearTimeout(timer);
}, []);
return loading ? <p>loading...</p> : <Component {...props} />;
};
return WithLoadingComponent;
}
I have been fork and then fixed it. You can refer by this link: https://codesandbox.io/s/strange-cerf-glxquu?file=/src/withLoading.jsx

Related

React Refs undefined inside functions and has values outside

I am having a lot of troubles working with react refs, what i want is to use a function declared in another components
the below code is what i am doing:
const Component1 = (props, ref) => {
const getText = () => {};
React.useImperativeHandle(ref, () => ({ getText }));
return <div />;
};
export default React.forwardRef(Component1);
const Component2 = (props) => {
const component1Ref = React.createRef();
const getTextFromComponent1 = () => {
console.log({ component1Ref }); //will be equal to {current:null}
};
console.log({ component1Ref }); //will be equal to {current:{getText}}
return <Component1 ref={component1Ref} />;
};
export default Component2;
It is very weird, the value inside getTextFromComponent1 was the same as outside, it suddenly broke! this happened with me many times
Anyone has a clue of the solution?
Features are breaking without any change
Thanks in advance
Hanan
It will take some time for ref to be initialized. I have shown an example to call it from a click event handler and within a useEffect hook.
Following works without any issues:
const Component1 = React.forwardRef((props, ref) => {
const textRef = React.createRef();
const getText = () => {
return textRef?.current?.value;
};
React.useImperativeHandle(ref, () => ({ getText }));
return <input ref={textRef} defaultValue="sample text" />;
});
const Component2 = (props) => {
const component1Ref = React.createRef();
React.useEffect(() => {
getTextFromComponent1();
}, []);
const getTextFromComponent1 = () => {
console.log(component1Ref.current?.getText());
};
return (
<div>
<button onClick={getTextFromComponent1}>Check text</button>
<br />
<Component1 ref={component1Ref} />
</div>
);
};
export default Component2;
Working Demo

React: Is there a way to access component state from function in another file?

I've a react component which includes a large function that updates the component state, the function is large so I want to move it to a separate file and export it in the react component. But I don't find anyway to access the component state if I move the function to its own file.
Is there anyway to do this ?
example:
component.tsx
import { myFunction } from './function.ts'
const [toggle, setToggle] = useState(false)
const my_component = () => {
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = () => {
// do something that updates `toggle`
}
you can do the logic apart from the component and return the result to the component. have a look at the code below.
https://codesandbox.io/s/hopeful-dubinsky-930p7?file=/src/App.js
This is just a raw example of what you can do with custom state hooks (reference: https://dev.to/spukas/react-hooks-creating-custom-state-hook-300c)
import React from 'react';
export function useMyFunction(value) {
const [toggle, setToggle] = React.useState(value || false);
const myFunction = () => {
// do something that updates `toggle` with setToggle(...)
}
return { toggle, myFunction };
}
import { useMyFunction } from './function.ts'
const my_component = () => {
const [toggle, myFunction] = useMyFunction(false)
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
This can be achieved by 2 different ways one using HOC components and another just by using functions.
Approach 1: Using HOC
handler.js
const withHandlers = (WrappedComponent) => {
class HandlerComponent extends Component {
state = {toggle:false};
myFunction = () => {
//Do your update here
}
render() {
return <WrappedComponent
toggle={this.state.toggle
myFunction={this.myFunction}
/>
}
};
my_component.js
const my_component = (props) => {
return (
<div>
<button onClick={props.myFunction}>Run function</button>
</div>
}
export default withHandlers(my_component);
Approach 2: Using Functions
handler.js
export const myFunction(toggle) => {
return !toggle; //return the changed value
}
my_component.js
const my_component = () => {
const [toggle, setToggle] = useState(false);
const myFunction = () => {
setToggle(handler.myFunction); //the state will be passed as a parameter by default
};
return(
<div>
<button onClick={myFunction}>Run function</button>
</div>
);
};
For the toggle to work, it must be passed to the function as a props then for update it used state management (redux or react context).
The best solution is to define the toggle in the function itself and pass it a Boolean props to control it.
import { myFunction } from './function.ts'
const my_component = () => {
return (
<div>
<button onClick={myFunction(false)}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = (props) => {
const [toggle, setToggle] = useState(props || false);
// your codes
};

How to pass data from child to parent component using react hooks

I have a Parent component and couple of child components. I need to disable or enable the button in the parent based on the ErrorComponent. If there is an error then I disable the button or else I enable it. I believe we can pass callbacks from the child to parent and let the parent know and update the button property. I need to know how to do the same using react hooks? I tried few examples but in vain. There is no event on error component. If there is an error (props.errorMessage) then I need to pass some data to parent so that I can disable the button. Any help is highly appreciated
export const Parent: React.FC<Props> = (props) => {
....
const createContent = (): JSX.Element => {
return (
{<ErrorPanel message={props.errorMessage}/>}
<AnotherComponent/>
);
}
return (
<Button onClick={onSubmit} disabled={}>My Button</Button>
{createContent()}
);
};
export const ErrorPanel: React.FC<Props> = (props) => {
if (props.message) {
return (
<div>{props.message}</div>
);
}
return null;
};
I'd use useEffect hook in this case, to set the disabled state depending on the message props. You can see the whole working app here: codesandbox
ErrorPanel component will look like this:
import React, { useEffect } from "react";
interface IPropTypes {
setDisabled(disabled:boolean): void;
message?: string;
}
const ErrorPanel = ({ setDisabled, message }: IPropTypes) => {
useEffect(() => {
if (message) {
setDisabled(true);
} else {
setDisabled(false);
}
}, [message, setDisabled]);
if (message) {
return <div>Error: {message}</div>;
}
return null;
};
export default ErrorPanel;
So depending on the message prop, whenever it 'exists', I set the disabled prop to true by manipulating the setDisabled function passed by the prop.
And to make this work, Parent component looks like this:
import React, { MouseEvent, useState } from "react";
import ErrorPanel from "./ErrorPanel";
interface IPropTypes {
errorMessage?: string;
}
const Parent = ({ errorMessage }: IPropTypes) => {
const [disabled, setDisabled] = useState(false);
const createContent = () => {
return <ErrorPanel setDisabled={setDisabled} message={errorMessage} />;
};
const handleSubmit = (e: MouseEvent) => {
e.preventDefault();
alert("Submit");
};
return (
<>
<button onClick={handleSubmit} disabled={disabled}>
My Button
</button>
<br />
<br />
{createContent()}
</>
);
};
export default Parent;

createPortal does not overwrite div contents (like ReactDOM.render)

I am trying to get ReactDOM.createPortal to override the contents of the container I am mounting it too. However it seems to appendChild.
Is it possible to override contents? Similar to ReactDOM.render?
Here is my code:
import React from 'react';
import { createPortal } from 'react-dom';
class PrivacyContent extends React.Component {
render() {
return createPortal(
<div>
<button onClick={this.handleClick}>
Click me
</button>
</div>,
document.getElementById('privacy')
)
}
handleClick() {
alert('clicked');
}
}
export default PrivacyContent;
If you know what you're doing, here is a <Portal /> component that under the hoods creates a portal, empties the target DOM node and mounts any component with any props:
const Portal = ({ Component, container, ...props }) => {
const [innerHtmlEmptied, setInnerHtmlEmptied] = React.useState(false)
React.useEffect(() => {
if (!innerHtmlEmptied) {
container.innerHTML = ''
setInnerHtmlEmptied(true)
}
}, [innerHtmlEmptied])
if (!innerHtmlEmptied) return null
return ReactDOM.createPortal(<Component {...props} />, container)
}
Usage:
<Portal Component={MyComponent} container={document.body} {...otherProps} />
This empties the content of document.body, then mounts MyComponent while passing down otherProps.
Hope that helps.
In the constructor of the component, you could actually clear the contents of the div before rendering your Portal content:
class PrivacyContent extends React.Component {
constructor(props) {
super(props);
const myNode = document.getElementById("privacy");
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
}
render() {
return createPortal(
<div>
<button onClick={this.handleClick}>
Click me
</button>
</div>,
document.getElementById('privacy')
)
}
handleClick() {
alert('clicked');
}
}
export default PrivacyContent;
I find this is better and doesn't need useState:
export const Portal = () => {
const el = useRef(document.createElement('div'));
useEffect(() => {
const current = el.current;
// We assume `root` exists with '?'
if (!root?.hasChildNodes()) {
root?.appendChild(current);
}
return () => void root?.removeChild(current);
}, []);
return createPortal(<Cmp />, el.current);
};
Bit of an old question, but here's another sync solution (without useState). Also in a reusable component format.
const Portal = ({ selector, children, replaceContent = true }) => {
const target = useRef(document.querySelector(selector)).current;
const hasMounted = useRef(false);
if (!target) return null;
if (replaceContent && !hasMounted.current) {
target.innerHTML = '';
hasMounted.current = true;
}
return createPortal(children, target);
};
A solution with zero hook dependencies
import { createPortal } from 'react-dom';
const getNode = (id) => {
const domNode = document.getElementById(id);
const div = document.createElement("div");
domNode?.replaceChildren(div);
return div;
};
const Portal = ({ children }) => {
const domNode = getNode("privacy");
if (domNode) {
return createPortal(children, domNode);
}
return null;
};

Can a React portal be used in a Stateless Functional Component (SFC)?

I have used ReactDOM.createPortal inside the render method of a stateful component like so:
class MyComponent extends Component {
...
render() {
return (
<Wrapper>
{ReactDOM.createPortal(<FOO />, 'dom-location')}
</Wrapper>
)
}
}
... but can it also be used by a stateless (functional) component?
Will chime in with an option where you dont want to manually update your index.html and add extra markup, this snippet will dynamically create a div for you, then insert the children.
export const Portal = ({ children, className = 'root-portal', el = 'div' }) => {
const [container] = React.useState(() => {
// This will be executed only on the initial render
// https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
return document.createElement(el);
});
React.useEffect(() => {
container.classList.add(className)
document.body.appendChild(container)
return () => {
document.body.removeChild(container)
}
}, [])
return ReactDOM.createPortal(children, container)
}
It can be done like this for a fixed component:
const MyComponent = () => ReactDOM.createPortal(<FOO/>, 'dom-location')
or, to make the function more flexible, by passing a component prop:
const MyComponent = ({ component }) => ReactDOM.createPortal(component, 'dom-location')
can it also be used by a stateless (functional) component
?
yes.
const Modal = (props) => {
const modalRoot = document.getElementById('myEle');
return ReactDOM.createPortal(props.children, modalRoot,);
}
Inside render :
render() {
const modal = this.state.showModal ? (
<Modal>
<Hello/>
</Modal>
) : null;
return (
<div>
<div id="myEle">
</div>
</div>
);
}
Working codesandbox#demo
TSX version based on #Samuel's answer (React 17, TS 4.1):
// portal.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom'
interface IProps {
className? : string
el? : string
children : React.ReactNode
}
/**
* React portal based on https://stackoverflow.com/a/59154364
* #param children Child elements
* #param className CSS classname
* #param el HTML element to create. default: div
*/
const Portal : React.FC<IProps> = ( { children, className, el = 'div' } : IProps ) => {
const [container] = React.useState(document.createElement(el))
if ( className )
container.classList.add(className)
React.useEffect(() => {
document.body.appendChild(container)
return () => {
document.body.removeChild(container)
}
}, [])
return ReactDOM.createPortal(children, container)
}
export default Portal
IMPORTANT useRef/useState to prevent bugs
It's important that you use useState or useRef to store the element you created via document.createElement because otherwise it gets recreated on every re-render
//This div with id of "overlay-portal" needs to be added to your index.html or for next.js _document.tsx
const modalRoot = document.getElementById("overlay-portal")!;
//we use useRef here to only initialize el once and not recreate it on every rerender, which would cause bugs
const el = useRef(document.createElement("div"));
useEffect(() => {
modalRoot.appendChild(el.current);
return () => {
modalRoot.removeChild(el.current);
};
}, []);
return ReactDOM.createPortal(
<div
onClick={onOutSideClick}
ref={overlayRef}
className={classes.overlay}
>
<div ref={imageRowRef} className={classes.fullScreenImageRow}>
{renderImages()}
</div>
<button onClick={onClose} className={classes.closeButton}>
<Image width={25} height={25} src="/app/close-white.svg" />
</button>
</div>,
el.current
);
Yes, according to docs the main requirements are:
The first argument (child) is any renderable React child, such as an element, string, or fragment. The second argument (container) is a DOM element.
In case of stateless component you can pass element via props and render it via portal.
Hope it will helps.
Portal with SSR (NextJS)
If you are trying to use any of the above with SSR (for example NextJS) you may run into difficulty.
The following should get you what you need. This methods allows for passing in an id/selector to use for the portal which can be helpful in some cases, otherwise it creates a default using __ROOT_PORTAL__.
If it can't find the selector then it will create and attach a div.
NOTE: you could also statically add a div and specify a known id in pages/_document.tsx (or .jsx) if again using NextJS. Pass in that id and it will attempt to find and use it.
import { PropsWithChildren, useEffect, useState, useRef } from 'react';
import { createPortal } from 'react-dom';
export interface IPortal {
selector?: string;
}
const Portal = (props: PropsWithChildren<IPortal>) => {
props = {
selector: '__ROOT_PORTAL__',
...props
};
const { selector, children } = props;
const ref = useRef<Element>()
const [mounted, setMounted] = useState(false);
const selectorPrefixed = '#' + selector.replace(/^#/, '');
useEffect(() => {
ref.current = document.querySelector(selectorPrefixed);
if (!ref.current) {
const div = document.createElement('div');
div.setAttribute('id', selector);
document.body.appendChild(div);
ref.current = div;
}
setMounted(true);
}, [selector]);
return mounted ? createPortal(children, ref.current) : null;
};
export default Portal;
Usage
The below is a quickie example of using the portal. It does NOT take into account position etc. Just something simple to show you usage. Sky is limit from there :)
import React, { useState, CSSProperties } from 'react';
import Portal from './path/to/portal'; // Path to above
const modalStyle: CSSProperties = {
padding: '3rem',
backgroundColor: '#eee',
margin: '0 auto',
width: 400
};
const Home = () => {
const [visible, setVisible] = useState(false);
return (
<>
<p>Hello World <a href="#" onClick={() => setVisible(true)}>Show Modal</a></p>
<Portal>
{visible ? <div style={modalStyle}>Hello Modal! <a href="#" onClick={() => setVisible(false)}>Close</a></div> : null}
</Portal>
</>
);
};
export default Home;
const X = ({ children }) => ReactDOM.createPortal(children, 'dom-location')
Sharing my solution:
// PortalWrapperModal.js
import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
const PortalWrapperModal = ({
children,
onHide,
backdrop = 'static',
focus = true,
keyboard = false,
}) => {
const portalRef = useRef(null);
const handleClose = (e) => {
if (e) e.preventDefault();
if (portalRef.current) $(portalRef.current).modal('hide');
};
useEffect(() => {
if (portalRef.current) {
$(portalRef.current).modal({ backdrop, focus, keyboard });
$(portalRef.current).modal('show');
$(portalRef.current).on('hidden.bs.modal', onHide);
}
}, [onHide, backdrop, focus, keyboard]);
return ReactDOM.createPortal(
<>{children(portalRef, handleClose)}</>,
document.getElementById('modal-root')
);
};
export { PortalWrapperModal };

Resources