I want wrapping module for multi use.
so, I make an ItemComponent
export const DragItem = (props: DragProps) => {
const [{... }, fooRef] = useFoo({
})
return (
props.children // how can i send fooRef to here??
)
}
I should send ref to props.children
Is it possible?
check this : https://codesandbox.io/s/gracious-williams-ywv9m
You need to use React.cloneElement to attach/pass extra data to children
export const DragItem = (props: DragProps) => {
const [foo, fooRef] = React.useState({});
var childrenWithRef = React.Children.map(props.children, function(child) {
return React.cloneElement(child, { fooRef: fooRef });
});
return childrenWithRef;
};
I got it.
export const DragItem = (props: DragProps) => {
const [{... }, fooRef] = useFoo({
})
return (
React.cloneElement(props.children, { ref: fooRef })
)
}
Related
I have created a higher order component as shown below:
import React from 'react';
interface IVisibility {
Component: JSX.Element;
visibilityThreshold?: number;
onVisibleCallback?: () => void;
}
const VisibilityHandler = ({
Component,
visibilityThreshold,
onVisibleCallback
}: IVisibility) => {
const ref = React.useRef(null);
React.useEffect(() => {
const componentObserver = new IntersectionObserver(
(entries) => {
const [entry] = entries;
if (entry.isIntersecting) {
onVisibleCallback ? onVisibleCallback() : null;
}
},
{
rootMargin: '0px',
threshold: visibilityThreshold ?? 0
}
);
const current = ref.current;
if (current) componentObserver.observe(current);
return () => {
componentObserver.disconnect();
};
}, [visibilityThreshold, onVisibleCallback]);
return <section ref={ref}>{Component}</section>;
};
export default VisibilityHandler;
And use it like this:
<VisibilityHandler Component={<div>Hello World</div>} />
However this wraps every component into a section which I don't want. I tried using React.Fragment but that doesn't let you pass ref to track the component. Is there a better way to re-create this HOC in order to incorporate visibility tracking without wrapping it in additional div or section?
You can use
function as a children
React.cloneElement
Function as a children
<VisibilityHandler Component={({ ref }) => <div ref={ref}>Hello world</div>} />
You have to change you HOC code
- return <section ref={ref}>{Component}</section>;
+ return Component({ ref });
React.cloneElement
Documentation
your case
- return <section ref={ref}>{Component}</section>;
+ return React.cloneElement(Component, { ref });
But I highly recommend use hook (packages) instead of HOC.
react-use: useIntersection
react-intersection-observer
I found a really neat way to do so like this:
import React from 'react';
interface IVisibility {
Component: JSX.Element;
visibilityThreshold?: number;
onVisibleCallback?: () => void;
}
const VisibilityHandler = ({
Component,
visibilityThreshold,
onVisibleCallback
}: IVisibility): JSX.Element => {
const ref = React.useRef(null);
React.useEffect(() => {
const componentObserver = new IntersectionObserver(
(entries) => {
const [entry] = entries;
if (entry.isIntersecting) {
onVisibleCallback ? onVisibleCallback() : null;
}
},
{
rootMargin: '0px',
threshold: visibilityThreshold ?? 0
}
);
const current = ref.current;
if (current) componentObserver.observe(current);
return () => {
componentObserver.disconnect();
};
}, [visibilityThreshold, onVisibleCallback]);
return <Component.type {...Component.props} ref={ref} />;
};
export default VisibilityHandler;
Is it possible to call a function of a child component on a direct way without creating a 'helper'-function on the top-level?
Can you explain me the right way to pass this problem?
export const AppComponent = () => {
return (
<ParentDiv>
<ChildDiv />
<ParentDiv />
);
}
const ParentDiv = (props) => {
return (<div>Parent{props.children}<button>do something...</button></div>);
}
const ChildDiv = (props) => {
const submitButton = () => {
console.log('do something...');
}
return (<div>Child</div>);
}
Here's many similar questions but I still couldn't solve this problem.
Child's ref is null in Listner.
I really don't understand what this is.
The code is below.
react 17.0.1
// Parent.tsx
const Parent: React.FC<{id: string}> = (props) => {
const [id] = useState(props.id)
const modalRef = createRef<ModalRef>();
// If I registerd the Listner here, modalRef is not null but,
// multiple Listner has registered.
useEffect(() => {
listner.on('MODAL_POPUP', (o:{param:string}) => {
modalRef.current?.pop(o.param); // <--- modalRef.current is null
});
return() => {};
}, []);
return (
<Modal ref={modalRef} id={id}>
<div>contents</div>
</Modal>
);
};
// Modal.tsx
export interface ModalProps {
id: string;
}
export interface ModalRef {
pop: () => void;
}
const Modal = React.forwardRef<ModalRef, ModalProps>((props, ref) => {
const [id] = useState(props.id);
useImperativeHandle(ref, () => ({
pop() {
console.log('popup modal');
},
}));
return createPotal(
<div>contents..</div>,
document.getElementById('modal-root') as HTMLElement,
);
});
Any advice for me?
Thanks.
You need to use useRef for creating the ref in React Function Components, so change it to this:
const Parent: React.FC<{id: string}> = (props) => {
const [id] = useState(props.id)
const modalRef = useRef<ModalRef>(); // <== here
useEffect(() => {
listner.on('MODAL_POPUP', (o:{param:string}) => {
modalRef.current?.pop(o.param);
});
return() => {
listner.off('MODAL_POPUP', ()=>{});
};
}, []);
return (
<Modal ref={modalRef} id={id}>
<div>contents</div>
</Modal>
);
};
I have the following component that decides whether or not a component should be rendered based upon user permissions:
const Can = forwardRef(({ requiredPermissions, children }, ref): null | JSX.Element => {
const {
user: { role },
userPermissions,
} = useAuth();
return userHasPermission(role, userPermissions, requiredPermissions) ? children : null;
});
export default Can;
What I need to do is pass the ref to the child component that will be inside Can when the component is used.
How can I achieve that?
Yes you can. But you need to do two additional things:
You must be sure that only one child is being passed to the component. You can check it with the React.Children.only method.
Use React.cloneElement to pass ref to the child.
const Can = forwardRef(({ requiredPermissions, children }, ref): null | JSX.Element => {
const {
user: { role },
userPermissions,
} = useAuth();
return userHasPermission(role, userPermissions, requiredPermissions)
? React.cloneElement(React.Children.only(children), {
ref,
})
: null;
});
export default Can;
Use React.Children and React.cloneElement to pass the ref props to all child elements of Can.
Like this:
const Can = forwardRef(
({ requiredPermissions, children }, ref): null | JSX.Element => {
const {
user: { role },
userPermissions,
} = useAuth();
const childrenWithProps = () =>
React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return (
<React.Fragment>
{React.cloneElement(child, {
ref,
})}
</React.Fragment>
);
}
return child;
}) as React.ReactElement;
return userHasPermission(role, userPermissions, requiredPermissions)
? childrenWithProps()
: null;
}
);
export default Can;
I want to assign props dynamically in the children of a component
Ex: I want to check if the component passed in children here is of type Second, and in that case, add a "prova" prop
export const First: React.FC<PropsWithChildren<{}>> = ({ children }) => {
const modifiedChildren = useMemo(
() =>
React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
if(child.type === 'Second')
return React.cloneElement(child, { ...child.props, prova: "ciao" });
}
}),
[children]
);
return <>{modifiedChildren}</>;
};
The "type" property on the child doesnt seem to return the name as it does in Class components (as from what I've seen in other people examples), how am I supposed to do this the correct way ?
// main
export const MainPage = () => {
return (
<First>
<Second></Second>
</First>
);
};
// second
export const Second: React.FC<{ prova?: string }> = ({ prova }) => {
return <div>{prova}</div>;
};
I just realizeed I can just do child.type === Second (second being the functional component/the function itself)
const Second: React.FC<{ prova?: string }> = ({ prova }) => {
return <div>{prova}</div>;
};
React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
if (child.type === Second) {
return React.cloneElement(child, { ...child.props, prova: "bruh" });
}
}
}),