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"
      />
    </>
  )
}

Refrence Element

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.


Refrence Element
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}
      />
    </>
  )
}

Refrence Element

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}
      />
    </>
  )
}

Refrence Element