How to mount child component outside parent using React? - javascript

I know that there is React portals that may solves the problem, but portals mount child components outside of DOM tree if I understand it correctly. But I need to render child component inside DOM tree but just outside the parent. Here the example.
I have a page:
const Page = () => {
return (
<>
// -> the place to mount <Child_2/> <--
<Parent/>
</>)
I have the Parent:
const Parent = () => {
return (
<>
<Child_1/>
<Child_2/> //<- I need it NOT to mount here but outside the parent
// in the <Page> component and not outside the DOM.
</>
)
How can I do it? And yet can I make it by portal?
In React doc I found an example just for case:
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
But it is not my case...

const Page = ()=> {
const [el, setEl] = React.useState(null);
return (
<>
<div ref={setEl}></div>
{el && <Parent el={el} />}
</>
)
}
const Parent = ({el})=> {
React.useEffect();
return (
<>
<Child_1/>
{ReactDOM.createPortal(<Child2 />,el)}
</>
)
}

Related

Adding a prop to a specific React element type in any level of the DOM tree

I'm trying to add a "position" prop to the 'Child' components and use it inside the 'Parent' component, So the only purpose of the 'PositionWrapper' is to add one prop and return the new children.
The problem is that when I call props.children inside 'Parent' I get a 'PositionWrapper' component instead of 'Child' components as I want.
I know that I can call props.children.props.children and I will get the 'Child' components but this solution doesn't look like a dynamic one (What if I remove the 'PositionWrraper'? Or what if add more wrappers?)
Does anyone know an optimal/ a better solution?
(or Am I implementing the 'PositionWraaper' correctly? )
Thanks!
The code:
Child.js:
const Child = (props) => {
return (
<>
<p>my id is :{ props.id}</p>
<p>my position is : {props.position} </p>
</>
)
}
export default Child;
PositionWrapper.js :
import React from "react"
const PositionWrapper = (props) => {
return (
React.Children.toArray(props.children).map((child)=> React.cloneElement(child, { position: [0,0,0]}))
)
}
export default PositionWrapper;
Parent.js:
import React from "react";
const Parent = ( props) => {
// here I want to do things with children components of type 'Child' but props.children consists 'Wrapper' Component.
return (
props.children
)
}
export default Parent;
App.js :
import './App.css';
import PositionWrapper from './Wrapper'
import Child from './Child';
import Parent from './Parent'
function App() {
return (
<Parent>
<PositionWrapper>
<Child id ={1} />
<Child id ={2} />
</PositionWrapper>
</Parent>
);
}
export default App;
Why dont you pass position prop to Parent - since you want to use it in Parent ?
Anyway, uou should use High-Order-Component for this scenario.
Check: https://reactjs.org/docs/higher-order-components.html
You can do a complete virtual DOM tree search to pass the props only to a specific type like below. This is a simple recursive function doing depth-first traversal.
We process the children of the child and pass it as the third argument of React.cloneElement.
React.cloneElement( element, [config], [...children] )
const Child = (props) => {
return (
<React.Fragment>
<p>my id is :{props.id}</p>
<p>my position is : {props.position} </p>
</React.Fragment>
);
};
const PositionWrapper = (props) => {
return React.Children.toArray(props.children).map((child) =>
React.cloneElement(child)
);
};
const Parent = (props) => {
// here I want to do things with children components of type 'Child' but props.children consists 'Wrapper' Component.
return passPositionPropToChildren(props.children, [1, 2, 3]);
};
const passPositionPropToChildren = (children, position) => {
return React.Children.toArray(children).map((child) => {
// Process the childrens first - depth first traversal
const childrenOfChildren =
child.props && child.props.children
? passPositionPropToChildren(child.props.children, position)
: null;
return React.isValidElement(child)
? React.cloneElement(
child,
child.type.name === "Child"
? {
...child.props,
position: position
}
: child.props,
childrenOfChildren
)
: child;
});
};
function App() {
return (
<Parent>
abc
<h2>hey</h2>
<PositionWrapper>
<Child id={1} />
<Child id={2} />
<div>
<Child id={3} />
</div>
</PositionWrapper>
<Child id={4} />
</Parent>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>

Passing React State

I have a modal that I want to appear on a button click. This works fine, however, in order for it to be positioned correctly, I need to move the modal component to be rendered in a parent component.
I'm unsure how to do this as I'm not sure how I would pass the state down to the component where the button click is created. I'm also unsure of whether this is about passing a state or using a button onClick outside the component the state is defined in.
My code works and does what I want, but, I need <DeleteWarehouseModal show={show} /> to be in the parent component below. How can I do this?
The parent component where I want to render my modal:
function WarehouseComponent(props) {
return (
<section>
<div>
<WarehouseListItems warehouseData={props.warehouseData} />
//Modal component should go here
</div>
</section>
);
}
The component (WarehouseListItems) where the modal is currently being rendered:
function WarehouseListItem(props) {
const [show, setShow] = useState(false);
return (
<>
//some code necessary to the project, but, irrelevant to this issue
<Link>
<div onClick={() => setShow(true)}></div>
</Link>
<DeleteWarehouseModal show={show} />
</>
);
}
The modal:
const DeleteWarehouseModal = (props) => {
if (!props.show) {
return null;
}
return (
//some elements here
);
};
Yes, you can move the state and it's handler in WarehouseComponent component and pass the handler down to child component, which can change the state in parent component.
WarehouseComponent :-
function WarehouseComponent(props) {
const [show, setShow] = useState(false);
const toggleShow = () => {
setShow(state => !state);
}
return (
<section>
<div>
<WarehouseListItems
warehouseData={props.warehouseData}
toggleShow={toggleShow}
/>
<DeleteWarehouseModal show={show} />
</div>
</section>
);
}
WarehouseListItems : -
function WarehouseListItem(props) {
const {toggleShow} = props;
return (
<>
//some code necessary to the project, but, irrelevant to this issue
<Link>
<div onClick={() => toggleShow()}></div>
</Link>
</>
);
}

getBoundingClientRect() on two React components and check if they overlap onScroll

I want to get a ref, more specifically a getBoundingClientRect() on the <Header/> and <Testimonials/> component. I then want to watch for a scroll event and check if the two components ever overlap. Currently, my overlap variable never flips to true even if what appears on the page is that the two components are overlaping.
const [isIntersecting, setIsIntersecting] = useState(false)
const header = useRef(null)
const testimonials = useRef(null)
const scrollHandler = _ => {
let headerRect = header.current.getBoundingClientRect();
let testiRect = testimonials.current.getBoundingClientRect();
let overlap = !(headerRect.right < testiRect.left ||
headerRect.left > testiRect.right ||
headerRect.bottom < testiRect.top ||
headerRect.top > testiRect.bottom)
console.log(overlap) // never flips to true
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler, true);
return () => {
window.removeEventListener("scroll", scrollHandler, true);
};
}, []);
const App = () => {
return (
<div className="App">
<Header />
<LandingPage />
<div style={{ height: '100vh', backgroundColor: 'black', color: 'white' }}>
</div>
<AboutPage />
<TestimonialsPage />
<Footer />
</div>
);
}
First: Components can't receive directly a ref prop, unless you are wrapping the Component itself in a React.forwardRef wrapper:
const Component = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// Inside your Parent Component:
const ref = useRef();
<Component ref={ref}>Click me!</Component>;
Second: you can also pass a ref down to a child as a standard prop, but you can't call that prop ref since that's a special reserved word just like the key prop:
const Component= (props) => (
<button ref={props.myRef}>
{props.children}
</button>
);
// Inside your Parent Component
const ref = useRef();
<Component myRef={ref}>Click me!</Component>;
This works perfectly fine, and if it's a your personal project you
might work like this with no issues, the only downside is that you
have to use custom prop name for those refs, so the code gets harder to
read and to mantain, especially if it's a shared repo.
Third: Now that you learnt how to gain access to the DOM node of a child Component from its parent, you must know that even if usually it's safe to perform manipulations on those nodes inside a useEffect ( or a componentDidMount ) since they are executed once the DOM has rendered, to be 100% sure you will have access to the right DOM node it's always better using a callback as a ref like this:
const handleRef = (node) => {
if (node) //do something with node
};
<Component ref={handleRef}/>
Basically your function hanldeRef will be called by React during
DOM node render by passing the node itself as its first parameter,
this way you can perform a safe check on the node, and be sure it's
100% valorized when you are going to perform your DOM manipulation.
Concerning your specific question about how to access the getBoundingClientRect of a child Component DOM node, I made a working example with both the approaches:
https://stackblitz.com/edit/react-pqujuz
You'll need to define each of your components as Forwarding Refs, eg
const Header = forwardRef<HTMLElement>((_, ref) => (
<header ref={ref}>
<h1>I am the header</h1>
</header>
));
You can then pass a HTMLElement ref to your components to refer to later
const headerRef = useRef<HTMLElement>(null);
const scrollHandler = () => {
console.log("header position", headerRef.current?.getBoundingClientRect());
};
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
window.removeEventListener("scroll", scrollHandler);
};
}, []);
return (
<Header ref={headerRef} />
);
I'm using TypeScript examples since it's easier to translate back down to JS than it is to go up to TS

