Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update repo to new React 18 and use new context API #32

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
# 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)
# @matspa - React Tunnels

Render React components in placeholders that are placed somewhere else in the component tree.

- ⚠️ This is a fork of original [react-tunnels](https://www.npmjs.com/package/react-tunnels) with new **React v18 context API**

## Install

```
yarn add react-tunnels
yarn add @matspa/react-tunnels
```

### Why

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*.
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

Expand All @@ -31,7 +33,7 @@ render(
This will be rendered on the placeholder 👆
</Tunnel>
</div>
</TunnelProvider>
</TunnelProvider>,
)
```

Expand All @@ -44,11 +46,13 @@ It's easy to build a breadcrumb using the prop `multiple` in the `TunnelPlacehol
```jsx
const Breadcrumbs = () => (
<TunnelPlaceholder id="breadcrumb" multiple>
{({ items }) => (
{({ items }) =>
items.map(({ children, href }) => (
<span><a href={href}>{children}</a></span>
<span>
<a href={href}>{children}</a>
</span>
))
)}
}
</TunnelPlaceholder>
)

Expand All @@ -64,8 +68,10 @@ render(
<Breadcrumbs />
{/* Somewhere else in children */}
<Breadcrumb url="/products">Products</Breadcrumb>
<Breadcrumb url="/products/123">Product <strong>123</strong></Breadcrumb>
</TunnelProvider>
<Breadcrumb url="/products/123">
Product <strong>123</strong>
</Breadcrumb>
</TunnelProvider>,
)
```

Expand All @@ -78,7 +84,7 @@ Check the live example [here](https://codesandbox.io/s/0ym0n37jnl)

## 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.
This project has been originally developed by [Javi Velasco](https://twitter.com/javivelasco) and updated by [matspa](https://github.com/spadettomattia) due to deprecation of `contextTypes` API in newer version of React.

## License

Expand Down
20 changes: 11 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"name": "react-tunnels",
"version": "1.1.0",
"description": "A easy way to communicate rendering logic and data to ancestor components in React.",
"name": "@matspa/react-tunnels",
"version": "2.0.1",
"description": "A fork of original react-tunnels updated to new context API",
"main": "./lib/index.js",
"repository": "javivelasco/react-tunnels",
"repository": {
"type": "git",
"url": "git+https://github.com/spadettomattia/react-tunnels.git#fork/react-context-api"
},
"scripts": {
"build": "babel ./src --out-dir ./lib",
"build:watch": "babel ./src --out-dir ./lib --watch",
Expand All @@ -15,8 +18,7 @@
"keywords": [
"react",
"react-tunnels",
"portals",
"components"
"portals"
],
"author": "Javi Velasco <[email protected]> (http://javivelasco.com/)",
"license": "MIT",
Expand All @@ -38,12 +40,12 @@
"jest": "23.5.0",
"prettier": "1.14.2",
"prop-types": "15.6.2",
"react": "16.4.2",
"react-dom": "16.4.2",
"react": "18.3.1",
"react-dom": "18.3.1",
"rimraf": "2.6.2"
},
"peerDependencies": {
"react": "^0.14.9 || ^15.3.0 || ^16.0.0"
"react": "^18.0.0"
},
"babel": {
"presets": [
Expand Down
51 changes: 18 additions & 33 deletions src/Tunnel.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,27 @@
import { Component } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import uniqueId from './uniqueId'
import { TunnelContext } from './TunnelProvider'

class Tunnel extends Component {
static propTypes = {
id: PropTypes.string,
render: PropTypes.func,
}

static contextTypes = {
tunnelState: PropTypes.object,
}

itemId = uniqueId()
Tunnel.propTypes = {
id: PropTypes.string,
}

componentDidMount() {
this.setTunnelProps(this.props)
}
export default function Tunnel({ id, ...props }) {
const { tunnelState } = React.useContext(TunnelContext)

componentDidUpdate() {
this.setTunnelProps(this.props)
}
const itemId = React.useMemo(() => uniqueId(), [])

componentWillUnmount() {
const { id } = this.props
const { tunnelState } = this.context
tunnelState.setTunnelProps(id, this.itemId, null)
}
React.useEffect(
() => {
tunnelState.setTunnelProps(id, itemId, props)

setTunnelProps(newProps) {
const { id, ...props } = newProps
const { tunnelState } = this.context
tunnelState.setTunnelProps(id, this.itemId, props)
}
return () => {
tunnelState.setTunnelProps(id, itemId, null)
}
},
[id, itemId, props, tunnelState],
)

render() {
return null
}
return null
}

export default Tunnel
101 changes: 45 additions & 56 deletions src/TunnelPlaceholder.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,55 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, { Component, Fragment } from 'react'
import { TunnelContext } from './TunnelProvider'

class TunnelPlaceholder extends Component {
static propTypes = {
children: PropTypes.func,
component: PropTypes.oneOfType([PropTypes.node, PropTypes.symbol]),
id: PropTypes.string.isRequired,
multiple: PropTypes.bool,
}

static defaultProps = {
component: Fragment,
}

static contextTypes = {
tunnelState: PropTypes.object,
}

componentDidMount() {
const { id } = this.props
const { tunnelState } = this.context
tunnelState.subscribe(id, this.handlePropsChange)
}

componentWillUnmount() {
const { id } = this.props
const { tunnelState } = this.context
tunnelState.unsubscribe(id, this.handlePropsChange)
}
TunnelPlaceholder.propTypes = {
children: PropTypes.func,
Component: PropTypes.oneOfType([PropTypes.node, PropTypes.symbol]),
id: PropTypes.string.isRequired,
multiple: PropTypes.bool,
}

handlePropsChange = () => {
this.forceUpdate()
}
export default function TunnelPlaceholder({
id,
children,
Component = React.Fragment,
multiple,
}) {
const { tunnelState } = React.useContext(TunnelContext)
const [tunnelProps, setTunnelProps] = React.useState(() =>
tunnelState.getTunnelProps(id),
)

React.useEffect(
() => {
const handlePropsChange = () => {
setTunnelProps(tunnelState.getTunnelProps(id))
}

render() {
const { tunnelState } = this.context
const {
id,
children: renderChildren,
component: Tag,
multiple,
} = this.props
const tunnelProps = tunnelState.getTunnelProps(id)
tunnelState.subscribe(id, handlePropsChange)

if (renderChildren) {
if (Array.isArray(tunnelProps) || multiple) {
return !tunnelProps
? renderChildren({ items: [] })
: renderChildren({
items: Array.isArray(tunnelProps) ? tunnelProps : [tunnelProps],
})
} else {
return renderChildren(tunnelProps || {})
return () => {
tunnelState.unsubscribe(id, handlePropsChange)
}
},
[id, tunnelState],
)

if (children) {
if (Array.isArray(tunnelProps) || multiple) {
return !tunnelProps
? children({ items: [] })
: children({
items: Array.isArray(tunnelProps) ? tunnelProps : [tunnelProps],
})
} else {
return children(tunnelProps || {})
}
}

if (!tunnelProps) {
return null
}

return <Tag>{tunnelProps.children}</Tag>
if (!tunnelProps) {
return null
}
}

export default TunnelPlaceholder
return <Component>{tunnelProps.children}</Component>
}
34 changes: 13 additions & 21 deletions src/TunnelProvider.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Children, Component } from 'react'
import TunnelState from './TunnelState'
import useTunnelState from './useTunnelState'

class TunnelProvider extends Component {
static propTypes = {
children: PropTypes.node,
}
export const TunnelContext = React.createContext()

static childContextTypes = {
tunnelState: PropTypes.object,
}
export default function TunnelProvider({ children }) {
const tunnelState = React.useMemo(() => useTunnelState(), [])

tunnelState = new TunnelState()

getChildContext() {
return {
tunnelState: this.tunnelState,
}
}

render() {
return Children.only(this.props.children)
}
return (
<TunnelContext.Provider value={{ tunnelState }}>
{children}
</TunnelContext.Provider>
)
}

export default TunnelProvider
TunnelProvider.propTypes = {
children: PropTypes.node,
}
54 changes: 0 additions & 54 deletions src/TunnelState.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { default as Tunnel } from './Tunnel'
export { default as TunnelPlaceholder } from './TunnelPlaceholder'
export { default as TunnelProvider } from './TunnelProvider'
export { default as TunnelState } from './TunnelState'
export { default as useTunnelState } from './useTunnelState'
Loading