├── docs ├── README.md ├── Gemfile ├── index.md ├── _sass │ ├── _tiles.scss │ └── type-on-strap.scss ├── _config.yml ├── _layouts │ ├── page.html │ └── home.html ├── assets │ ├── redux-logo.svg │ └── react-logo.svg ├── _includes │ └── toc.html ├── pages │ ├── redux-ramda.md │ └── react-ramda.md └── Gemfile.lock ├── src ├── Ex01 │ ├── index.js │ ├── README.md │ ├── Loading.js │ └── Ex01.js ├── Ex02 │ ├── index.js │ ├── README.md │ ├── Content.js │ └── Ex02.js ├── Ex03 │ ├── index.js │ ├── README.md │ ├── Content.js │ └── Ex03.js ├── Ex04 │ ├── index.js │ ├── README.md │ ├── Ex04.js │ └── Section.js ├── index.css └── index.js ├── .eslintrc.json ├── public ├── favicon.ico ├── manifest.json └── index.html ├── README.md ├── .editorconfig ├── .gitignore └── package.json /docs/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Ex01/index.js: -------------------------------------------------------------------------------- 1 | export * from './Ex01'; -------------------------------------------------------------------------------- /src/Ex02/index.js: -------------------------------------------------------------------------------- 1 | export * from './Ex02'; -------------------------------------------------------------------------------- /src/Ex03/index.js: -------------------------------------------------------------------------------- 1 | export * from './Ex03'; -------------------------------------------------------------------------------- /src/Ex04/index.js: -------------------------------------------------------------------------------- 1 | export * from './Ex04'; -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | gem "github-pages", group: :jekyll_plugins -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | hide: true 4 | --- 5 | 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "eslint-config-react-app" 4 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tommmyy/ramda-react-redux-patterns/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/Ex01/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | In this example we show how to use `R.always` to generate static component. -------------------------------------------------------------------------------- /src/Ex03/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | In this example we show how `R.cond` can be used as HoC for conditional render. -------------------------------------------------------------------------------- /src/Ex02/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | In this example we show how `R.ifElse` can be used as HoC for conditional render. -------------------------------------------------------------------------------- /src/Ex04/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | In this example we show how `R.pick` can be used as HoC for filtering the valid properties. -------------------------------------------------------------------------------- /src/Ex01/Loading.js: -------------------------------------------------------------------------------- 1 | import { always } from 'ramda'; 2 | 3 | const Loading = always("Loading..."); 4 | 5 | export { Loading }; 6 | -------------------------------------------------------------------------------- /src/Ex01/Ex01.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Loading } from './Loading'; 3 | 4 | const Ex01 = () => ; 5 | 6 | export { Ex01 }; 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ramda: patterns for React and Redux 2 | 3 | [➡️ tommmyy.github.io/ramda-react-redux-patterns ⬅️](https://tommmyy.github.io/ramda-react-redux-patterns/) 4 | 5 | **⚠️ Work in progress ⚠️** 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import { Ex02 } from './Ex02'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | 6 | [*.md] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /docs/_sass/_tiles.scss: -------------------------------------------------------------------------------- 1 | .tiles { 2 | display: flex; 3 | text-align: center; 4 | 5 | &-tile { 6 | width: 50%; 7 | display: inline-block; 8 | } 9 | 10 | .img { 11 | width: 100%; 12 | height: auto; 13 | } 14 | } -------------------------------------------------------------------------------- /src/Ex04/Ex04.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Section } from './Section'; 3 | 4 | class Ex04 extends Component { 5 | render() { 6 | return ( 7 |
8 | Ramda rulez! 9 |
10 | ); 11 | } 12 | } 13 | 14 | export { Ex04 }; 15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | .sass-cache 24 | NOTES.md 25 | /docs/_site -------------------------------------------------------------------------------- /src/Ex02/Content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { always, prop, ifElse } from 'ramda'; 3 | 4 | const Loading = always("Loading..."); 5 | const Section = ({ content }) =>
{content}
; 6 | 7 | // Component -> Component 8 | const withLoading = ifElse(prop('loading'), Loading) 9 | 10 | // const Content = (props) => props.loading ? :
11 | const Content = withLoading(Section) 12 | 13 | export { Content }; 14 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | #baseurl: "" 2 | #url: "" 3 | 4 | remote_theme: Sylhare/Type-on-Strap 5 | 6 | theme_settings: 7 | title: Use Ramda with React and Redux 8 | avatar: assets/img/triangle.svg 9 | description: Patterns and techniques for Ramda and React with Ramda library 10 | github: "tommmyy" 11 | google_fonts: Source+Sans+Pro:400,700,700italic,400italic 12 | footer_text: 2018 13 | 14 | title: React and Ramda patterns 15 | kramdown: 16 | parse_block_html: true 17 | -------------------------------------------------------------------------------- /src/Ex03/Content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { T, always, prop, cond, isEmpty, map, o } from 'ramda'; 3 | 4 | const Loading = always("Loading..."); 5 | const Missing = always("No results."); 6 | 7 | const Section = ({ items, children }) =>
{map(children)(items)}
; 8 | 9 | // Component -> Component 10 | const Content = cond([ 11 | [prop('loading'), Loading], 12 | [o(isEmpty, prop('items')), Missing], 13 | [T, Section], 14 | ]); 15 | 16 | export { Content }; 17 | -------------------------------------------------------------------------------- /src/Ex02/Ex02.js: -------------------------------------------------------------------------------- 1 | import { complement } from 'ramda'; 2 | import React, { Component } from 'react'; 3 | import { Content } from './Content'; 4 | 5 | class Ex02 extends Component { 6 | state = { content: null }; 7 | 8 | componentDidMount() { 9 | setInterval(() => this.setState({ content: "Loaded content." }), 2000) 10 | } 11 | 12 | render() { 13 | const { content } = this.state; 14 | 15 | return ; 16 | } 17 | } 18 | 19 | export { Ex02 }; 20 | -------------------------------------------------------------------------------- /docs/_sass/type-on-strap.scss: -------------------------------------------------------------------------------- 1 | @import 'base/variables'; 2 | 3 | // External 4 | @import 'external/reset'; 5 | @import 'external/syntax'; 6 | 7 | // Base 8 | @import 'base/global'; 9 | @import 'base/utility'; 10 | 11 | // Posts 12 | @import 'layouts/posts'; 13 | @import 'layouts/index'; 14 | @import 'layouts/page'; 15 | @import 'layouts/tags'; 16 | @import 'layouts/search'; 17 | @import 'layouts/portfolio'; 18 | 19 | // Includes 20 | @import 'includes/footer'; 21 | @import 'includes/post_nav'; 22 | @import 'includes/navbar'; 23 | 24 | @import 'tiles'; 25 | 26 | -------------------------------------------------------------------------------- /src/Ex03/Ex03.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Content } from './Content'; 3 | 4 | const renderItem = (x) =>
{x}!
; 5 | 6 | class Ex03 extends Component { 7 | state = { loading: true, items: null }; 8 | 9 | componentDidMount() { 10 | setInterval(() => { 11 | // this.setState({ items: [], loading: false }); 12 | this.setState({ items:["Pichu", "Pikachu", "Raichu"], loading: false }) 13 | }, 2000); 14 | } 15 | 16 | render() { 17 | const { items, loading } = this.state; 18 | 19 | return {renderItem}; 20 | } 21 | } 22 | 23 | export { Ex03 }; 24 | -------------------------------------------------------------------------------- /docs/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 |
7 |

8 | {{ page.title }} 9 |

10 | 11 | {% if page.subtitle %} 12 |

{{ page.subtitle }}

13 | {% endif %} 14 |
15 | 16 | {% include toc.html html=content h_min=2 %} 17 | 18 |
{{ content }}
19 | 20 | 21 | 22 | {% capture tag_list %}{{ page.tags | join: "|"}}{% endcapture %} 23 | {% include tags_list.html tags=tag_list %} 24 | 25 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ramda-react-redux-patterns", 3 | "version": "1.0.0", 4 | "description": "Patterns for Ramda, Redux with Ramda", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/tommmyy/react-redux-ramda-patterns.git" 8 | }, 9 | "keywords": [ 10 | "javascript", 11 | "guide", 12 | "programming patterns", 13 | "functional programming", 14 | "ramda", 15 | "react", 16 | "redux" 17 | ], 18 | "author": "Tomas Konrady ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/tommmyy/react-redux-ramda-patterns/issues" 22 | }, 23 | "private": true, 24 | "dependencies": { 25 | "ramda": "^0.25.0", 26 | "ramda-extension": "^0.3.5", 27 | "react": "^16.3.1", 28 | "react-dom": "^16.3.1", 29 | "react-scripts": "1.1.4" 30 | }, 31 | "scripts": { 32 | "start": "react-scripts start", 33 | "build": "react-scripts build", 34 | "test": "react-scripts test --env=jsdom", 35 | "eject": "react-scripts eject" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Ex04/Section.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import * as R from 'ramda'; 4 | 5 | const SimpleSection = ({ heading, childrenLength, children, ...rest }) => ( 6 |
7 |

{heading}

8 |
{children}
9 |
{childrenLength}
10 |
11 | ); 12 | 13 | SimpleSection.propTypes = { 14 | heading: PropTypes.string, 15 | children: PropTypes.string, 16 | childrenLength: PropTypes.number, 17 | className: PropTypes.string, 18 | }; 19 | 20 | const pickByPropTypes = R.useWith(R.pick, [R.o(R.keys, R.prop('propTypes'))]); 21 | 22 | //const withValidProps_ = (C) => (props) => 23 | 24 | // Props -> Component -> Element 25 | const renderComponent = R.curryN(2, React.createElement); 26 | 27 | // const withValidProps = (C) => (ps) => renderComponent(C, pickByPropTypes(C)(ps)) 28 | // const withValidProps = (C) => (ps) => renderComponent(C, pickByPropTypes(C)(ps)) 29 | //const Section = withValidProps(SimpleSection); 30 | 31 | // mapProps :: (a -> a) -> Component -> Component 32 | // const mapProps = R.curry((mapping, C) => (props) => renderComponent(C)(mapping(props))) 33 | // const mapProps = R.curry((mapping, C) => R.o(renderComponent(C), mapping)) 34 | const mapProps = R.flip(R.useWith(R.o, [renderComponent, R.identity])); 35 | 36 | const addLength = ({ children }) => ({ childrenLengthgth: R.length(children) }) 37 | 38 | const computeProps = R.converge(R.merge, [R.nthArg(1), R.call]); 39 | 40 | const setDisplayName = (displayName) => R.tap((C) => C.displayName = displayName) 41 | 42 | const Section = R.compose( 43 | setDisplayName("Section"), 44 | mapProps( 45 | R.compose( 46 | R.evolve({ heading: R.toUpper }), 47 | computeProps(addLength), 48 | pickByPropTypes(SimpleSection) 49 | ) 50 | ) 51 | )(SimpleSection); 52 | 53 | 54 | export { Section }; -------------------------------------------------------------------------------- /docs/assets/redux-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/_layouts/home.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 | {% if site.theme_settings.header_text %} 7 |
9 | {{ site.theme_settings.header_text }} 10 |
11 | {% endif %} 12 | 13 | 27 | 28 |
29 | {% for post in paginator.posts %} 30 | 56 | {% endfor %} 57 |
58 | 59 | {% if paginator.total_pages > 1 %} 60 | 74 | {% endif %} 75 |
-------------------------------------------------------------------------------- /docs/assets/react-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/_includes/toc.html: -------------------------------------------------------------------------------- 1 | {% capture tocWorkspace %} 2 | {% comment %} 3 | Version 1.0.4 4 | https://github.com/allejo/jekyll-toc 5 | 6 | "...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe 7 | 8 | Usage: 9 | {% include toc.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %} 10 | 11 | Parameters: 12 | * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll 13 | 14 | Optional Parameters: 15 | * sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC 16 | * class (string) : '' - a CSS class assigned to the TOC 17 | * id (string) : '' - an ID to assigned to the TOC 18 | * h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored 19 | * h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored 20 | * ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list 21 | * item_class (string) : '' - add custom class for each list item; has support for '%level%' placeholder, which is the current heading level 22 | 23 | Output: 24 | An ordered or unordered list representing the table of contents of a markdown block. This snippet will only generate the table of contents and will NOT output the markdown given to it 25 | {% endcomment %} 26 | 27 | {% capture my_toc %}{% endcapture %} 28 | {% assign orderedList = include.ordered | default: false %} 29 | {% assign minHeader = include.h_min | default: 1 %} 30 | {% assign maxHeader = include.h_max | default: 6 %} 31 | {% assign nodes = include.html | split: ' maxHeader %} 44 | {% continue %} 45 | {% endif %} 46 | 47 | {% if firstHeader %} 48 | {% assign firstHeader = false %} 49 | {% assign minHeader = headerLevel %} 50 | {% endif %} 51 | 52 | {% assign indentAmount = headerLevel | minus: minHeader | add: 1 %} 53 | {% assign _workspace = node | split: '' | first }}>{% endcapture %} 60 | {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} 61 | 62 | {% assign space = '' %} 63 | {% for i in (1..indentAmount) %} 64 | {% assign space = space | prepend: ' ' %} 65 | {% endfor %} 66 | 67 | {% unless include.item_class == blank %} 68 | {% capture listItemClass %}{:.{{ include.item_class | replace: '%level%', headerLevel }}}{% endcapture %} 69 | {% endunless %} 70 | 71 | {% capture my_toc %}{{ my_toc }} 72 | {{ space }}{{ listModifier }} {{ listItemClass }} [{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}](#{{ html_id }}){% endcapture %} 73 | 74 | {% endfor %} 75 | 76 | {% if include.class %} 77 | {% capture my_toc %}{:.{{ include.class }}} 78 | {{ my_toc | lstrip }}{% endcapture %} 79 | {% endif %} 80 | 81 | {% if include.id %} 82 | {% capture my_toc %}{: #{{ include.id }}} 83 | {{ my_toc | lstrip }}{% endcapture %} 84 | {% endif %} 85 | {% endcapture %}{% assign tocWorkspace = '' %}{{ my_toc | markdownify | strip }} -------------------------------------------------------------------------------- /docs/pages/redux-ramda.md: -------------------------------------------------------------------------------- 1 | # Redux patterns 2 | 3 | ## Patterns 4 | 5 | ### 1. Selectors 6 | 7 | ```js 8 | const items = (state) => fromEntities.getItems(state.entities) 9 | ``` 10 | 11 | ```js 12 | const items = R.o(fromEntities.getItems, R.path(['entities']); 13 | 14 | const isLoading = R.path(['ui', 'loading']); 15 | ``` 16 | 17 | ### 2. Memoized Selectors 18 | 19 | ```js 20 | const createSelector = (path) => R.memoizeWith(R.identity, R.path(path)) 21 | 22 | // ... 23 | 24 | const isLoading = createSelector(['ui', 'loading']) 25 | ``` 26 | 27 | ### 3. Mapping state 28 | 29 | ```js 30 | // Before Ramda 31 | const mapStateToProps = (state, ownProps) => ({ 32 | items: getItems(state, ownProps), 33 | isLoading: isLoading(state, ownProps), 34 | }) 35 | ``` 36 | 37 | ```js 38 | // After Ramda 39 | const mapStateToProps = R.applySpec({ 40 | items: getItems, 41 | isLoading: isLoading, 42 | }) 43 | ``` 44 | 45 | ### 4. Creating action creators 46 | 47 | We want to use following action creators in our application: 48 | 49 | ```js 50 | reset() // { type": "RESET" } 51 | increment(1) // { payload: 1, type: "INCREMENT" } 52 | fetchItems({ items: "some" }) 53 | // => { meta: { page: 0 }, payload: "some", type: "FETCH_ITEMS" } 54 | ``` 55 | 56 | * `reset` is simpliest action creator - it does not use any arguments 57 | * `increment` takes one argument that represents `payload` of the action 58 | * `fetchItems` is the most complex one: 59 | * computes `payload` from first argument and 60 | * adds `meta` attribute to the action 61 | 62 | --- 63 | 64 | We can introduce factory functions (`createAction`, `createConstantAction`, `createSimpleAction`), that can encapsulates creation of action creators. 65 | 66 | ```js 67 | // Before Ramda 68 | const createAction = (type, getPayload, getMeta) => 69 | (payload, meta) => ({ 70 | type, 71 | payload: getPayload(payload), 72 | meta: getMeta(meta), 73 | }); 74 | 75 | const createConstantAction = (type) => createAction( 76 | type, 77 | x => undefined, 78 | () => undefined 79 | ); 80 | 81 | const createSimpleAction = (type) => createAction( 82 | type, 83 | x => x, 84 | () => undefined 85 | ); 86 | 87 | // ... 88 | 89 | const reset = createConstantAction("RESET") 90 | const increment = createSimpleAction("INCREMENT"); 91 | const fetchItems = createAction( 92 | "FETCH_ITEMS", 93 | (x) => x.items, 94 | () => ({ page: 0 }) 95 | ) 96 | ``` 97 | 98 | *Note:* The above code is not quite ideal. Because call `reset()` gives you: 99 | ```js 100 | {"meta": undefined, "payload": undefined, "type": "RESET"} 101 | ``` 102 | 103 | Ramda version bellow solves the problem by filtering out unspecified values. 104 | 105 | ```js 106 | // After Ramda 107 | const createAction = R.curry( 108 | (type, getPayload, getMeta) => R.compose( 109 | R.reject(R.isNil), 110 | R.applySpec({ 111 | type: R.always(type), 112 | payload: getPayload, 113 | meta: getMeta, 114 | }) 115 | ) 116 | ); 117 | const createSimpleAction = createAction(R.__, R.identity, R.always(null)); 118 | const createContantAction = createAction(R.__, R.always(null), R.always(null)); 119 | 120 | // ... 121 | 122 | const reset = createContantAction("RESET") 123 | const increment = createSimpleAction("INCREMENT"); 124 | const fetchItems = createAction("FETCH_ITEMS", R.prop("items"), R.always({ page: 0 })) 125 | ``` 126 | 127 | ### 5. Replacing `switch` inside reducer 128 | 129 | ```js 130 | // Before Ramda: 131 | cosnt initialState = 0; 132 | const counter = (state = initialState, action) => 133 | switch (action.type) { 134 | case "INCREMENT": 135 | return state + action.payload; 136 | case "RESET": 137 | return initialState; 138 | default: 139 | return state; 140 | } 141 | ``` 142 | 143 | ```js 144 | // After Ramda 145 | 146 | // Firstly we introduce `switchReducer` function 147 | 148 | const isUndefined = R.o(R.equals("Undefined"), R.type); 149 | const overHead = R.over(R.lensIndex(0)); 150 | const toActionTypeEquals = (type) => R.flip(R.whereEq({ type })); 151 | 152 | const switchReducer = (rs, initialState) => R.compose( 153 | R.cond, 154 | R.prepend([isUndefined, R.always(initialState)]), 155 | R.append([R.T, R.identity]), 156 | R.map(overHead(toActionTypeEquals)) 157 | )(rs); 158 | 159 | //... 160 | 161 | // Than we can write every reducer with following convenient API: 162 | const initialState = 1 163 | const counter = switchReducer([ 164 | ["INCREMENT", (state, action) => state + action.payload], 165 | ["RESET", R.always(initialState)], 166 | ], initialState); 167 | 168 | // ... 169 | 170 | counter(undefined, {}) // 1 171 | counter(3, { type: "INCREMENT", payload: 2 }) // 5 172 | counter(3, { type: "RESET" }) // 1 173 | counter(3, { type: "LOAD_ITEMS" }) // 3 174 | ``` 175 | 176 | ### 6. Local State with `filteredReducer` 177 | 178 | In examples we will use following reducer: 179 | 180 | ```js 181 | const add = (state = 0, action) => action.type === "INCREMENT" ? state + 1 : state 182 | ``` 183 | 184 | Lets see following code as an introduction to the problem: 185 | 186 | ```js 187 | 188 | const root = combineReducers({ 189 | widgetA: add, 190 | widgetB: add 191 | }) 192 | 193 | // { widgetA: 0, widgetB: 0 } 194 | 195 | store.dispatch({ type: "INCREMENT" }) 196 | // { widgetA: 1, widgetB: 1 } 197 | 198 | ``` 199 | 200 | As we can see, after "INCREMENT" action, every slice of the state, that is managed by `add` reducer, will be incremented. 201 | Following pattern solves the problem of how to target the specific slice of state. 202 | 203 | Following most verbose solution uses `action.meta` to determine if the `add` reducer should be called: 204 | 205 | ```js 206 | const root = combineReducers({ 207 | widgetA: (state, action) => 208 | action.meta && action.meta.namespace === "@WIDGET-A" ? add(state, action) : state, 209 | widgetB:(state, action) => 210 | action.meta && action.meta.namespace === "@WIDGET-B" ? add(state, action) : state, 211 | }) 212 | 213 | // { widgetA: 0, widgetB: 0 } 214 | 215 | store.dispatch({ type: "INCREMENT", meta: { namespace: "@WIDGET-A" } }) 216 | // { widgetA: 1, widgetB: 0 } 217 | 218 | 219 | store.dispatch({ type: "INCREMENT", meta: { namespace: "@WIDGET-B" } }) 220 | // { widgetA: 1, widgetB: 1 } 221 | ``` 222 | 223 | Next, we introduce `filteredReducer` function: 224 | 225 | ```js 226 | // Before Ramda 227 | const filteredReducer = (pred, reducer) => 228 | (state, action) => 229 | pred(action) ? reducer(state, action) : state; 230 | 231 | const namespaceEquals = (ns) => (action) => action.meta && action.meta.namespace === ns 232 | 233 | // ... 234 | 235 | const root = combineReducers({ 236 | widgetA: filteredReducer(namespaceEquals("@WIDGET-A"), add), 237 | widgetB: filteredReducer(namespaceEquals("@WIDGET-B"), add), 238 | global: add, 239 | }) 240 | ``` 241 | 242 | ```js 243 | // After Ramda 244 | const filteredReducer = (pred, reducer) => R.cond([ 245 | [R.flip(pred), reducer], 246 | [R.T, R.nthArg(0)] 247 | ]) 248 | 249 | const namespaceEquals = R.pathEq(["meta", "namespace"]) 250 | 251 | // ... 252 | 253 | const root = combineReducers({ 254 | widgetA: filteredReducer(namespaceEquals("@WIDGET-A"), add), 255 | widgetB: filteredReducer(namespaceEquals("@WIDGET-B"), add), 256 | global: add, 257 | }) 258 | ``` 259 | -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | specs: 3 | activesupport (4.2.9) 4 | i18n (~> 0.7) 5 | minitest (~> 5.1) 6 | thread_safe (~> 0.3, >= 0.3.4) 7 | tzinfo (~> 1.1) 8 | addressable (2.5.2) 9 | public_suffix (>= 2.0.2, < 4.0) 10 | coffee-script (2.4.1) 11 | coffee-script-source 12 | execjs 13 | coffee-script-source (1.11.1) 14 | colorator (1.1.0) 15 | commonmarker (0.17.9) 16 | ruby-enum (~> 0.5) 17 | concurrent-ruby (1.0.5) 18 | em-websocket (0.5.1) 19 | eventmachine (>= 0.12.9) 20 | http_parser.rb (~> 0.6.0) 21 | ethon (0.11.0) 22 | ffi (>= 1.3.0) 23 | eventmachine (1.2.5) 24 | execjs (2.7.0) 25 | faraday (0.14.0) 26 | multipart-post (>= 1.2, < 3) 27 | ffi (1.9.23) 28 | forwardable-extended (2.6.0) 29 | gemoji (3.0.0) 30 | github-pages (181) 31 | activesupport (= 4.2.9) 32 | github-pages-health-check (= 1.4.0) 33 | jekyll (= 3.7.3) 34 | jekyll-avatar (= 0.5.0) 35 | jekyll-coffeescript (= 1.1.1) 36 | jekyll-commonmark-ghpages (= 0.1.5) 37 | jekyll-default-layout (= 0.1.4) 38 | jekyll-feed (= 0.9.3) 39 | jekyll-gist (= 1.5.0) 40 | jekyll-github-metadata (= 2.9.4) 41 | jekyll-mentions (= 1.3.0) 42 | jekyll-optional-front-matter (= 0.3.0) 43 | jekyll-paginate (= 1.1.0) 44 | jekyll-readme-index (= 0.2.0) 45 | jekyll-redirect-from (= 0.13.0) 46 | jekyll-relative-links (= 0.5.3) 47 | jekyll-remote-theme (= 0.2.3) 48 | jekyll-sass-converter (= 1.5.2) 49 | jekyll-seo-tag (= 2.4.0) 50 | jekyll-sitemap (= 1.2.0) 51 | jekyll-swiss (= 0.4.0) 52 | jekyll-theme-architect (= 0.1.1) 53 | jekyll-theme-cayman (= 0.1.1) 54 | jekyll-theme-dinky (= 0.1.1) 55 | jekyll-theme-hacker (= 0.1.1) 56 | jekyll-theme-leap-day (= 0.1.1) 57 | jekyll-theme-merlot (= 0.1.1) 58 | jekyll-theme-midnight (= 0.1.1) 59 | jekyll-theme-minimal (= 0.1.1) 60 | jekyll-theme-modernist (= 0.1.1) 61 | jekyll-theme-primer (= 0.5.3) 62 | jekyll-theme-slate (= 0.1.1) 63 | jekyll-theme-tactile (= 0.1.1) 64 | jekyll-theme-time-machine (= 0.1.1) 65 | jekyll-titles-from-headings (= 0.5.1) 66 | jemoji (= 0.9.0) 67 | kramdown (= 1.16.2) 68 | liquid (= 4.0.0) 69 | listen (= 3.1.5) 70 | mercenary (~> 0.3) 71 | minima (= 2.4.0) 72 | nokogiri (>= 1.8.1, < 2.0) 73 | rouge (= 2.2.1) 74 | terminal-table (~> 1.4) 75 | github-pages-health-check (1.4.0) 76 | addressable (~> 2.3) 77 | net-dns (~> 0.8) 78 | octokit (~> 4.0) 79 | public_suffix (~> 2.0) 80 | typhoeus (~> 1.3) 81 | html-pipeline (2.7.1) 82 | activesupport (>= 2) 83 | nokogiri (>= 1.4) 84 | http_parser.rb (0.6.0) 85 | i18n (0.9.5) 86 | concurrent-ruby (~> 1.0) 87 | jekyll (3.7.3) 88 | addressable (~> 2.4) 89 | colorator (~> 1.0) 90 | em-websocket (~> 0.5) 91 | i18n (~> 0.7) 92 | jekyll-sass-converter (~> 1.0) 93 | jekyll-watch (~> 2.0) 94 | kramdown (~> 1.14) 95 | liquid (~> 4.0) 96 | mercenary (~> 0.3.3) 97 | pathutil (~> 0.9) 98 | rouge (>= 1.7, < 4) 99 | safe_yaml (~> 1.0) 100 | jekyll-avatar (0.5.0) 101 | jekyll (~> 3.0) 102 | jekyll-coffeescript (1.1.1) 103 | coffee-script (~> 2.2) 104 | coffee-script-source (~> 1.11.1) 105 | jekyll-commonmark (1.2.0) 106 | commonmarker (~> 0.14) 107 | jekyll (>= 3.0, < 4.0) 108 | jekyll-commonmark-ghpages (0.1.5) 109 | commonmarker (~> 0.17.6) 110 | jekyll-commonmark (~> 1) 111 | rouge (~> 2) 112 | jekyll-default-layout (0.1.4) 113 | jekyll (~> 3.0) 114 | jekyll-feed (0.9.3) 115 | jekyll (~> 3.3) 116 | jekyll-gist (1.5.0) 117 | octokit (~> 4.2) 118 | jekyll-github-metadata (2.9.4) 119 | jekyll (~> 3.1) 120 | octokit (~> 4.0, != 4.4.0) 121 | jekyll-mentions (1.3.0) 122 | activesupport (~> 4.0) 123 | html-pipeline (~> 2.3) 124 | jekyll (~> 3.0) 125 | jekyll-optional-front-matter (0.3.0) 126 | jekyll (~> 3.0) 127 | jekyll-paginate (1.1.0) 128 | jekyll-readme-index (0.2.0) 129 | jekyll (~> 3.0) 130 | jekyll-redirect-from (0.13.0) 131 | jekyll (~> 3.3) 132 | jekyll-relative-links (0.5.3) 133 | jekyll (~> 3.3) 134 | jekyll-remote-theme (0.2.3) 135 | jekyll (~> 3.5) 136 | rubyzip (>= 1.2.1, < 3.0) 137 | typhoeus (>= 0.7, < 2.0) 138 | jekyll-sass-converter (1.5.2) 139 | sass (~> 3.4) 140 | jekyll-seo-tag (2.4.0) 141 | jekyll (~> 3.3) 142 | jekyll-sitemap (1.2.0) 143 | jekyll (~> 3.3) 144 | jekyll-swiss (0.4.0) 145 | jekyll-theme-architect (0.1.1) 146 | jekyll (~> 3.5) 147 | jekyll-seo-tag (~> 2.0) 148 | jekyll-theme-cayman (0.1.1) 149 | jekyll (~> 3.5) 150 | jekyll-seo-tag (~> 2.0) 151 | jekyll-theme-dinky (0.1.1) 152 | jekyll (~> 3.5) 153 | jekyll-seo-tag (~> 2.0) 154 | jekyll-theme-hacker (0.1.1) 155 | jekyll (~> 3.5) 156 | jekyll-seo-tag (~> 2.0) 157 | jekyll-theme-leap-day (0.1.1) 158 | jekyll (~> 3.5) 159 | jekyll-seo-tag (~> 2.0) 160 | jekyll-theme-merlot (0.1.1) 161 | jekyll (~> 3.5) 162 | jekyll-seo-tag (~> 2.0) 163 | jekyll-theme-midnight (0.1.1) 164 | jekyll (~> 3.5) 165 | jekyll-seo-tag (~> 2.0) 166 | jekyll-theme-minimal (0.1.1) 167 | jekyll (~> 3.5) 168 | jekyll-seo-tag (~> 2.0) 169 | jekyll-theme-modernist (0.1.1) 170 | jekyll (~> 3.5) 171 | jekyll-seo-tag (~> 2.0) 172 | jekyll-theme-primer (0.5.3) 173 | jekyll (~> 3.5) 174 | jekyll-github-metadata (~> 2.9) 175 | jekyll-seo-tag (~> 2.0) 176 | jekyll-theme-slate (0.1.1) 177 | jekyll (~> 3.5) 178 | jekyll-seo-tag (~> 2.0) 179 | jekyll-theme-tactile (0.1.1) 180 | jekyll (~> 3.5) 181 | jekyll-seo-tag (~> 2.0) 182 | jekyll-theme-time-machine (0.1.1) 183 | jekyll (~> 3.5) 184 | jekyll-seo-tag (~> 2.0) 185 | jekyll-titles-from-headings (0.5.1) 186 | jekyll (~> 3.3) 187 | jekyll-watch (2.0.0) 188 | listen (~> 3.0) 189 | jemoji (0.9.0) 190 | activesupport (~> 4.0, >= 4.2.9) 191 | gemoji (~> 3.0) 192 | html-pipeline (~> 2.2) 193 | jekyll (~> 3.0) 194 | kramdown (1.16.2) 195 | liquid (4.0.0) 196 | listen (3.1.5) 197 | rb-fsevent (~> 0.9, >= 0.9.4) 198 | rb-inotify (~> 0.9, >= 0.9.7) 199 | ruby_dep (~> 1.2) 200 | mercenary (0.3.6) 201 | mini_portile2 (2.3.0) 202 | minima (2.4.0) 203 | jekyll (~> 3.5) 204 | jekyll-feed (~> 0.9) 205 | jekyll-seo-tag (~> 2.1) 206 | minitest (5.11.3) 207 | multipart-post (2.0.0) 208 | net-dns (0.8.0) 209 | nokogiri (1.8.2) 210 | mini_portile2 (~> 2.3.0) 211 | octokit (4.8.0) 212 | sawyer (~> 0.8.0, >= 0.5.3) 213 | pathutil (0.16.1) 214 | forwardable-extended (~> 2.6) 215 | public_suffix (2.0.5) 216 | rb-fsevent (0.10.3) 217 | rb-inotify (0.9.10) 218 | ffi (>= 0.5.0, < 2) 219 | rouge (2.2.1) 220 | ruby-enum (0.7.2) 221 | i18n 222 | ruby_dep (1.5.0) 223 | rubyzip (1.2.1) 224 | safe_yaml (1.0.4) 225 | sass (3.5.6) 226 | sass-listen (~> 4.0.0) 227 | sass-listen (4.0.0) 228 | rb-fsevent (~> 0.9, >= 0.9.4) 229 | rb-inotify (~> 0.9, >= 0.9.7) 230 | sawyer (0.8.1) 231 | addressable (>= 2.3.5, < 2.6) 232 | faraday (~> 0.8, < 1.0) 233 | terminal-table (1.8.0) 234 | unicode-display_width (~> 1.1, >= 1.1.1) 235 | thread_safe (0.3.6) 236 | typhoeus (1.3.0) 237 | ethon (>= 0.9.0) 238 | tzinfo (1.2.5) 239 | thread_safe (~> 0.1) 240 | unicode-display_width (1.3.0) 241 | 242 | PLATFORMS 243 | ruby 244 | 245 | DEPENDENCIES 246 | github-pages 247 | 248 | BUNDLED WITH 249 | 1.16.1 250 | -------------------------------------------------------------------------------- /docs/pages/react-ramda.md: -------------------------------------------------------------------------------- 1 | # React patterns 2 | 3 | ## Introduction 4 | 5 | ### React Components 6 | 7 | We will speak about properties of React Component as `Props`. But in JS terms properties are just an plain object. 8 | 9 | For more compact syntax instead of `React.Component`, `React.Element` we will write `Component`, `Element`. 10 | 11 | In general, we will work with functional stateless components - pure functions that takes `Props` as an argument and returns JSX/`Element`: 12 | 13 | ``` 14 | React.Component :: Props -> Element 15 | ``` 16 | 17 | 18 | ### High-Order Component (HOC) 19 | 20 | A Higher-Order Component is a function that accepts a `Component` as an argument and returns another `Component`: 21 | 22 | ``` 23 | Component -> Component 24 | ``` 25 | 26 | **Example of identity HOC:** 27 | 28 | ```jsx 29 | const identity = (NextComponent) => (props) => 30 | 31 | const MyContainer = () => "I am MyContainer"; 32 | 33 | const NewContainer = identity(MyContainer); 34 | ``` 35 | 36 | In example above `identity` HOC creates `NewContainer` which renders exactly the same output as the `MyContainer`. 37 | 38 | **Note:** The term originates from [_high-order function_](https://en.wikipedia.org/wiki/Higher-order_function). 39 | 40 | **Note:** Sometimes HOCs are called _component decorators_. [See what decorators really are](https://github.com/tc39/proposal-decorators) and why they are occasionally mismatched with term _high-Order component_. 41 | 42 | ### Examples of HOCs 43 | 44 | **Collections of HOCs** 45 | 46 | * [acdlite/recompose](https://github.com/acdlite/recompose) 47 | * [kriasoft/eact-decorators](https://github.com/kriasoft/react-decorators) 48 | * [jaredpalmer/react-fns](https://github.com/jaredpalmer/react-fns) 49 | * [klarna/higher-Order-components](https://github.com/klarna/higher-Order-components) 50 | 51 | **Providing a context of libraries** 52 | 53 | In many cases is HOC used for adding functionality to your component from certain library via React context API. 54 | 55 | See: 56 | 57 | * [reactjs/redux](https://github.com/reactjs/redux) - `connect` 58 | * [yahoo/react-intl](https://github.com/yahoo/react-intl) - `injectIntl` HOC 59 | * [erikras/redux-form](https://github.com/erikras/redux-form) - `reduxForm` 60 | 61 | ## Patterns 62 | 63 | ### 1. Static Component 64 | 65 | 66 | ```jsx 67 | // Before Ramda: 68 | const Loading = () => "Loading..."; 69 | 70 | ReactDOM.render(, rootEl) // renders "Loading..." 71 | ``` 72 | 73 | 74 | ```jsx 75 | // After Ramda: 76 | const Loading = R.always("Loading..."); 77 | 78 | ReactDOM.render(, rootEl) // renders "Loading..." 79 | ``` 80 | 81 | 82 | ### 2. Composition of High-Order components 83 | 84 | Use function composition instead of nesting calls. 85 | 86 | --- 87 | 88 | ```js 89 | // Before Ramda 90 | connect()( 91 | reduxForm()( 92 | injectIntl(Container) 93 | ) 94 | ) 95 | ``` 96 | 97 | ```js 98 | // After Ramda 99 | R.compose( 100 | connect(), 101 | reduxForm(), 102 | injectIntl 103 | )(Container) 104 | 105 | // or 106 | 107 | R.pipe( 108 | injectIntl, 109 | reduxForm(), 110 | connect() 111 | )(Container) 112 | ``` 113 | 114 | If you are composing from exactly two HOCs, you can use `R.o`. 115 | 116 | It is highly recommended to use just one of `R.o`, `R.compose`, `R.pipe` in the scope of your Application for composing HOCs. 117 | 118 | ### 3. Branching with `R.ifElse` 119 | 120 | Use `R.ifElse` for conditional render. 121 | 122 | --- 123 | 124 | Lets define following components: 125 | 126 | ```jsx 127 | const Loading = R.always("Loading..."); 128 | const Section = ({ content }) =>
{content}
; 129 | ``` 130 | 131 | Than define HOC of conditional render: 132 | 133 | ```jsx 134 | // Before Ramda: 135 | const Content = (props) => props.loading ? 136 | : 137 |
; 138 | ``` 139 | 140 | ```jsx 141 | // After Ramda: 142 | const withLoading = R.ifElse(R.prop('loading'), Loading) 143 | 144 | const Content = withLoading(Section) 145 | ``` 146 | 147 | In this example `withLoading` HOC can be simply reused for all your components with `loading` property. 148 | 149 | ### 4. Branching with `R.cond` 150 | 151 | Use `R.cond` for conditional render. 152 | 153 | --- 154 | 155 | Lets define following components: 156 | 157 | ```jsx 158 | const Loading = R.always("Loading..."); 159 | const Missing = R.always("No results."); 160 | const Section = ({ content }) =>
{content}
; 161 | ``` 162 | 163 | Than define HOC of conditional render: 164 | 165 | ```jsx 166 | // Before Ramda: 167 | const Content = (props) => { 168 | if (props.loading) { 169 | return ; 170 | } 171 | if (!props.items.length) { 172 | return ; 173 | } 174 | 175 | return
176 | } 177 | ``` 178 | 179 | ```jsx 180 | // After Ramda: 181 | const Content = R.cond([ 182 | [R.prop('loading'), Loading], 183 | [R.isEmpty('items'), Missing], 184 | [R.T, Section], 185 | ]); 186 | ``` 187 | 188 | ### 5. Mapping properties with `mapProps` 189 | 190 | Lets define following functions: 191 | 192 | ```js 193 | // Props -> Component -> Element 194 | const createElement = R.curryN(2, React.createElement); 195 | 196 | // mapProps :: (a -> a) -> Component -> Component 197 | const mapProps = R.flip(R.useWith(R.o, [createElement, R.identity])); 198 | ``` 199 | 200 | See Appendix for process of refactor to pointfreee version of `mapProps`. 201 | 202 | With `mapProps` you can transform properties of final component with your custom mapping functions: 203 | 204 | ```jsx 205 | const Section = ({ heading, children, ...rest }) => ( 206 |
207 |

{heading}

208 | {children} 209 |
210 | ); 211 | 212 | 213 | const SectionWithUpperHeading = mapProps( 214 | (props) => ({ ...props, heading: R.toUpper(props.heading) }) 215 | )(Section) 216 | ``` 217 | 218 | 219 | Follows few examples with `mapProps`. 220 | 221 | #### 5.1 Transforming properites with `R.evolve` 222 | 223 | Assuming component `Section` from section 5: 224 | 225 | ```jsx 226 | const EnhancedSection = mapProps( 227 | R.evolve({ heading: R.toUpper }) 228 | )(Section) 229 | 230 | // ... 231 | 232 | 233 | Ramda is awesome! 234 | 235 | 236 | // renders to: 237 |
238 |

TRUTH ABOUT RAMDA

239 | Ramda is awesome! 240 |
241 | ``` 242 | 243 | #### 5.2 Adding props with `computeProps` 244 | 245 | Lets introduce function `computeProps` (see Appendix section): 246 | 247 | ```js 248 | const computeProps = R.converge(R.merge, [R.nthArg(1), R.call]); 249 | ``` 250 | 251 | Assuming following contrieved component `SimpleSection`: 252 | 253 | ```jsx 254 | const SimpleSection = ({ 255 | heading, 256 | childrenLength, 257 | children, 258 | ...rest 259 | }) => ( 260 |
261 |

{heading}

262 |
{children}
263 |
{childrenLength}
264 |
265 | ); 266 | ``` 267 | 268 | ```jsx 269 | const EnhancedSection = mapProps( 270 | ({ children }) => ({ childrenLength: R.length(children) }) 271 | )(SimpleSection) 272 | 273 | // ... 274 | 275 | 276 | Ramda is awesome! 277 | 278 | 279 | // renders to: 280 |
281 |

Truth about Ramda

282 |
Ramda is awesome!
283 |
17
284 |
285 | ``` 286 | 287 | #### 5.3 Picking properties with `R.pick` 288 | 289 | Assuming component `Section` from section 5: 290 | 291 | ```jsx 292 | const EnhancedSection = mapProps( 293 | R.pick(["className", "children", "heading"]) 294 | )(Section) 295 | 296 | // ... 297 | 298 | 303 | Ramda is awesome! 304 | 305 | 306 | // renders to: 307 |
308 |

Truth about Ramda

309 | Ramda is awesome! 310 |
311 | ``` 312 | 313 | ### 6. Defining [`displayName`](https://reactjs.org/docs/react-component.html#displayname) with `setDisplayName` 314 | 315 | Lets define following HOC `setDisplayName`: 316 | 317 | ```jsx 318 | const setDisplayName = (displayName) => R.tap( 319 | (C) => C.displayName = displayName 320 | ); 321 | ``` 322 | 323 | ## Appendix 324 | 325 | ### Pointfree `mapProps` 326 | 327 | ```js 328 | const mapProps = R.curry( 329 | (mapping, C) => (props) => 330 | ); 331 | 332 | // Replacing JSX 333 | const mapProps = R.curry( 334 | (mapping, C) => (props) => React.createElement(C, mapping(props)) 335 | ); 336 | 337 | // Introducting curried version of React.createElement 338 | const createElement = R.curryN(2, React.createElement); 339 | 340 | const mapProps = R.curry( 341 | (mapping, C) => (props) => renderComponent(C)(mapping(props)) 342 | ); 343 | 344 | // R.o for functional composition 345 | const mapProps = R.curry( 346 | (mapping, C) => (props) => R.o(renderComponent(C), mapping)(props) 347 | ); 348 | 349 | // removing explicit argument `props` 350 | const mapProps = R.curry( 351 | (mapping, C) => R.o(renderComponent(C), mapping) 352 | ); 353 | 354 | // final pointfree version with R.flip 355 | const mapProps = R.flip(R.useWith(R.o, [renderComponent, R.identity])); 356 | ``` 357 | 358 | ### Pointfree `computeProps` 359 | 360 | ```js 361 | const computeProps = R.curry( 362 | (props, mapping) => R.merge(props, mapping(props)) 363 | ); 364 | 365 | // Adding converge is pretty straigtforward 366 | const computeProps = R.converge(R.merge, [R.nthArg(1), R.call]); 367 | ``` 368 | 369 | --------------------------------------------------------------------------------