GithubHelp home page GithubHelp logo

cloudflare / react-gateway Goto Github PK

View Code? Open in Web Editor NEW
572.0 572.0 73.0 47 KB

Render React DOM into a new context (aka "Portal")

License: BSD 3-Clause "New" or "Revised" License

JavaScript 98.10% CSS 1.90%

react-gateway's Introduction

React Gateway

Render React DOM into a new context (aka "Portal")

This can be used to implement various UI components such as modals. See react-modal2.

It also works in universal (isomorphic) React applications without any additional setup and in React Native applications when used correctly.

Installation

$ npm install --save react-gateway

Example

import React from 'react';
import {
  Gateway,
  GatewayDest,
  GatewayProvider
} from 'react-gateway';

export default class Application extends React.Component {
  render() {
    return (
      <GatewayProvider>
        <div>
          <h1>React Gateway Universal Example</h1>
          <div className="container">
            <Gateway into="one">
              <div className="item">Item 1</div>
            </Gateway>
            <Gateway into="two">
              <div className="item">Item 2</div>
            </Gateway>
            <div className="item">Item 3</div>
          </div>
          <GatewayDest name="one" component="section" className="hello"/>
          <GatewayDest name="two"/>
        </div>
      </GatewayProvider>
    );
  }
}

Will render as:

<div>
  <h1>React Gateway Universal Example</h1>
  <div className="container">
    <noscript></noscript>
    <noscript></noscript>
    <div className="item">Item 3</div>
  </div>
  <section className="hello">
    <div className="item">Item 1</div>
  </section>
  <div>
    <div className="item">Item 2</div>
  </div>
</div>

Usage

To get started with React Gateway, first wrap your application in the <GatewayProvider>.

  import React from 'react';
+ import {
+   GatewayProvider
+ } from 'react-gateway';

  export default class Application extends React.Component {
    render() {
      return (
+       <GatewayProvider>
          <div>
            {this.props.children}
          </div>
+       </GatewayProvider>
      );
    }
  }

Then insert a <GatewayDest> whereever you want it to render and give it a name.

  import React from 'react';
  import {
    GatewayProvider,
+   GatewayDest
  } from 'react-gateway';

  export default class Application extends React.Component {
    render() {
      return (
        <GatewayProvider>
          <div>
            {this.props.children}
+           <GatewayDest name="global"/>
          </div>
        </GatewayProvider>
      );
    }
  }

Then in any of your components (that get rendered inside of the <GatewayProvider>) add a <Gateway>.

  import React from 'react';
+ import {Gateway} from 'react-gateway';

  export default class MyComponent extends React.Component {
    render() {
      return (
        <div>
+         <Gateway into="global">
+           Will render into the "global" gateway.
+         </Gateway>
        </div>
      );
    }
  }

If you want to customize the <GatewayDest> element, you can pass any props, including component (which will allows you to specify a tagName or custom component), and they will be passed to the created element.

  export default class Application extends React.Component {
    render() {
      return (
        <GatewayProvider>
          <div>
            {this.props.children}
-           <GatewayDest name="global"/>
+           <GatewayDest name="global" component="section" className="global-gateway"/>
          </div>
        </GatewayProvider>
      );
    }
  }

How it works

React Gateway works very differently than most React "portals" in order to work in server-side rendered React applications.

It maintains an internal registry of "containers" and "children" which manages where things should be rendered.

This registry is created by <GatewayProvider> and passed to <Gateway> and <GatewayDest> invisibly via React's contextTypes.

Whenever a child or container is added or removed, React Gateway will update its internal registry and ensure things are properly rendered.

React Native example

React Gateway does not directly depend on react-dom, so it works fine with React Native under one condition:

You must pass React Native component like View or similar to component prop of <GatewayDest>.

Because if you don't, <GatewayDest> will try to render div element, which is not available.

import React from 'react';
import { Text, View } from 'react-native';
import {
  Gateway,
  GatewayDest,
  GatewayProvider
} from 'react-gateway';

export default class Application extends React.Component {
  render() {
    return (
      <GatewayProvider>
        <View>
          <Text>React Gateway Native Example</Text>
          <View>
            <Gateway into="one">
              <Text>Text rendered elsewhere</Text>
            </Gateway>
          </View>
          <GatewayDest name="one" component={View} />
        </View>
      </GatewayProvider>
    );
  }
}

react-gateway's People

Contributors

aij avatar danielberndt avatar hawkrives avatar hturan avatar iest avatar istarkov avatar jamiebuilds avatar jossmac avatar jwineman avatar kylecesmat avatar manatarms avatar marksteyn avatar npbee avatar rnons avatar sejoker avatar tajo avatar vlki avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-gateway's Issues

Context is lost

When a component is rendered into a <Gateway /> the subtree context is lost.

Invalid context `gatewayRegistry` of type `GatewayRegistry` supplied to `Gateway`

I've recently started getting this issue, and I really don't know where it's coming from. The full message is:

Warning: Failed context type: Invalid context `gatewayRegistry` of type `GatewayRegistry` supplied to `Gateway`, expected instance of `GatewayRegistry`.

