What is the current behavior?
When the React Portal component uses the useContext, its props.children disappears when the state is updated.If I don't update the state or use Portal, the props.children of Portal will display normally.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
const Context = React.createContext({})
const Provider = (props) => {
const [value, setValue] = useState(0)
return (
<Context.Provider
value={{
value
}}
>
{props.children}
</Context.Provider>
)
}
const Portal = (props) => {
const el = document.createElement('div')
const {value} = useContext(Context)
useEffect(() => {
document.body.appendChild(el)
return () => {
document.body.removeChild(el)
}
}, [])
return ReactDOM.createPortal(
props.children,
el
)
}
const LoadingBar = (props) => {
return (
<Provider>
<Portal>
<Bar />
</Portal>
{props.children}
</Provider>
)
}
const Bar = () => {
const {value} = useContext(Context)
return (
<div>{value}</div>
)
}
const Child = (props) => {
const { value, setValue } = useContext(Context)
setValue(5)
return (<div>child</div>)
}
const App = () => {
return (
<LoadingBar>
<Child></Child>
</LoadingBar>
)
}
After updating the "value" in the <Child /> component, the <Bar > component on the page disappears, leaving only the <div /> created by the Portal, deleting "setValue(5)" or the Portal component return<div>{props.children}</div>, the <Bar /> component is displayed again.
What is the expected behavior?
I am expecting Portal's props.children to display properly when using the useContext and the state is updated.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 16.8
You're creating a different div on every render, and portal into it:
const Portal = (props) => {
const el = document.createElement('div')
// ...
return ReactDOM.createPortal(props.children, el)
}
However, only initial div gets added to the document:
useEffect(() => {
// el here is the div from first render
document.body.appendChild(el)
return () => {
document.body.removeChild(el)
}
}, []) // <-- never reruns
So this is why they disappear.
The fix is to use the same portal target for all renders. You can put it in a ref:
const Portal = (props) => {
const elRef = useRef(null)
if (!elRef.current) {
// Create once
elRef.current = document.createElement('div')
}
const el = elRef.current
const {value} = useContext(Context)
useEffect(() => {
document.body.appendChild(el)
return () => {
document.body.removeChild(el)
}
}, [])
return ReactDOM.createPortal(
props.children,
el
)
}
Then it persists between renders.
Most helpful comment
You're creating a different
divon every render, and portal into it:However, only initial
divgets added to the document:So this is why they disappear.
The fix is to use the same portal target for all renders. You can put it in a ref:
Then it persists between renders.