Is there a way to pass data to props.children in a functional component

I am working a project and want to be able to send data from a component to its props.children component. I think this is done using render props in class components but I don't know how to implement it in a functional one. Here is an extremely simplified version of something I am trying to accomplish. I want to render different things in the Child component depending on what "edit" is set to in Parent.
function Parent({ children }) {
const [edit, setEdit] = useState(false);
return (
<div>
{"blah blah blah..."}
<button onClick={()=>{setEdit(!edit)}}>Click</button>
{children}
</div>
);
}
function Child() {
if (edit === true) {
return (
<Parent>
{"Edit is True"}
</Parent>
);
}
return (
<Parent>
{"Edit is False"}
</Parent>
);
}
You have to generate children components by cloning elements manually.
How to pass props to {this.props.children}
Is this what you need? You can share states to children
const Parent = () => {
const [edit, setEdit] = useState(true);
return (
<>
<div>Hello</div>
<Child edit={edit} />
</>
);
};
const Child = params => {
return params.edit? (
<div>Do something if true</div>
):
(
<div>Do something if false</div>
)
};

how to access a component style in react js

I have a div tag I want to render only if renderCard() style overflow is scroll. I have tried a renderCard().style.overflow which does not seem to target this
Edit: renderCard added
const SearchCard = () => (
<button class="invisible-button" onClick={onSearchCardClick}>
//
</button>
);
const AnswerCard = () => (
<div className="results-set">
//
</div>
);
const renderCard = () => {
if (card && card.answer) {
return AnswerCard();
} else if (card) {
return SearchCard();
}
return null;
};
<React.Fragment>
<div id="search-results">{renderCard()}</div>
{renderFollowup ? null : (
<React.Fragment>
<div id="search-footer">
{
(renderCard().style.overflow = "scroll" ? (
<div className="scroll-button">
<a href="#bottomSection">
<img src="images/arrow_down.svg" alt="scroll to bottom" />
</a>
</div>
) : null)
}
</div>
</React.Fragment>
)}
</React.Fragment>
);
};
To do this you need a ref to the actual DOM element rendered by renderCard().
renderCard() here returns a React element which doesn't have the style property or any other DOM properties on it - it's just a React representation of what the DOM element will eventually be once rendered - hence you need to get the actual DOM element via a ref where you'll have access to this and other properties.
Example code below using useRef to create the ref that will be attached to the element with the style you need to access. Note how useEffect is used to access the ref's value because it's only available after the first render when the DOM element is present.
const Example = () => {
const ref = React.useRef()
React.useEffect(() => {
alert('overflow value is: ' + ref.current.style.overflow)
}, [])
return (
<div ref={ref} style={{ overflow: 'scroll' }}>hello world</div>
)
}
ReactDOM.render(<Example />, document.getElementById('root'))
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Refactor the cards to be actual rendered components, pass the ref and attach to the elements
const SearchCard = ({ overflowRef }) => (
<button ref={overflowRef} class="invisible-button" onClick={onSearchCardClick}>
//
</button>
);
const AnswerCard = ({ overflowRef }) => (
<div ref={overflowRef} className="results-set">
//
</div>
);
const RenderCard = ({ overflowRef }) => {
if (card && card.answer) {
return <AnswerCard overflowRef={overflowRef} />;
} else if (card) {
return <SearchCard overflowRef={overflowRef} />;
}
return null;
};
In the component rendering them, create a ref using either createRef or useRef react hook if it is a functional component
const overflowRef = createRef();
or
const overflowRef = useRef();
Pass the ref to RenderCard, to be passed on
<RenderCard overflowRef={overflowRef} />
And then check the overflow value as such
overflowRef.current.style.overflow === "scroll"
With the approach above you might want to refactor some of the HoC components and pass a function from parent to child that returns the ref of the element to be accessed also I am not sure of the scope of the block of code where you are executing the ternary.
Perhaps a simpler hack is to rely on using a useState hook with vanilla DOM selectors and passing that into the or even better, just add it to the stateless func component that wraps React.Fragment as a hook:
const myComponent = () => {
const [hasScrollOverflow, setHasScrollOverflow] = useState(false);
React.useEffect(() => {
const element = document.querySelector(".results-set");
const elementStyle = element.style;
if (elementStyle.getPropertyValue('overflow') === 'scroll') {
setHasScrollOverflow(true);
}
}, [])
return (
<React.Fragment>
<div id="search-results">{renderCard()}</div>
{renderFollowup ? null : (
<React.Fragment>
<div id="search-footer">
{hasScrollOverflow ? (
<div className="scroll-button">
<a href="#bottomSection">
<img src="images/arrow_down.svg" alt="scroll to bottom" />
</a>
</div>
) : null}
</div>
</React.Fragment>
)}
</React.Fragment>
);
};

Categories

Resources