How to use ReactDOM.createPortal() in React 16? - javascript

I'm using React 16 and need portals, but am not able to find any documentation on this feature. Does anyone know how to use this yet?
https://github.com/facebook/react/pull/10675
Thanks for any advice.

React v16 has just released a couple of hours ago (Yay!!) which officially supports Portal.
What is Portal? Since How long has it been there?
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
Portal is not new concept in the react community. Many libraries are available that supports this kind of functionality. e.g react-portal and react-gateway.
What happens when rendering any react app?
Generally, when rendering any React application, a single DOM element is used to render the whole React tree.
class HelloReact extends React.Component {
render() {
return (
<h1>Hello React</h1>
);
}
}
ReactDOM.render(<HelloReact />, document.getElementById('root'));
As you can see we are rendering our react component into a DOM element having id root.
What is Portal and why is it needed? Why is it there?
Portals are a way to render React children outside the main DOM hierarchy of the parent component without losing the react context. I'm emphasizing on it because very popular libraries like react-router, redux heavily uses the react context. So context availability when using Portal is very helpful.
As per the react docs,
A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually "break out" of its container. For example, dialogs, hovercards, and tooltip.
So, with portals, you can render a parallel react tree onto another DOM node when needed. Even though it is rendered in the different DOM node, parent component can catch the uncaught events. See this codepen provided in the docs itself.
The below example should give your more idea:
// index.html
<html>
<body>
<div id="root"></div>
<div id="another-root"></div>
</body>
</html>
// index.jsx
const mainContainer = document.getElementById('root');
const portalContainer = document.getElementById('another-root');
class HelloFromPortal extends React.Component {
render() {
return (
<h1>I am rendered through a Portal.</h1>
);
}
}
class HelloReact extends React.Component {
render() {
return (
<div>
<h1>Hello World</h1>
{ ReactDOM.createPortal(<HelloFromPortal />, portalContainer) }
</div>
);
}
}
ReactDOM.render(<HelloReact />, mainContainer);
https://codesandbox.io/s/62rvxkonnw
You can use devtools inspect element and see that <h1>I am rendered through a Portal.</h1> is rendered inside #another-root tag, whereas <h1>Hello World</h1> is rendered inside #root tag.
Hope this helps :)
Update: To answer #PhillipMunin's comment.
What is the different between ReactDOM.render and
ReactDOM.createPortal?
Even though the component rendered through the portal is rendered somewhere else (Outside the current container root), it remains present as the child of the same parent component. (Who invoked the ReactDOM.createPortal) So any events on the child are propagated to the parent. (Ofc, This doesn't work if you manually stop the propagation of the event.)
Same context is accessible inside the component rendered through a portal. But not in the case when we do ReactDOM.render directly.
I've created another demo to illustrate my point. https://codesandbox.io/s/42x771ykwx

I think we can achieve same feature by creating an external dom node outside 'root' element
node = document.createElement('div')
document.body.appendChild(node)
ReactDOM.render(<Modal {...props} />,node)
Let's say we are creating a modal view, we can create a wrapper for the same
import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import Modal from './Modal'
let node = null
const ModalWrapper = (props) =>{
useEffect(()=>{
node && ReactDOM.render(<Modal {...props} />,node)
})
useEffect(()=>{
node = document.createElement('div')
document.body.appendChild(node)
ReactDOM.render(<Modal {...props} />,node)
},[])
return(
<script />
)
}
Here is an example Create Modal with React Portal and React Hooks from scratch without external library

For anyone wondering, here is how I implemented my own custom Portal component in Typescript
import React from 'react'
import ReactDOM from 'react-dom'
const Portal: React.FC<PortalProps> = (props) => {
return ReactDOM.createPortal(props.children, props.target)
}
export interface PortalProps {
target: Element;
children: React.ReactNode;
}
export default Portal

Portals are created by calling the createPortal method inside the render method of your component.
render() {
return (
<div>
{ReactDOM.createPortal(this.renderPortal(), this.props.portalEl)}
</div>
)
}
renderPortal should return contents to be rendered inside the portal, while portalEl is an external DOM element that will receive the content.
Someone recently twitted that info on portals can be found in React tests.

For example
const Popover = React.memo((props) => {
// your component implementation
return (
<div>Hi! I am a Popover in body!</div>
);
});
// This allows you to insert a component in the portal when you call the component;
export default function (props) {
return (
React.createPortal(<Popover {...props} />, document.body)
);
}

Related

Persist data between two pages with Next.js

I would like to refactor my Next.js webapp to have different pages handle different screens. Currently, I have this component holding several states to know in which screen I'm in. In the jsx section, I'm using {value && ... } to render the right component.
But I feel this is not good design, and won't be maintainable when adding more and more screens.
I would also like to avoid Redux as it is overkill for my project.
I was thinking about persisting data in cookies so I can retrieve them with getInitialProps in every component when rendering a new page, but is there a more elegant way?
I've read about tweaking the _app.js but I'm not sure to understand the consequences of doing so, and how it could help me..
Any suggestion?
When multiple of your pages need to make use of same data, you can make use of Context to store the result. It a good way to make a centralized storage without using complex and more self sufficient libraries like redux
You can implement context inside of _app.js file which must reside inside your root folder. This way next.js treats it as a root wrapper and you would just need to use 1 instance of Context
contexts/appContext
import React from 'react';
const AppContext = React.createContext();
export const AppProvider = AppContext.Provider;
export const AppConsumer = AppContext.Consumer;
export default AppContext;
_app.js
import React from 'react'
import App from 'next/app'
import AppProvider from '../contexts/appContext';
class MyApp extends App {
state={
data:[]
}
render() {
const { Component, pageProps } = this.props;
// You can implement logic in this component to fetch data and update state
return (
<div>
<AppProvider value={this.state.data}> // pass on value to context
<Component {...pageProps} />
</AppProvider>
</div>
)
}
}
export default MyApp
Now further each component can make use of context value by using AppConsumer or using useContext if you use hooks
Please read more about how to use Context here

React Context API not working from custom NPM component library

I've built a ReactJS component library that I use for multiple projects installed via an NPM package using a sim link. I want to use the context API to pass data from a parent component served from the component library to my base project to be consumed by multiple consumer components also served from the component library. When I try the context is always undefined in my child components.
If I place my consumer component in my provider component within my library it works like a champ but this defeats what I'm trying to achieve. If I export both the provider and the consumer to my base project the consumer doesn't see the provider.
This is from my base project
import { Screen, COD, GenericSocketServer } from 'component-library'
export default class View extends React.PureComponent {
render() {
return (
<Screen className="screen odmb1">
<GenericSocketServer>
<COD />
</GenericSocketServer>
</Screen>
)
}
}
This is my provider code exported from my 'component-library'
import React from 'react';
import MyContext from "./context";
import COD from './../cod';
export default class GenericSocketServer extends React.Component {
render() {
return (
<MyContext.Provider value={{ foo: 'bar' }}>
<COD />
{this.props.children}
</MyContext.Provider>
);
}
}
This is my content code used in 'component-library'
import React from 'react'
const MyContext = React.createContext()
export default MyContext
This is my consumer component exported from 'component-library'
import MyContext from "../GenericSocketServer/context"
class COD extends React.Component {
render() {
return (
<React.Fragment>
<MyContext.Consumer>
{(context) => {
/*
context comes back undefined
I expect { foo: 'bar' }
*/
console.log('context :', context)
return (
<p>This should work</p>
)}}
</MyContext.Consumer>
</React.Fragment>
)
}
}
Context always comes back undefined as if it doesn't see the parent provider. I think I'm ether doing something wrong initializing the context myself or for some reason the two components I'm importing just don't share the same context. Please help!! Not sure if I should give up on this and just use redux.
Maybe you are making multiple instances of the component providing the context. Let's say you have a component Sound, which starts by:
const { Provider, Consumer } = React.createContext();
If you import this library from your main project, the context will be created at the global space. You then use it to render your document tree. But in another component you also imported this library, which had to be resolved during webpack transpilation. It thus has its own copy of the above lines and a context object created in its own space. The problem occurs when you try to use the Consumer, because the Provider was only made by the main project for the first context object, and the second context's provider instance was never instantiated, thus returns undefined.
A solution to the problem is to enforce a single context object, which you can achieve by telling the second component's webpack that the provider-owning library is an external, so when webpack reaches e.g. the "import sound" line, it will not go further and will assume this dependency is resolved at runtime. When runtime comes, it will take it from the same place where the main project is taking it. To do this in webpack, e.g. for above "sound" library, add this to your other component (not main project):
{
...
externals: {
...
'sound': 'sound'
}
...
}
Also in your component package.json:
{
...
peerDependencies: {
"sound": "^1.2.3"
}
}
Apart from Darko's answer, esm and cjs export is also a possible reason for context to fail in a package. If you use the hook in esm and the provider in cjs, you will not get the value for that context.
I recently had a similar issue where I was trying to consume the value of a context inside my library components but using the provider (imported from the package) in the host app.
I managed to solve the issue just by making react and react-dom external and peerDependencies when bundling in rollup.
should your code of consumer be
<React.Fragment>
<MyContext.Consumer>
{value => /* render something based on the context value */}
</MyContext.Consumer>
</React.Fragment>
as stated from the official react doc : https://zh-hant.reactjs.org/docs/context.html
when you define
you can use it like

Use react without a router component

If I want to make a web application using reactjs that is not a single page.
Should I compile all the react code into a single file and load it on all pages of the application, then use the function that I expose to render the necessary components?
Example of an html file
<div id="Clock" data-react="Clock"></div>
<div id="HelloWorld" data-react="HelloWorld"></div>
example of index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './Clock';
import HelloWorld from './HelloWorld';
import OtherComponent from './OtherComponent';
const APPS = {
Clock,
HelloWorld,
OtherComponent
};
const MyReactRender = react => {
let component = react.getAttribute('data-react');
let App = APPS[component];
if(App != undefined) {
ReactDOM.render(<App />, document.getElementById(component));
}
}
document.querySelectorAll('[data-react]').forEach(MyReactRender);
I'd see two ways, of increasing quality and difficulty. In both cases, you use good old anchors elements to redirect the page to a url, to which different templates correspond.
Manually check for the existence of divs id's
In this case, each template includes the same javascript bundle that contains everything in the app and a single element with an id corresponding to the specific component. The idea is to check wether or not an element is present in the page, then activate its corresponding react component if it is.
if (document.getElementById('component-root')) {
ReactDOM.render(<Component />, document.getElementById('component-root'));
}
On the up side, it's quite easily implemented. On the down side, the bundle will always get bigger and bigger, and the list of ifs grows each time you add a new "page".
Separate your modules in actual bundles
Different bundle managers exist, but I'd recommend using Webpack to create multiple bundles that contain only specific part of your application. Then, each template contains only the corresponding div element, as well as that specific bundle.
<head><script src="/js/clock.js"></head>
<body><div id="root-clock"></div></body>
<head><script src="/js/otherComponent.js"></head>
<body><div id="root-other-component"></div></body>
How to package multiple bundles with webpack is out of the scope of this answer, but look here.
I've tried making a react application without a router. I used ternary operators to switch from component to component.
// App Component
class App extends Component {
constructor(props) {
super(props)
this.state = {
inClockComponent: true,
inHelloWorldComponent: false,
inOtherComponent: false
}
}
render() {
const {inClockComponent, inHelloWorldComponent, inOtherComponent} = this.state
return (
<div>
{
inClockComponent
? <Clock> : inHelloWorldComponent
? <HelloWorld> : inOtherComponent ? <OtherComponent> :
<div>No Component Here</div>
}
</div>
}
You could pass a function from the App component that would change the display state to each child component of App
Example
// in App Component
showHelloWorldComponent() {
this.setState({
inClockComponent: false,
inHelloWorldComponent: true,
inOtherComponent: false
)}
}
You insert that function onto a button that would navigate to a different component
Example
// in Clock Component
render() {
return (
<div>
<h2>Time is 5:15 P.M.</h2>
<button onClick={this.props.showHelloWorldComponent}>
Go To Hello World
</button>
)
}
It's a messy solution, and I wouldn't suggest using it in a big application, but I hope this answers your question!

React routes not automatically updating when nested under 'smart' Redux container components

I'm trying to create an Electron app using React, React-router and Redux. What I'm finding is that my routing logic works absolutely fine when I'm nesting the switch/route logic under a purely presentational component (Page), but that I'm forced to refresh the page to see navigational changes if nested under a 'smart' container component.
Near the top of my React component hierarchy (right beneath HashRouter) I have a Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
Here, DetailPane and SideBar are both container components wrapped around presentational components of the same name.
At startup (and during hot reloads), I create my React hierarchy using this function:
export default () => (
<Router>
<Page>
<Switch>
<Route exact path='/txDefinitions/:definitionName/:fieldName' component={FieldPage}/>
<Route exact path='/txDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/rxDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/'/>
<Route component={Route404}/>
</Switch>
</Page>
</Router>
This means that <Switch>...</Switch> gets nested underneath <DetailPane>.
If I try to navigate around my app (clicking links in the side bar), I won't actually see the detail pane render the new component until I force-reload the Electron app.
However, I find that routing works as expected if I omit DetailPane from Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
{props.children}
</div>
);
}
Here is my React hierarchy without DetailPane (works fine):
Here is my React hierarchy with DetailPane (does not work right):
(Apologies for using images but I'm not sure if there's a way to copy from React devtools into clipboard - appears larger if opened in a new tab).
As I was writing this question, I realised this wouldn't be a huge issue for me because earlier refactoring had made the 'smart' version of DetailPane apparently obsolete. Using the purely presentational version of DetailPane
instead resolves this issue:
import * as React from 'react';
//import {DetailPane} from '../../containers'; // Smart/Redux
import {DetailPane} from '../../components'; // Dumb/presentational
import {SideBar} from '../../containers/';
const styles = require('./Page.scss');
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
However, I'm still curious why this doesn't work for the container component version. For reference, this is the container component version of DetailPane:
import {connect} from 'react-redux';
import {DetailPane} from '../../components';
// TODO: delete this container?
function mapStateToProps (state): {} {
return {};
}
function mapDispatchToProps (dispatch) {
// TODO.
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(DetailPane);
The connect HOC implements shouldComponentUpdate logic so if the props don't change, the component doesn't update.
To prevent this from occurring, and have the component always render, you can override the pure option in the connect call.
export default connect(mapStateToProps, mapDispatchToProps, undefined, { pure: false })(DetailPane);
See the react-redux API docs for more details.

Construct React Component from a React Element

I'm trying to create a "higher-order" function in React that performs some permissions-based checks on the wrapped component and returns it accordingly.
MyComponent.js
...
export default Permissions(MyComponent)
Permissions.js
export default function Permissions(Component) {
class NewComponent extends React.Component {
// ... perform checks here
render() {
return {validPermissions && <Component />}
}
}
}
However, I'd like to be able to use this Permissions as a React Component (as opposed to a function that wraps the component export).
It would looks similar to this:
<Permissions>
<MyComponent />
</Permissions>
When I run React.Component.isPrototypeOf(Component.children) I get false in these instances. My inclination is to think that the solution is to use some React or ReactDOM method to transform the React Element into a React Component, and then perform the same checks.
How can I transform a React Element into a React Component?
Update:
I gave the bit about permissions as context, but not looking for help with regard to implementing permissions.
I am basically looking for the opposite of React.createElement(MyComponent).
You can use a functional component, which combines the best of both worlds: it's simple (just a function!) and at the same time it's a proper stateless React component.
const Permissions = ({ granted, children }) =>
granted ? React.Children.only(children) : null;
Usage:
<Permissions granted={true}>
<MyComponent />
</Permissions>

Categories

Resources