├── dist
└── .gitignore
├── .npmignore
├── src
├── index.js
├── Demo.css
├── Modal.js
├── Modal.css
├── Demo.js
└── MountAtSelector.js
├── public
└── index.html
├── .gitignore
├── package.json
└── README.md
/dist/.gitignore:
--------------------------------------------------------------------------------
1 | # empty file to ensure folder /dist exists
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .git
3 | .idea
4 | .gitignore
5 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import Demo from './Demo'
4 |
5 | ReactDOM.render(
6 | ,
7 | document.getElementById('app')
8 | )
9 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | MountAtSelector demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | dist
4 |
5 | # dependencies
6 | node_modules
7 |
8 | # testing
9 | coverage
10 |
11 | # production
12 | build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | npm-debug.log
18 | .idea
19 |
--------------------------------------------------------------------------------
/src/Demo.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: lightblue;
3 | font-family: sans-serif;
4 | }
5 |
6 | .content {
7 | background: white;
8 | position: relative;
9 | width: 500px;
10 | padding: 10px;
11 | border: 1px solid black;
12 | height: 300px;
13 | transform: translate(0px, 0px);
14 | margin: 50px;
15 | }
16 |
--------------------------------------------------------------------------------
/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import './Modal.css'
3 | import MountAtSelector from './MountAtSelector'
4 |
5 | export default class Modal extends Component {
6 | render() {
7 | return
8 |
9 |
10 | {this.props.children}
11 |
12 |
13 |
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Modal.css:
--------------------------------------------------------------------------------
1 |
2 | .modal {
3 | background-color: rgba(0, 0, 0, .4);
4 | height: 100%;
5 | overflow: auto;
6 | position: fixed;
7 | left: 0;
8 | top: 0;
9 | width: 100%;
10 | z-index: 10000;
11 | }
12 |
13 | .modal.container {
14 | background-color: white;
15 | position: relative;
16 | border: 1px solid black;
17 | border-radius: 4px;
18 | margin-left: auto;
19 | margin-right: auto;
20 | max-width: 400px;
21 | min-width: 400px;
22 | margin-top: 100px;
23 | padding: 10px;
24 | height: auto;
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mount-at-selector",
3 | "version": "1.0.3",
4 | "description": "Render React components like a Modal outside the main React app",
5 | "keywords": [
6 | "mount",
7 | "render",
8 | "attach",
9 | "modal",
10 | "root",
11 | "dom"
12 | ],
13 | "main": "./dist/MountAtSelector.js",
14 | "author": "Jos de Jong",
15 | "license": "MIT",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/josdejong/mount-at-selector.git"
19 | },
20 | "devDependencies": {
21 | "babel-cli": "6.18.0",
22 | "babel-preset-react-app": "2.0.1",
23 | "react-scripts": "0.8.4"
24 | },
25 | "dependencies": {
26 | "react": "15.4.1",
27 | "react-dom": "15.4.1"
28 | },
29 | "scripts": {
30 | "start": "react-scripts start",
31 | "build": "BABEL_ENV=production babel ./src/MountAtSelector.js -o ./dist/MountAtSelector.js --presets=react-app",
32 | "prepublish": "npm run build",
33 | "eject": "react-scripts eject"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Demo.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import './Demo.css'
3 | import Modal from './Modal'
4 |
5 | export default class Demo extends Component {
6 | state = {
7 | showModal: false
8 | }
9 |
10 | render() {
11 | return
12 |
13 | Content 1
14 |
15 |
16 |
17 |
18 |
19 | {
20 | this.renderModal()
21 | }
22 |
23 |
24 | }
25 |
26 | renderModal () {
27 | if (this.state.showModal) {
28 | return
29 |
30 | I'm a modal.
31 |
32 |
33 |
34 |
35 |
36 | }
37 | else {
38 | return null
39 | }
40 | }
41 |
42 | showModal = () => {
43 | this.setState({ showModal: true })
44 | }
45 |
46 | hideModal = () => {
47 | this.setState({ showModal: false })
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MountAtSelector
2 |
3 | This React component allows rendering components like a Modal outside the main React app.
4 |
5 | Inspired by a blog of Ryan Zec: https://blog.komand.com/how-to-render-components-outside-the-main-react-app
6 |
7 |
8 | # Install
9 |
10 | ```
11 | npm install --save mount-at-selector
12 | ```
13 |
14 |
15 | # Use
16 |
17 | Create an extra container like `#myModal` in your HTML:
18 |
19 |
20 | ```html
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ```
32 |
33 | Create a `MountAtSelector` component in your React app:
34 |
35 | ```jsx
36 | import React from 'react'
37 | import ReactDOM from 'react-dom'
38 | import MountAtSelector from 'mount-at-selector'
39 |
40 | ReactDOM.render(
41 |
42 |
43 | Content rendered inside the React app...
44 |
45 |
46 |
47 |
48 | I'm rendered inside the #myModal element,
49 | outside of the React app.
50 |
51 |
52 |
,
53 | document.getElementById('app')
54 | )
55 | ```
56 |
57 | # Demo
58 |
59 | See the folder `/src` for a full demo. To run the demo, clone the git project
60 | and run `npm install; npm start` in the root of the project.
61 |
62 |
63 | # API
64 |
65 | ```
66 |
67 | ...
68 |
69 | ```
70 |
71 | Properties:
72 |
73 | - `selector`
74 | A query selector like `'#myModal'` or a DOM element like
75 | `document.getElementById('myModal)`. You can attach
76 | multiple `MountAtSelector` components at the same selector.
77 |
78 |
79 | # License
80 |
81 | MIT
--------------------------------------------------------------------------------
/src/MountAtSelector.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react'
2 | import { render, unmountComponentAtNode } from 'react-dom'
3 |
4 | /**
5 | * Usage:
6 | *
7 | *
8 | *
9 | * I'm rendered inside the #myModal element,
10 | * outside of the current React app.
11 | *
12 | *
13 | *
14 | * Properties:
15 | *
16 | * - `selector` A query selector like '#myModal' or a DOM element like
17 | * document.getElementById('myModal). You can attach
18 | * multiple MountAtSelector components at the same selector.
19 | *
20 | */
21 | export default class MountAtSelector extends Component {
22 | renderedSelector = null
23 | container = null // the DOM container where the children or MountAtSelector are currently rendered
24 |
25 | render() {
26 | // don't render anything in the parent component,
27 | // we will render the children in a separate root
28 | return null
29 | }
30 |
31 | renderChildren () {
32 | const selector = this.getSelector()
33 |
34 | if (this.renderedSelector && selector !== this.renderedSelector) {
35 | // selector changed. Unmount rendered content at the old selector
36 | this.unrenderChildren()
37 | this.renderedSelector = selector
38 | }
39 |
40 | if (selector) {
41 | if (!this.container) {
42 | // create a container inside the selector and render stuff there,
43 | // so we can create and destroy multiple MountAtSelector components
44 | // in the same selector.
45 | this.container = document.createElement('div')
46 | this.container.className = 'mount-at-selector-container'
47 | selector.appendChild(this.container)
48 | }
49 |
50 | render(this.props.children, this.container)
51 | }
52 | else {
53 | console.error('Selector not found in MountAtSelector component')
54 | }
55 | }
56 |
57 | unrenderChildren () {
58 | if (this.renderedSelector) {
59 | unmountComponentAtNode(this.renderedSelector)
60 | this.renderedSelector = null
61 | }
62 |
63 | if (this.container && this.container.parentNode) {
64 | this.container.parentNode.removeChild(this.container)
65 | this.container = null
66 | }
67 | }
68 |
69 | getSelector() {
70 | // a query selector like '#myModal'
71 | if (typeof this.props.selector === 'string') {
72 | return document.querySelector(this.props.selector)
73 | }
74 |
75 | // a DOM element like document.getElementById('myModal)
76 | if (this.props.selector !== undefined) {
77 | return this.props.selector
78 | }
79 |
80 | // hm, not good
81 | return null
82 | }
83 |
84 | componentDidMount() {
85 | this.renderChildren()
86 | }
87 |
88 | componentDidUpdate() {
89 | this.renderChildren()
90 | }
91 |
92 | componentWillUnmount() {
93 | this.unrenderChildren()
94 | }
95 | }
96 |
97 | MountAtSelector.propTypes = {
98 | selector: function validateSelector (props, propName, componentName) {
99 | if (typeof props[propName] !== 'string' && props[propName] instanceof Element === false) {
100 | return new Error(
101 | 'Invalid prop `' + propName + '` supplied to' +
102 | ' `' + componentName + '`. String or DOM Element expected.'
103 | )
104 | }
105 | }
106 | }
107 |
108 |
--------------------------------------------------------------------------------