├── .babelrc
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── lib
├── URLProvider.js
├── connectURL.js
├── index.js
└── parser.js
├── package.json
└── test
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-es2015-modules-commonjs",
4 | "transform-es2015-arrow-functions",
5 | "transform-es2015-parameters",
6 | "transform-object-assign",
7 | "transform-es2015-classes",
8 | "transform-class-properties",
9 | "transform-es2015-spread",
10 | "transform-react-constant-elements",
11 | "transform-react-inline-elements"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | build
36 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | test
4 | logs
5 | *.log
6 | .babelrc
7 | .eslintrc
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Platzi.com
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of react-url nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-url
2 | A React.js High-Order Component and decorator for parsing and resolving URLs inside your application.
3 |
4 | ## Installation
5 | ```bash
6 | npm i -S react react-url
7 | ```
8 |
9 | ## API
10 | ### URLProvider
11 | ```javascript
12 | import { render } from 'react-dom';
13 | import { URLProvider } from 'react-url';
14 |
15 | import App from './containers/App';
16 |
17 | const urls = {
18 | profile: '/profile/:username/',
19 | };
20 |
21 | render(
22 |
23 |
24 | ,
25 | document.body,
26 | );
27 | ```
28 | * `URLProvider` is a High-Order Component.
29 | * `URLProvider` expect only one property named `urls`.
30 | * `urls` should be an object where the keys are the URLs names and the values are the unparsed url using the syntax of Express.js.
31 |
32 | ### connectURL
33 | ```javascript
34 | import React, { Component } from 'react';
35 | import { connectURL } from 'react-url';
36 |
37 | function mapURLToProps(getURL, props) {
38 | return {
39 | profileURL: getURL('profile', { username: props.username }),
40 | };
41 | }
42 |
43 | @connectURL(mapURLToProps)
44 | class UserData extends Component { ... }
45 |
46 | export default UserData;
47 | ```
48 | * The `connectURL` argument (`mapURLToProps`) it's optional.
49 | * If you don't supply it then it will add the `getURL` function as a property.
50 | * The `mapURLToProps` function will receive the `getURL` function and `props` object as parameter and should return an object.
51 | * The `getURL` function receive the URL name and an object with the parameters to use in it and return the parsed URL.
52 | * You can use it as a decorator (like the example above) or just as a function and send them the component to connect.
53 |
54 | ### parser
55 | ```javascript
56 | import { parser } from 'react-url';
57 |
58 | const urls = {
59 | profile: '/profile/:username/',
60 | };
61 |
62 | const profileURL = parser(urls, 'profile', {
63 | username: 'sergiodxa',
64 | });
65 | ```
66 | * This is a Low-Level API and is used internally for the `connectURL` decorator, it's not expected that you use it directly.
67 | * `parser` receive as arguments the `urls` map, the URL name and the options/parameters object.
68 | * It will return the final parsed url string.
69 |
--------------------------------------------------------------------------------
/lib/URLProvider.js:
--------------------------------------------------------------------------------
1 | import { Component, Children, PropTypes } from 'react';
2 |
3 | const propTypes = {
4 | urls: PropTypes.object,
5 | children: PropTypes.element.isRequired,
6 | };
7 | const defaultProps = {
8 | urls: {},
9 | };
10 | const childContextTypes = {
11 | urls: PropTypes.object,
12 | };
13 |
14 | /**
15 | * Set URLs object to React application context
16 | * @param {Object} urls URL to set
17 | * @param {Class} children Application component
18 | */
19 | class UrlProvider extends Component {
20 | getChildContext() {
21 | return {
22 | urls: this.props.urls,
23 | };
24 | }
25 |
26 | render() {
27 | return Children.only(this.props.children);
28 | }
29 | }
30 |
31 | UrlProvider.propTypes = propTypes;
32 | UrlProvider.defaultProps = defaultProps;
33 | UrlProvider.childContextTypes = childContextTypes;
34 |
35 | export default UrlProvider;
36 |
--------------------------------------------------------------------------------
/lib/connectURL.js:
--------------------------------------------------------------------------------
1 | import { createFactory, PropTypes } from 'react';
2 |
3 | import parser from './parser';
4 |
5 | const contextTypes = {
6 | urls: PropTypes.object,
7 | };
8 |
9 | /**
10 | * Create decorator to connect a React component to the URLs
11 | * @param {Function} mapURLToProps Map function for URLs
12 | * @returns {Function} Decorator function
13 | */
14 | function connectURL(mapURLToProps = null) {
15 | /**
16 | * URL decorator function
17 | * @param {Class} Target Component to decorate
18 | * @returns {Class} Decorated component
19 | */
20 | function decorator(Target) {
21 | /**
22 | * Wrapper component
23 | * @param {Object} props Component properties
24 | * @param {Object} context Application context
25 | * @returns {VDOM} Rendered component
26 | */
27 | function Connected(props, context) {
28 | /**
29 | * Get specified URL given the received options
30 | * @param {String} name URL name
31 | * @param {Object} opts URL parameters
32 | * @returns {String} Parsed URL
33 | */
34 | function getURL(name, opts = undefined) {
35 | return parser(context.urls, name, opts);
36 | }
37 |
38 | /**
39 | * Get mapped (or not) properties
40 | * @returns {Object} Child properties
41 | */
42 | function getProps() {
43 | if (mapURLToProps) {
44 | return Object.assign(mapURLToProps(getURL, props), props);
45 | }
46 | return Object.assign({}, props, { getURL });
47 | }
48 |
49 | return createFactory(Target)(getProps());
50 | }
51 |
52 | Connected.contextTypes = contextTypes;
53 |
54 | return Connected;
55 | }
56 |
57 | return decorator;
58 | }
59 |
60 | export default connectURL;
61 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import parser from './parser';
2 | import URLProvider from './URLProvider';
3 | import connectURL from './connectURL';
4 |
5 | export default {
6 | parser,
7 | URLProvider,
8 | connectURL,
9 | };
10 |
--------------------------------------------------------------------------------
/lib/parser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Parse url
3 | * @param {Object} urls URLs map to use
4 | * @param {String} name URL name to parse
5 | * @param {Object} opts Parameters for the URL
6 | * @returns {String} Parsed URL
7 | */
8 | function parser(urls, name, opts = undefined) {
9 | const url = urls[name];
10 |
11 | if (!opts) return url;
12 |
13 | return url
14 | .split('/')
15 | .map(key => {
16 | if (key[0] !== ':') return key;
17 | return opts[key.slice(1, key.length)];
18 | })
19 | .join('/');
20 | }
21 |
22 | export default parser;
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-url",
3 | "version": "1.0.1",
4 | "description": "URL resolver for React.js, like the Django url templatetag",
5 | "main": "build/index.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "scripts": {
10 | "lint": "eslint lib/index.js",
11 | "prebuild": "npm run lint",
12 | "build": "babel lib --out-dir build",
13 | "pretest": "npm run build",
14 | "test": "babel-node test/index.js | tap-spec",
15 | "prepublish": "npm run test"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/PlatziDev/react-url.git"
20 | },
21 | "keywords": [
22 | "react",
23 | "url",
24 | "resolver",
25 | "decorator"
26 | ],
27 | "author": "Sergio Daniel Xalambrí (http://sergio.xalambri.com.ar/)",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/PlatziDev/react-url/issues"
31 | },
32 | "homepage": "https://github.com/PlatziDev/react-url#readme",
33 | "devDependencies": {
34 | "babel": "6.5.2",
35 | "babel-cli": "6.7.5",
36 | "babel-core": "6.7.6",
37 | "babel-eslint": "6.0.2",
38 | "babel-plugin-transform-class-properties": "6.6.0",
39 | "babel-plugin-transform-es2015-arrow-functions": "6.5.2",
40 | "babel-plugin-transform-es2015-classes": "6.6.5",
41 | "babel-plugin-transform-es2015-modules-commonjs": "6.7.4",
42 | "babel-plugin-transform-es2015-parameters": "6.7.0",
43 | "babel-plugin-transform-es2015-spread": "6.6.5",
44 | "babel-plugin-transform-object-assign": "6.5.0",
45 | "babel-plugin-transform-react-constant-elements": "6.5.0",
46 | "babel-plugin-transform-react-inline-elements": "6.6.5",
47 | "cheerio": "0.20.0",
48 | "eslint": "2.8.0",
49 | "eslint-config-airbnb": "7.0.0",
50 | "eslint-plugin-jsx-a11y": "0.6.2",
51 | "eslint-plugin-react": "4.3.0",
52 | "react-dom": "15.0.1",
53 | "tap-spec": "4.1.1",
54 | "tape": "4.5.1"
55 | },
56 | "dependencies": {
57 | "react": "^15.0.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import test from 'tape';
2 | import cheerio from 'cheerio';
3 |
4 | import {
5 | Component,
6 | createElement,
7 | createFactory,
8 | PropTypes,
9 | } from 'react';
10 | import { renderToString } from 'react-dom/server';
11 |
12 | import parser from '../build/parser.js';
13 | import URLProvider from '../build/URLProvider';
14 | import connectURL from '../build/connectURL';
15 |
16 |
17 | const urls = {
18 | home: '/',
19 | discussion: '/discussion/:slug/',
20 | random: '/random/:param1/rand/:param2/',
21 | };
22 |
23 |
24 | class App extends Component {
25 | render() {
26 | return createElement('a', { href: this.props.randomURL }, 'click me!');
27 | }
28 | }
29 | App.propTypes = {
30 | randomURL: PropTypes.string,
31 | };
32 |
33 |
34 | test('url parser', t => {
35 | t.plan(3);
36 |
37 | const homeUrl = parser(urls, 'home');
38 | const discussionUrl = parser(urls, 'discussion', { slug: 'mi-discusion' });
39 | const randomUrl = parser(urls, 'random', {
40 | param1: '123',
41 | param2: 456,
42 | });
43 |
44 | t.equals(
45 | homeUrl,
46 | '/',
47 | 'it should return the url without parameters'
48 | );
49 |
50 | t.equals(
51 | discussionUrl,
52 | '/discussion/mi-discusion/',
53 | 'it should return url with only one parameter'
54 | );
55 |
56 | t.equals(
57 | randomUrl,
58 | '/random/123/rand/456/',
59 | 'it should return the url with multiple parameters and a number as value'
60 | );
61 | });
62 |
63 |
64 | test('connected component', t => {
65 | t.plan(1);
66 |
67 | function mapURLToProps(getURL) {
68 | return {
69 | randomURL: getURL('random', {
70 | param1: 123,
71 | param2: '456',
72 | }),
73 | };
74 | }
75 |
76 | const ConnectedApp = connectURL(mapURLToProps)(App);
77 |
78 | function ContainerApp() {
79 | return createElement(
80 | URLProvider,
81 | {
82 | urls,
83 | },
84 | createFactory(ConnectedApp)({ key: 1 })
85 | );
86 | }
87 |
88 | const html = renderToString(createFactory(ContainerApp)());
89 |
90 | const $ = cheerio.load(html);
91 |
92 | t.equals(
93 | $('a').attr('href'),
94 | '/random/123/rand/456/',
95 | 'it should return an anchor with the parsed url as href attribute'
96 | );
97 | });
98 |
--------------------------------------------------------------------------------