I'm not really sure where this comes from since I've been using this library for a while and this is the first time I come across it. I'll try digging into the Gateway component to see what's going on.

React Native Packager throws error because of `.babelrc` in `node_modules/react-gateway`

There is actually one issue when using React Gateway in React Native application. And that's React Native Packager not ignoring .babelrc in node_modules/react-gateway.

React Native Packager does not ignore .babelrc, parses it and fails on loading the babel preset cf.

See similar issues

The solution is simple: add .babelrc to .npmignore


Full React Native Packager console output with error:

> node node_modules/react-native/local-cli/cli.js start "--reset-cache"

Scanning 634 folders for symlinks in /Users/vlki/repos/tablet/node_modules (15ms)
 ┌────────────────────────────────────────────────────────────────────────────┐
 │  Running packager on port 8081.                                            │
 │                                                                            │
 │  Keep this packager running while developing on any JS projects. Feel      │
 │  free to close this tab and run your own packager instance if you          │
 │  prefer.                                                                   │
 │                                                                            │
 │  https://github.com/facebook/react-native                                  │
 │                                                                            │
 └────────────────────────────────────────────────────────────────────────────┘
Looking for JS files in
   /Users/vlki/repos/tablet


React packager ready.

Loading dependency graph, done.
warning: the transform cache was reset.
Bundling `index.ios.js`  [development, non-minified, hmr disabled]  99.7% (1182/1184), failed.
error: bundling failed: "TransformError: /Users/vlki/repos/tablet/node_modules/react-gateway/lib/index.js: Couldn't find preset \"cf\" relative to directory \"/Users/vlki/repos/tablet/node_modules/react-gateway\""

Versions used:

$ ./node_modules/.bin/react-native --version
0.47.1

$ node -v
v6.11.0

$ npm -v
4.6.1

React 15.3.0 PropTypes

Warning: You are manually calling a React.PropTypes validation function
for the `tagName` prop on `GatewayDest`. 
This is deprecated and will not work in the next major version.
You may be seeing this warning due to a third-party PropTypes library

Infinite render loop with deeply dynamic children

Description

I've got a Modal component which renders the modal box + its children if it's own open prop is set to true. This is run through VelocityTransitionGroup, which means if the open prop changes, it'll add remove/children respectively.

This causes an infinite render bug due, which I believe is due to lines 34 - 36 in GatewayRegistry.js.

Background information

The purpose of using react-gateway was to avoid an issue with nested forms, where the modal rendered its own form, after being triggered by an existing form's element event.

Question

Why is it necessary to completely delete all the children?

If my Modal is set to open={true}, it'll continuously try to re-render the children, because sending a <Modal open={true} /> into Gateway instructs the modal to add its own children, thus triggering componentWillReceiveProps onGateway, and effectively deleting it and adding it continuously.

Can't use for server-side rendering

