2

Right now, I have a react portal rendering a 'pinnable' side drawer modal that will display based on a redux state. The contents of the modal will have information based on where that modal was pinned from, in this case my notifications.

The problem I'm running into at the moment is that since the modal will be pinnable in multiple places, I'm not exactly sure of the logic on how to handle the modal contents if the modal is already pinned.

I've tried/considered the following:

  1. Just have one portal render its children dynamically. Unfortunately the location of where the portal will be rendered does not contain the contents and logic of the modal, so I believe this can't be done.
  2. Compare props.children and if they're not identical, render the newer portal and deconstruct the other. I'm hesitant to use this approach since I believe there's a better solution out there.
  3. Render the portals based on Ids and deconstruct/reconstruct where needed if one exists. I'm leaning towards this approach, but again I'd like to see if there's a better one.

Portal location:

export default function PaperContainer() {


return <div id="pinned-container"></div>;

}

Portal:

export default function PinnedContainer(props) {
const pinned = useSelector(state => state.ui.isDrawerPinned);

return (
    pinned &&
    createPortal(            
            <div>
                <div>{props.children}</div>
            </div>
        ,
        document.getElementById('pinned-container')
    )
);
}

Where the portals are called (simplified for brevity):

export default function PortalCallLocationOne() {
    const dispatch = useDispatch();
    const pinContainer = () => {
         dispatch(toggleDrawer());
    };

 return  (
    <>
         <Button startIcon={<Icon>push_pin</Icon>} onClick={() => pinContainer}>
                 Pin Notification
         </Button>
         <PinnedContainer>
               //Notification
         </PinnedContainer>
    </>
   );
}

export default function PortalCallLocationTwo() {
     const dispatch = useDispatch();
     const pinContainer = () => {
         dispatch(toggleDrawer());
     };
     return (
<>
        <Button startIcon={<Icon>push_pin</Icon>} onClick={() => pinContainer}>
                 Pin List
        </Button>
       <PinnedContainer>
          // List
       </PinnedContainer>
     );
</>
   }
z0mby
  • 95
  • 1
  • 11
  • What I understood from your description is that you want to have multiple portals open in your application. If that's the case you can use https://github.com/RickoNoNo3/react-winbox this package If you cannot use that, create a Portal component that you can reuse in your application. Now, destructure your logic in a parent component and there you'll decide whether to show the Portal or not, also, if a portal is pinned you need to store relevant ID or something in your store to distinguish itself from the others. the main idea is to separate your logic from the portal and reuse it – Shamim Fahad Dec 09 '22 at 07:37

1 Answers1

1

I tried going off of #3 and destroying pinned-container's first child if it existed and replace it with the new children. This didn't work since React was expecting that child and kept throwing failed to execute removeChild on node errors.

Unfortunately it looks like react is unable to replace portal children instead of appending them.

However, I was able to solve my issue by unpinning the portal and repinning it with redux actions.

export default function PinnedContainer(props) {
const pinned = useSelector(state => state.ui.isDrawerPinned);

useEffect(() => {
    if (pinned) {
         dispatch(clearPinned());
         dispatch(pinDrawer(true));
      }
    }, []);

return (
    pinned &&
    createPortal(            
            <div>
                <div>{props.children}</div>
            </div>
        ,
        document.getElementById('pinned-container')
    )
);
}

Reducer:

export const initialState = {
    isDrawerPinned: false,
}

export const reducer = (state = initialState, action) => {
     switch(action.type){
       case actionTypes.PIN_DRAWER:
            return {
                ...state,
                isDrawerPinned: action.isPinned ? action.isPinned : !state.isDrawerPinned,
            };
    case actionTypes.CLEAR_PINNED:
            return {
                ...state,
                isDrawerPinned: state.isDrawerPinned ? !state.isDrawerPinned : state.isDrawerPinned
            };

}

}
z0mby
  • 95
  • 1
  • 11