react-tunnels
Advanced tools
Comparing version 1.0.2 to 1.1.0
@@ -52,5 +52,5 @@ 'use strict'; | ||
}, { | ||
key: 'componentWillUpdate', | ||
value: function componentWillUpdate(nextProps) { | ||
this.setTunnelProps(nextProps); | ||
key: 'componentDidUpdate', | ||
value: function componentDidUpdate() { | ||
this.setTunnelProps(this.props); | ||
} | ||
@@ -57,0 +57,0 @@ }, { |
@@ -67,2 +67,3 @@ 'use strict'; | ||
renderChildren = _props.children, | ||
Tag = _props.component, | ||
multiple = _props.multiple; | ||
@@ -74,7 +75,7 @@ | ||
if (Array.isArray(tunnelProps) || multiple) { | ||
return !tunnelProps ? (0, _react.createElement)(renderChildren, { items: [] }) : (0, _react.createElement)(renderChildren, { | ||
return !tunnelProps ? renderChildren({ items: [] }) : renderChildren({ | ||
items: Array.isArray(tunnelProps) ? tunnelProps : [tunnelProps] | ||
}); | ||
} else { | ||
return (0, _react.createElement)(renderChildren, tunnelProps || {}); | ||
return renderChildren(tunnelProps || {}); | ||
} | ||
@@ -88,3 +89,3 @@ } | ||
return _react2.default.createElement( | ||
_react.Fragment, | ||
Tag, | ||
null, | ||
@@ -101,5 +102,9 @@ tunnelProps.children | ||
children: _propTypes2.default.func, | ||
component: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.symbol]), | ||
id: _propTypes2.default.string.isRequired, | ||
multiple: _propTypes2.default.bool | ||
}; | ||
TunnelPlaceholder.defaultProps = { | ||
component: _react.Fragment | ||
}; | ||
TunnelPlaceholder.contextTypes = { | ||
@@ -106,0 +111,0 @@ tunnelState: _propTypes2.default.object |
{ | ||
"name": "react-tunnels", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "A easy way to communicate rendering logic and data to ancestor components in React.", | ||
"main": "./lib/index.js", | ||
"repository": "javivelasco/react-tunnels", | ||
"scripts": { | ||
@@ -23,22 +24,22 @@ "build": "babel ./src --out-dir ./lib", | ||
"devDependencies": { | ||
"babel-cli": "^6.26.0", | ||
"babel-core": "^6.26.0", | ||
"babel-eslint": "^8.2.1", | ||
"babel-jest": "^22.1.0", | ||
"babel-preset-env": "^1.6.1", | ||
"babel-preset-react": "^6.24.1", | ||
"babel-preset-stage-2": "^6.24.1", | ||
"enzyme": "^3.3.0", | ||
"enzyme-adapter-react-16": "^1.1.1", | ||
"eslint": "^4.16.0", | ||
"eslint-config-prettier": "^2.9.0", | ||
"eslint-plugin-import": "^2.8.0", | ||
"eslint-plugin-prettier": "^2.5.0", | ||
"eslint-plugin-react": "^7.6.1", | ||
"jest": "^22.1.4", | ||
"prettier": "^1.10.2", | ||
"prop-types": "^15.6.0", | ||
"react": "^16.2.0", | ||
"react-dom": "^16.2.0", | ||
"rimraf": "^2.6.2" | ||
"babel-cli": "6.26.0", | ||
"babel-core": "6.26.3", | ||
"babel-eslint": "9.0.0", | ||
"babel-jest": "23.4.2", | ||
"babel-preset-env": "1.7.0", | ||
"babel-preset-react": "6.24.1", | ||
"babel-preset-stage-2": "6.24.1", | ||
"enzyme": "3.5.0", | ||
"enzyme-adapter-react-16": "1.3.1", | ||
"eslint": "5.5.0", | ||
"eslint-config-prettier": "3.0.1", | ||
"eslint-plugin-import": "2.14.0", | ||
"eslint-plugin-prettier": "2.6.2", | ||
"eslint-plugin-react": "7.11.1", | ||
"jest": "23.5.0", | ||
"prettier": "1.14.2", | ||
"prop-types": "15.6.2", | ||
"react": "16.4.2", | ||
"react-dom": "16.4.2", | ||
"rimraf": "2.6.2" | ||
}, | ||
@@ -45,0 +46,0 @@ "peerDependencies": { |
@@ -13,17 +13,17 @@ # React Tunnels 🚇 [![npm](https://img.shields.io/npm/v/react-tunnels.svg?style=flat)](https://www.npmjs.org/package/react-tunnels)[![Build Status](http://img.shields.io/travis/javivelasco/react-tunnels/master.svg?style=flat-square)](https://travis-ci.org/javivelasco/react-tunnels) | ||
There is a common use case in React apps where you want to define a `Layout` where the content of some elements are defined by `children` components. For example, you want define `Layout` just once and reuse it for every page but it has a breadcrumb whose steps depend on `children` components. This tiny library allows you to define *tunnels* to render from a element to whatever other element in the App, even elements located on top of the tree. It's like `Portal` but the target is a *component* instead of a *DOM element*. | ||
There is a common use case in React apps where you want to define a `Layout` where the content of some elements is defined by `children` components. For example, you want to define `Layout` just once and reuse it for every page but it has a breadcrumb whose steps depend on `children` components. This tiny library allows you to define *tunnels* to render from an element to whatever another element in the App, even elements located on top of the tree. It's like `Portal` but the target is a *component* instead of a *DOM element*. | ||
### Usage | ||
## Usage | ||
Define a `TunnelPlaceholder` identified by an `id` and decide what properties are going to be passed to its `render` function by defining `Tunnel` components with the **same id** anywhere else in the app. If you define just a single `Tunnel` its props will be passed straight to the `render` function, if there are more than one `Tunnel` for a single `id`, the placeholder `render` function will receive a `item` argument which is an Array containing the `props` for each `Tunnel`. Let's see some examples. | ||
Define a `TunnelPlaceholder` identified by an `id` and decide what properties are going to be passed to its `render` function by defining `Tunnel` components with the **same id** anywhere else in the app. If you define just a single `Tunnel` its props will be passed straight to the `render` function, if there is more than one `Tunnel` for a single `id`, the placeholder `render` function will receive an `item` argument which is an Array containing the `props` for each `Tunnel`. Let's see some examples. | ||
### Simple example: tunneling children | ||
### Simple example: children tunneling | ||
Define a placeholder without any render function so it will render any children coming from `Tunnel` components. | ||
Define a placeholder without any render function so it will render any children coming from a `Tunnel` component with the same id. | ||
```jsx | ||
import { TunnelsProvider, TunnelPlaceholder, Tunnel } from 'react-tunnels' | ||
import { TunnelProvider, TunnelPlaceholder, Tunnel } from 'react-tunnels' | ||
render( | ||
<TunnelsProvider> | ||
<TunnelProvider> | ||
<div> | ||
@@ -35,27 +35,19 @@ <TunnelPlaceholder id="my-tunnel" /> | ||
</div> | ||
</TunnelsProvider> | ||
</TunnelProvider> | ||
) | ||
``` | ||
### A more complex example: building a Breadcrumb | ||
Check the real example [here](https://codesandbox.io/s/p79k8w0jnq) | ||
### More complex example: building a Breadcrumb | ||
It's easy to build a breadcrumb using the prop `multiple` in the `TunnelPlaceholder`. This allows to let it know that there will be multiple tunnels so the `render` function will be called with an array of props. | ||
```jsx | ||
render( | ||
<TunnelsProvider> | ||
{/* This will render the breadcrumb */} | ||
<Breadcrumbs /> | ||
{/* Somewhere else in children */} | ||
<Breadcrumb url="/products">Products</Breadcrumb> | ||
<Breadcrumb url="/products/123">Product <strong>123</strong></Breadcrumb> | ||
</TunnelsProvider> | ||
) | ||
const Breadcrumbs = () => ( | ||
<TunnelPlaceholder id="breadcrumb"> | ||
{({ items = [] }) => ( | ||
<ul> | ||
{items.map(({ children, href }) => ( | ||
<li><a href={href}>{children}</a></li> | ||
))} | ||
</ul> | ||
<TunnelPlaceholder id="breadcrumb" multiple> | ||
{({ items }) => ( | ||
items.map(({ children, href }) => ( | ||
<span><a href={href}>{children}</a></span> | ||
)) | ||
)} | ||
@@ -66,15 +58,31 @@ </TunnelPlaceholder> | ||
const Breadcrumb = ({ children, url }) => ( | ||
<Tunnel id="breadcrumb" url={url}> | ||
<Tunnel id="breadcrumb" href={url}> | ||
{children} | ||
</Tunnel> | ||
) | ||
render( | ||
<TunnelProvider> | ||
{/* This will render the breadcrumb */} | ||
<Breadcrumbs /> | ||
{/* Somewhere else in children */} | ||
<Breadcrumb url="/products">Products</Breadcrumb> | ||
<Breadcrumb url="/products/123">Product <strong>123</strong></Breadcrumb> | ||
</TunnelProvider> | ||
) | ||
``` | ||
### Similar Libraries | ||
Check the live example [here](https://codesandbox.io/s/0ym0n37jnl) | ||
- [React Slot Fill](https://github.com/camwest/react-slot-fill): [@camwest](https://github.com/camwest) has built a similar project but with a different API and a bit more limited use cases. The main difference is that you can't pass content to a placeholder from multiple entry points while react-tunnels does it by passing an array with the props defined by each tunnel to the render function of the placeholder. For simple cases, it is pretty similar. | ||
## Similar Libraries | ||
- [React Slot Fill](https://github.com/camwest/react-slot-fill): A similar project built by [Cameron Westland](https://github.com/camwest) with a slightly different API and a bit more limited use cases. The main difference is that you can't pass content to a placeholder from multiple entry points. react-tunnels does this by passing an array with the props defined by each tunnel to the render function of the placeholder. For simple cases though, it is pretty similar. | ||
- [Preact Slots](https://github.com/developit/preact-slots): A library similar to React Slot Fill but for [Preact](https://github.com/developit/preact) developed by [Jason Miller](https://twitter.com/_developit). | ||
## About | ||
This project has been developed by [Javi Velasco](https://twitter.com/javivelasco) as a way to build *Breadcrumb* components and `Layout` customizations for a variety of React projects. Any feeback, help or improvements is highly appreciated. | ||
## License | ||
This project is licensed under the terms of the [MIT license](https://github.com/javivelasco/react-tunnels/blob/master/LICENSE). |
@@ -21,4 +21,4 @@ import { Component } from 'react' | ||
componentWillUpdate(nextProps) { | ||
this.setTunnelProps(nextProps) | ||
componentDidUpdate() { | ||
this.setTunnelProps(this.props) | ||
} | ||
@@ -25,0 +25,0 @@ |
import PropTypes from 'prop-types' | ||
import React, { createElement, Component, Fragment } from 'react' | ||
import React, { Component, Fragment } from 'react' | ||
@@ -7,2 +7,3 @@ class TunnelPlaceholder extends Component { | ||
children: PropTypes.func, | ||
component: PropTypes.oneOfType([PropTypes.node, PropTypes.symbol]), | ||
id: PropTypes.string.isRequired, | ||
@@ -12,2 +13,6 @@ multiple: PropTypes.bool, | ||
static defaultProps = { | ||
component: Fragment, | ||
} | ||
static contextTypes = { | ||
@@ -35,3 +40,8 @@ tunnelState: PropTypes.object, | ||
const { tunnelState } = this.context | ||
const { id, children: renderChildren, multiple } = this.props | ||
const { | ||
id, | ||
children: renderChildren, | ||
component: Tag, | ||
multiple, | ||
} = this.props | ||
const tunnelProps = tunnelState.getTunnelProps(id) | ||
@@ -42,8 +52,8 @@ | ||
return !tunnelProps | ||
? createElement(renderChildren, { items: [] }) | ||
: createElement(renderChildren, { | ||
? renderChildren({ items: [] }) | ||
: renderChildren({ | ||
items: Array.isArray(tunnelProps) ? tunnelProps : [tunnelProps], | ||
}) | ||
} else { | ||
return createElement(renderChildren, tunnelProps || {}) | ||
return renderChildren(tunnelProps || {}) | ||
} | ||
@@ -56,3 +66,3 @@ } | ||
return <Fragment>{tunnelProps.children}</Fragment> | ||
return <Tag>{tunnelProps.children}</Tag> | ||
} | ||
@@ -59,0 +69,0 @@ } |
@@ -12,6 +12,12 @@ import { configure, mount } from 'enzyme' | ||
const Msg = ({ message }) => ( | ||
<div className="msg">{message || 'defaultMessage'}</div> | ||
) | ||
Msg.propTypes = { message: PropTypes.string } | ||
class Msg extends React.Component { | ||
componentDidMount() { | ||
this.props.didMount && this.props.didMount() | ||
} | ||
render() { | ||
const { message } = this.props | ||
return <div className="msg">{message || 'defaultMessage'}</div> | ||
} | ||
} | ||
Msg.propTypes = { message: PropTypes.string, didMount: PropTypes.func } | ||
@@ -24,3 +30,3 @@ describe('Tunnel', () => { | ||
<TunnelPlaceholder id={TUNNEL_ID}> | ||
{({ message }) => <span>{message}</span>} | ||
{({ message }) => <Msg message={message} />} | ||
</TunnelPlaceholder> | ||
@@ -40,3 +46,3 @@ <Tunnel id={TUNNEL_ID} {...props} /> | ||
<Tunnel id={TUNNEL_ID}> | ||
<span>{props.message}</span> | ||
<Msg message={props.message} /> | ||
</Tunnel> | ||
@@ -89,3 +95,3 @@ </div> | ||
<TunnelPlaceholder id={TUNNEL_ID}> | ||
{({ message }) => <span>{message || 'Empty'}</span>} | ||
{({ message }) => <Msg message={message || 'Empty'} />} | ||
</TunnelPlaceholder> | ||
@@ -109,3 +115,3 @@ </TunnelProvider>, | ||
const Component = ( | ||
{ msg, visible }, //eslint-disable-line | ||
{ msg, visible, didMount }, //eslint-disable-line | ||
) => ( | ||
@@ -115,3 +121,5 @@ <TunnelProvider> | ||
<TunnelPlaceholder id={TUNNEL_ID}> | ||
{({ message }) => <span>{message || 'Empty'}</span>} | ||
{({ message }) => ( | ||
<Msg message={message || 'Empty'} didMount={didMount} /> | ||
)} | ||
</TunnelPlaceholder> | ||
@@ -145,7 +153,16 @@ {visible && <Tunnel id={TUNNEL_ID} message={msg} />} | ||
}) | ||
it('should keep children mounted on re-render', () => { | ||
let mountSpy = jest.fn() | ||
const wrapper = mount( | ||
<Component msg={msg1} visible didMount={mountSpy} />, | ||
) | ||
wrapper.setProps({ msg: msg2 }) | ||
expect(mountSpy).toHaveBeenCalledTimes(1) | ||
}) | ||
}) | ||
function assertTunnelPlaceholderContent(wrapper, expectedContent) { | ||
expect(wrapper.find(TunnelPlaceholder).text()).toEqual(expectedContent) | ||
expect(wrapper.find(Msg).text()).toEqual(expectedContent) | ||
} | ||
}) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
197675
671
86
20