I just stumbled across this package while looking at react-modal2 (while researching an alternative for react-modal and I really like the philosophy of creating small, dumb components that do one thing.

However, I'm working on an isomorphic app and this package currently can't be used server-side, since it dies on this error:

./node_modules/react-gateway/index.js:20
      to: document.body
          ^

ReferenceError: document is not defined
    at Function.module.exports.React.createClass.getDefaultProps (./node_modules/react-gateway/index.js:20:11)

This is pretty logical of course, but it would be excellent if this package could be used server-side too so it can be used in isomorphic apps.

Server side rendering fails when GatewayDest is declared before the Gateway component

I've been using react-gateway for years because of it's support for SSRing and it's been great, however I've recently run into a bug where the component does not render if the GatewayDest is declared before the Gateway. e.g.

// This does *not* server side render
const App: React.FunctionComponent<any> = ({}) => {
  return (
    <>
      <GatewayDest name="foo" />
      <Gateway into="foo">
        <div>Hello World</div>
      </Gateway>
    </>
  );
};
// This does server side render
const App: React.FunctionComponent<any> = ({}) => {
  return (
    <>
      <Gateway into="foo">
        <div>Hello World</div>
      </Gateway>
      <GatewayDest name="foo" />
    </>
  );
};

When you think about it this makes perfect sense as, in the first example, the gateway destination isn't aware of the gateway component as it's not yet rendered.

I think it's taken me this long to discover this issue because 99% of my use-cases have been to render modals or tooltips etc. which have always lived at the bottom of the page, however I'm now using it to render a notification banner at the top of the page which causes this issue.

Honestly, although I haven't looked into the code, I expect this would be quite challenging to fix but would be interested to hear otherwise?

If it's a "won't fix", perhaps you'd accept a PR to update the docs with this limitation?

Please release 3.0

With removed deprecations and react and prop-types as peerDependecies

Problems from swapping components

We're running into problems because we're trying to replace one with another within a single update. The new 's componentWillMount triggers before the old one's componentWillUnmount, resulting in a spurious "Only a single Gateway can be rendered at a time into a GatewayDest" warning, followed by the older gateway's componentWillUnmount removing the new gateway's children (because GatewayRegistry.removeChild doesn't care whose children it's removing).

One solution is to add or restore support for multiple children (see #15 or briteback/react-gateway).

props don't get passed from Gateway to GatewayDest

I'd like to do something like this:

function renderTabs(tabs) {
    tabs.forEach((tab) => <Link to={tab.dest} />)
}

function TabbedMenu(props) {
    return (<div>{renderTabs(props.tabs)}</div>)
}

<GatewayDest name="TabbedMenu" component={TabbedMenu} />
<Gateway into="TabbedMenu" tabs={[...list of tab titles...]} />

but it doesn't look like you propagate anything other than props.children from the Gateway object to the GatewayDest - is there any reason for that? This would allow for more spec drive gateways. As it is, the gateway will just wrap whatever gets sent through in a custom component.

React Native prop types validation

React fails to validate View on GatewayDest as a value of component property.

Failed prop type: Invalid prop `component` supplied to `GatewayDest`.

Code:

import React, {Component} from 'react'
import {View} from 'react-native'
import {GatewayProvider, GatewayDest} from 'react-gateway'

class RootComponent extends Component {

  /* ... */

  render () {
    const RootRoute = createRootRoute(this.state.isAuthorized)
    return this.state.isReady ? (
      <GatewayProvider>
        <View style={{flex: 1}}>
          <RootRoute ref={(el) => (this.rootRoute = el)} />
          <Notifier/>
          <GatewayDest name="inputAccessory" component={View}/>
        </View>
      </GatewayProvider>
    ) : null
  }
}

React 15 support

Hello and thanks for your work! I recently attempted to get this library working with [email protected] and ran into some issues. It looks like there are a couple of updates in v15 that may require some changes.

Returning null now returns a comment node instead of noscript

https://facebook.github.io/react/blog/#rendering-null-now-uses-comment-nodes
facebook/react@3581406

I don't think this should have any functional effect, we'd just need to update the tests, e.g.:

assertEqual(
  <GatewayProvider>
    <div>
      <section>
        <Gateway into="foo">
          Hello World
        </Gateway>
      </section>
      <GatewayDest name="foo"/>
    </div>
  </GatewayProvider>,
  // should equal
  <div>
    <section>
      <noscript/>
    </section>
    <div>Hello World</div>
  </div>
);

would now just be:

assertEqual(
  <GatewayProvider>
    <div>
      <section>
        <Gateway into="foo">
          Hello World
        </Gateway>
      </section>
      <GatewayDest name="foo"/>
    </div>
  </GatewayProvider>,
  // should equal
  <div>
    <section></section>
    <div>Hello World</div>
  </div>
);

In development, ref and key are defined on props with warnings

facebook/react#5744

This is relevant because of this line:

https://github.com/cloudflare/react-gateway/blob/master/src/Gateway.js#L36

renderIntoGatewayNode(props) {
  delete props.ref;   <----
  this.gatewayRegistry.addChild(this.props.into, props.children);
}

When in development, this cause a runtime error because the props object is frozen via Object.freeze and we're in strict mode. The Object.freeze is not new to React 15, so as far as I can tell the reason this has not failed before is because at the time of calling delete props.ref, the ref was never actually defined on props so it just kind of noop-ed and carried along. But now in React 15, ref is always defined so that it can throw the warning about trying to access it as a normal prop.

I can't quite figure out what the original intent was for deleting ref from props. Is that still needed? The tests all seem to pass without it.

I have a branch going here that I'd be happy to send along as a PR, but I wanted to get some clarification on the ref thing first. Also I'm not sure if it'd be better to wait until we get a proper 15.0 version so we're not relying on an RC version.

Possibility to dynamically chose destination

I can't make the following stuff to work, and I don't even know if it's possible :

// Somewhere
<GatewayDest name="global" component={View} />

// Somewhere else
<GatewayDest name={targetId} component={View} />
<Gateway into={condition ? 'global': targetId}>
  <Text>Hi</Text>
</Gateway>

It seems that it's not possible to determine dynamically a portal destination.

See snack on here

Is this planned or possible in another way ?

refs is null

I'm not sure if this is related to #4 but it seems React has a problem accessing refs when using a Gateway (i think this occurs on unmount - no error appears until after I've loaded then unloaded the view which adds the modal). Here's my code:

https://gist.github.com/MarkMurphy/781e49b0d5a1e6b8218d

This component is based on the react bootstrap modal example found here: https://github.com/facebook/react/blob/master/examples/jquery-bootstrap/js/app.js

The specific error message is:

Uncaught TypeError: Cannot read property 'refs' of null

Full stacktrace is here

Deprecated use of PropTypes validation function

Manually calling PropTypes validators has been deprecated in React 15.3.0, showing the following warning:

Warning: You are manually calling a React.PropTypes validation function for the tagName prop on ComponentName. This is deprecated and will not work in the next major version. You may be seeing this warning due to a third-party PropTypes library. See https://fb.me/react-warning-dont-call-proptypes for details.

This would be solved by upgrading react-prop-types to 0.4.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.