Active Prop
In the most cases, you only need to use a boolean as state for setting the popper to appear and hide.
const [active, setActive] = useState(false)
<ElementPopper
element={<Element />}
popper={active && <Popper />}
/>
But in some cases, for example, when a large number of operations are required to make the popper component and it is not optimal to repeat that operations every time popper appears, or in cases where the width and height of the popper are not specified at the moment of appearance (For example, the height and width are determined after the async operation) It is necessary to use active prop to show and hide Popper. Otherwise, Popper's position may not be calculated correctly.
function AsyncPopper() {
const [element, setElement] = useState()
useEffect(() => {
//async operation
setTimeout(() => {
setElement(
<div
style={{
width: "120px",
height: "120px",
backgroundColor: "white"
}}
>
Popper Element
</div>
)
}, 200);
}, [])
return element || <div>Loading ...</div>
}
const [active, setActive] = useState(false)
<ElementPopper
element={<Element />}
popper={<AsyncPopper />}
active={active}
/>
But it rarely happens that neither of the above two examples works. In these cases, it is recommended to use a combination of the above two examples.
In this section, I tried to give examples for each of these three situations:
First Example
In this example, active props are not used and it is useful for most of the times.
In fact, by using a boolean as state, popper is generated at the moment of appearance.
import React, { useState } from "react"
import ElementPopper from "react-element-popper"
function Component({ height, width, backgroundColor, children }) {
return (
<div
style={{
width: width + "px",
height: height + "px",
backgroundColor,
textAlign: "center",
display: "flex",
flexDirection: "column",
justifyContent: "center"
}}
>
{children}
</div>
)
}
export default function Example() {
const [active, setActive] = useState(false)
return (
<>
<button
onClick={() => setActive(!active)}
>
toggle visible
</button>
<br />
<ElementPopper
element={(
<Component
height={40}
width={120}
backgroundColor="red"
>
Refrence Element
</Component>
)}
popper={active && (
<Component
height={120}
width={120}
backgroundColor="gray"
>
Popper Element
</Component>
)}
position="right"
/>
</>
)
}
Second Example
I made a change in the above function Component to return its output with a delay and named it AsyncComponent, and as you can see, if we try to use it with the method of the first example, its position calculations will encounter error.
function AsyncComponent({ height, width, backgroundColor, children }) {
const [props, setProps] = useState()
useEffect(() => {
setProps({
style: {
width: width + "px",
height: height + "px",
backgroundColor,
textAlign: "center",
display: "flex",
flexDirection: "column",
justifyContent: "center"
}
})
}, [height, width, backgroundColor])
return props ?
<div {...props}>
{children}
</div>
:
null
}
export default function Example() {
const [active, setActive] = useState(false)
return (
<>
<button
onClick={() => setActive(!active)}
>
toggle visible
</button>
<br />
<ElementPopper
element={(
<Component
height={40}
width={120}
backgroundColor="red"
>
Refrence Element
</Component>
)}
popper={active && (
<AsyncComponent
height={120}
width={120}
backgroundColor="gray"
>
Popper Element
</AsyncComponent>
)}
position="right"
/>
</>
)
}
The reason for this computational error is that at the first moment, instead of div, the null value is defined as popper and causes this problem.
To solve this problem, you can easily use the active prop:
export default function Example() {
const [active, setActive] = useState(false)
return (
<>
<button
onClick={() => setActive(!active)}
>
toggle visible
</button>
<br />
<ElementPopper
element={(
<Component
height={40}
width={120}
backgroundColor="red"
>
Refrence Element
</Component>
)}
popper={(
<AsyncComponent
height={120}
width={120}
backgroundColor="gray"
>
Popper Element
</AsyncComponent>
)}
position="right"
active={active}
/>
</>
)
}
As you can see, there is no problem in calculating Popper's position, and the reason is that in fact, popper is not generated at the moment of appearance and is always there, and by changing the active value, you only change its visibility from hidden to visible.
Third Example
Sometimes your component is like the second example, but you want to make it visible as the first example.
In this case, you must use a combination of the first and second examples.
function AsyncComponent({ height, width, backgroundColor, children, onReady }) {
const [state, setState] = useState({})
useEffect(() => {
setState({
props: {
style: {
width: width + "px",
height: height + "px",
backgroundColor,
textAlign: "center",
display: "flex",
flexDirection: "column",
justifyContent: "center"
}
},
ready: true
})
}, [height, width, backgroundColor])
useEffect(() => {
if (!state.ready) return
onReady()
}, [state.ready, onReady])
return state.ready ?
<div {...state.props}>
{children}
</div>
:
null
}
export default function Example() {
const [active, setActive] = useState(false)
const [isPopperReady, setIsPopperReady] = useState(false)
return (
<>
<button
onClick={() => {
if (!active) {
setActive(true)
} else {
setActive(false)
setIsPopperReady(false)
}
}}
>
toggle visible
</button>
<br />
<ElementPopper
element={(
<Component
height={40}
width={120}
backgroundColor="red"
>
Refrence Element
</Component>
)}
popper={active && (
<AsyncComponent
height={120}
width={120}
backgroundColor="gray"
onReady={() => setIsPopperReady(true)}
>
Popper Element
</AsyncComponent>
)}
position="right"
active={isPopperReady}
/>
</>
)
}