├── .eslintrc
├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── App.css
├── App.js
├── App.test.js
├── appInitialState.js
├── components
│ ├── FullMainView.js
│ ├── MainViewPractice.js
│ ├── TableWithBorder.js
│ ├── WithBorder.js
│ └── WithBorderHOC.js
├── constants
│ └── columns.js
├── index.css
├── index.js
├── logo.svg
├── part1-no-reuse
│ └── MainView.js
├── part2-simple-composition
│ └── MainView.js
├── part3-simple-export
│ └── MainView.js
├── part4-hoc-export
│ └── MainView.js
├── part5-hoc-withState
│ └── MainView.js
├── part6-renderCallback-withState
│ └── MainView.js
├── reducers
│ ├── todoReducer.js
│ └── usersReducer.js
├── registerServiceWorker.js
└── utils.js
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "rules": {
4 | "react/prop-types": [1, {
5 | "ignore": [
6 | "children"
7 | ]
8 | }],
9 | "react/sort-comp": 2,
10 | "react/no-unused-prop-types": 2,
11 | "prefer-template" : 2,
12 | "arrow-body-style": [2, "as-needed"],
13 | "arrow-spacing": [2, { "before": true, "after": true }],
14 | "arrow-parens": [2, "as-needed"],
15 | "no-duplicate-imports": 2,
16 | "no-var": 2,
17 | "prefer-const": 2,
18 | "prefer-arrow-callback": [1, {
19 | "allowNamedFunctions": false,
20 | "allowUnboundThis": true
21 | }],
22 | "prefer-rest-params": 2,
23 | "prefer-spread": 2,
24 | "rest-spread-spacing": [2, "never"]
25 | }
26 | }
--------------------------------------------------------------------------------
/.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 | # Intellij
20 | .idea/
21 | *.iml
22 | *.iws
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React reuse patters
2 |
3 | This repository contains examples used for Wix Engineering meetup
4 | [Link to meetup](https://www.meetup.com/at-wix/events/244861816/)
5 | [Link to slides](https://docs.google.com/presentation/d/1jxHhiZELbMh1YvvXhB1YIaovyUbsc8abTy54PriLh4o/edit?usp=sharing)
6 | [Video in Youtube](https://www.youtube.com/watch?v=0BNgi9vofaw&t=1393s)
7 |
8 | The app was created using [create-react-app](https://github.com/facebookincubator/create-react-app)
9 | React components used in the examples:
10 | * [react-table](https://github.com/react-tools/react-table)
11 | * [react-tagsinput](https://github.com/olahol/react-tagsinput)
12 |
13 |
14 | The directories are divided according to the presentation phases, each contains a MainView component.
15 | To render the relevant part, switch the import of MainView in App.js
16 | The parts are:
17 |
18 | * No reuse
19 | * Simple composition
20 | * Repackaging with composition
21 | * Repackaging with HOC
22 | * HOC communication with inner component (withState example)
23 | * Render callback - Dynamic communication with inner component (withState example)
24 |
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-test",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "lodash": "^4.17.4",
7 | "prop-types": "^15.6.0",
8 | "react": "^16.1.1",
9 | "react-dom": "^16.1.1",
10 | "react-redux": "^5.0.6",
11 | "react-scripts": "1.0.17",
12 | "react-table": "^6.7.4",
13 | "react-tagsinput": "^3.19.0",
14 | "recompose": "^0.26.0",
15 | "redux": "^3.7.2"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test --env=jsdom",
21 | "eject": "react-scripts eject"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rommguy/react-reuse-patterns/ded7eb949b3641d65c383ff01f5ec8eb9c1b8a3f/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 | You need to enable JavaScript to run this app.
27 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | @keyframes App-logo-spin {
26 | from {
27 | transform: rotate(0deg);
28 | }
29 | to {
30 | transform: rotate(360deg);
31 | }
32 | }
33 |
34 | .main-view {
35 | margin: 50px;
36 | display: flex;
37 | align-items: center;
38 | }
39 |
40 | .with-border {
41 | padding: 10px;
42 | transition: padding 250ms linear, border-color 250ms linear;
43 | border: 5px solid darkgoldenrod;
44 | }
45 |
46 | .tags-container {
47 | display: inline-block;
48 | width: 350px;
49 | margin: 0 20px;
50 | }
51 |
52 | .table-container {
53 | display: inline-block;
54 | width: 600px;
55 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 | import {MainView} from './part1-no-reuse/MainView'
5 | // import {MainView} from './part2-simple-composition/MainView'
6 | // import {MainView} from './part3-simple-export/MainView'
7 | // import {MainView} from './part5-hoc-withState/MainView'
8 | // import {MainView} from './part6-dynamicWithState/MainView'
9 | import {withState} from 'recompose'
10 | import 'react-table/react-table.css'
11 | import {map, identity} from 'lodash/fp'
12 | import {columns} from './constants/columns'
13 | import 'react-tagsinput/react-tagsinput.css'
14 |
15 | const usersData = {
16 | '0001': {firstName: 'comfort', lastName: 'account', age: 27, status: 'relationshipnail', visits: 59},
17 | '0002': {firstName: 'tin', lastName: 'throne', age: 20, status: 'relationship', visits: 53},
18 | '0003': {firstName: 'trains', lastName: 'highway', age: 29, status: 'hand', visits: 2},
19 | '0004': {firstName: 'difficulty', lastName: 'clock', age: 28, status: 'single', visits: 89},
20 | '0005': {firstName: 'window', lastName: 'honey', age: 29, status: 'complicated', visits: 7},
21 | }
22 |
23 | const StatefullMainView = withState('tags', 'updateTags', ['Too', 'Many', 'People'])(MainView)
24 |
25 | class App extends Component {
26 | render() {
27 | const dataArr = map(identity, usersData)
28 | return (
29 |
30 |
31 |
32 | Welcome to React
33 |
34 |
38 |
39 | );
40 | }
41 | }
42 |
43 | export default App;
44 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | });
9 |
--------------------------------------------------------------------------------
/src/appInitialState.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | todos: [],
3 | usersData: {
4 | '0001': {firstName: 'comfort', lastName: 'account', age: 27, status: 'relationshipnail', visits: 59},
5 | '0002': {firstName: 'tin', lastName: 'throne', age: 20, status: 'relationship', visits: 53},
6 | '0003': {firstName: 'trains', lastName: 'highway', age: 29, status: 'hand', visits: 2},
7 | '0004': {firstName: 'difficulty', lastName: 'clock', age: 28, status: 'single', visits: 89},
8 | '0005': {firstName: 'window', lastName: 'honey', age: 29, status: 'complicated', visits: 7},
9 | }
10 | }
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/components/FullMainView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {WithBorder} from './WithBorder'
5 | import {getRandomColorStyleValue} from '../utils'
6 | import {WithBorderHOC} from './WithBorderHOC'
7 | import {map, identity} from 'lodash/fp'
8 | import {columns} from '../constants/columns'
9 | import TagsInput from 'react-tagsinput'
10 | import {TableWithBorder} from './TableWithBorder'
11 | import 'react-tagsinput/react-tagsinput.css'
12 |
13 |
14 | //const TableWithBorder = WithBorderHOC(ReactTable) // created statically
15 |
16 |
17 | class DyanmicWithState extends Component {
18 | static propTypes = {
19 | initialState: PropTypes.any
20 | }
21 |
22 | constructor(props) {
23 | super()
24 |
25 | this.state = {myState: props.initialState}
26 | }
27 |
28 | updateState = val => this.setState({myState: val})
29 |
30 | render() {
31 | return this.props.children(this.state.myState, this.updateState)
32 | }
33 | }
34 |
35 | class StatelessWithBorder extends Component {
36 | static propTypes = {
37 | borderColor: PropTypes.string,
38 | replaceColor: PropTypes.func.isRequired
39 | }
40 |
41 | render() {
42 | return (
43 | this.props.replaceColor(getRandomColorStyleValue())}
46 | onMouseLeave={() => this.props.replaceColor(null)}>
47 | {this.props.children}
48 |
49 | )
50 | }
51 | }
52 |
53 | export class MainView extends Component {
54 | static propTypes = {
55 | userData: PropTypes.object
56 | }
57 |
58 | constructor() {
59 | super()
60 |
61 | this.state = {
62 | tags: ['Too', 'Many', 'People']
63 | }
64 | }
65 |
66 | updateTags = tags => {
67 | this.setState({tags})
68 | }
69 |
70 | render() {
71 | const dataArr = map(identity, this.props.userData)
72 | return (
73 |
74 |
81 |
82 |
83 | {(borderColor, replaceColor) => (
84 |
85 |
86 |
87 | )}
88 |
89 |
90 |
91 | )
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/components/MainViewPractice.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {getRandomColorStyleValue} from '../utils'
5 | import TagsInput from 'react-tagsinput'
6 |
7 | const withState = (stateName, stateUpdaterName, initialStateGetter) => InnerComponent => class OuterComponent extends Component {
8 | static propTypes = {}
9 |
10 | constructor(props) {
11 | super()
12 |
13 | this.state = {myState: initialStateGetter(props)}
14 | }
15 |
16 | updateState = newVal => this.setState({myState: newVal})
17 |
18 | render() {
19 | const innerCompProps = {
20 | [stateName]: this.state.myState,
21 | [stateUpdaterName]: this.updateState
22 | }
23 | return ( )
24 | }
25 | }
26 |
27 |
28 | class WithBorderOld extends Component {
29 | static propTypes = {
30 | color: PropTypes.string
31 | }
32 |
33 | constructor(props) {
34 | super()
35 |
36 | this.state = {
37 | borderColor: props.color
38 | }
39 | }
40 |
41 | onMouseEnter = () => {
42 | this.setState({borderColor: getRandomColorStyleValue()})
43 | }
44 |
45 | onMouseLeave = () => {
46 | this.setState({borderColor: this.props.color})
47 | }
48 |
49 | render() {
50 | return (
51 |
55 | {this.props.children}
56 |
57 | )
58 | }
59 | }
60 |
61 | const StatelessWithBorder = props => (
62 | props.updateBorderColor(getRandomColorStyleValue())}
65 | onMouseLeave={() => props.updateBorderColor(props.color)}>
66 | {props.children}
67 |
68 | )
69 |
70 | StatelessWithBorder.propTypes = {
71 | borderColor: PropTypes.string,
72 | color: PropTypes.string,
73 | updateBorderColor: PropTypes.func
74 | }
75 |
76 | const WithBorder = withState('borderColor', 'updateBorderColor', props => props.color)(StatelessWithBorder)
77 |
78 | const withBorderHOC = InnerComp => props => (
79 |
80 |
81 |
82 | )
83 |
84 | const TableWithBorder = withBorderHOC(ReactTable)
85 | const TagsWithBorder = withBorderHOC(TagsInput)
86 |
87 | TagsWithBorder.propTypes = {
88 | color: PropTypes.string,
89 | value: PropTypes.array,
90 | onChange: PropTypes.func
91 | }
92 |
93 | TableWithBorder.propTypes = {
94 | color: PropTypes.string,
95 | data: PropTypes.array,
96 | columns: PropTypes.array,
97 | defaultPageSize: PropTypes.number
98 | }
99 |
100 | export class MainView extends Component {
101 | static propTypes = {
102 | data: PropTypes.array,
103 | tags: PropTypes.array.isRequired,
104 | updateTags: PropTypes.func.isRequired,
105 | color: PropTypes.string,
106 | columns: PropTypes.array
107 | }
108 |
109 | updateTags = newTags => {
110 | this.props.updateTags(newTags)
111 | }
112 |
113 | render() {
114 | return (
115 |
116 |
117 |
118 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | )
131 | }
132 | }
133 |
134 |
135 | class DynamicWithState extends Component {
136 | static propTypes = {
137 | initialState: PropTypes.any,
138 | children: PropTypes.func.isRequired
139 | }
140 |
141 | constructor(props) {
142 | super()
143 |
144 | this.state = {myState: props.initialState}
145 | }
146 |
147 | updateState = newValue => this.setState({myState: newValue})
148 |
149 | render() {
150 | return this.props.children(this.state.myState, this.updateState)
151 | }
152 | }
153 |
154 | DynamicWithState.propTypes = {
155 | initialState: PropTypes.any
156 | }
157 |
--------------------------------------------------------------------------------
/src/components/TableWithBorder.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {getRandomColorStyleValue} from '../utils'
5 |
6 | export class TableWithBorder extends Component {
7 | static propTypes = {
8 | columns: PropTypes.array,
9 | data: PropTypes.array,
10 | defaultPageSize: PropTypes.number,
11 | color: PropTypes.string
12 | }
13 |
14 | constructor() {
15 | super()
16 |
17 | this.onMouseEnter = this.onMouseEnter.bind(this)
18 | this.onMouseLeave = this.onMouseLeave.bind(this)
19 | this.state = {borderColor: null}
20 | }
21 |
22 | onMouseEnter() {
23 | this.setState({borderColor: getRandomColorStyleValue()})
24 | }
25 |
26 | onMouseLeave() {
27 | this.setState({borderColor: null})
28 | }
29 |
30 | render() {
31 | const borderColor = this.state.borderColor || this.props.color
32 | const {defaultPageSize, data, columns} = this.props
33 | return (
34 |
38 |
41 |
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/WithBorder.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import {getRandomColorStyleValue} from '../utils'
4 |
5 | export class WithBorder extends Component {
6 | static propTypes = {
7 | color: PropTypes.string
8 | }
9 |
10 | constructor() {
11 | super()
12 |
13 | this.state = {borderColor: null}
14 | }
15 |
16 | onMouseEnter = () => {
17 | this.setState({borderColor: getRandomColorStyleValue()})
18 | }
19 |
20 | onMouseLeave = () => {
21 | this.setState({borderColor: null})
22 | }
23 |
24 | render() {
25 | const borderColor = this.state.borderColor || this.props.color
26 | return (
27 |
31 | {this.props.children}
32 |
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/WithBorderHOC.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | export const WithBorderHOC = WrappedComponent => class WithBorderWrapper extends Component {
5 | static propTypes = {
6 | color: PropTypes.string
7 | }
8 |
9 | render() {
10 | return (
11 |
12 |
13 |
14 | )
15 | }
16 | }
--------------------------------------------------------------------------------
/src/constants/columns.js:
--------------------------------------------------------------------------------
1 | export const columns = [{
2 | Header: "Name",
3 | columns: [{
4 | Header: "First Name",
5 | accessor: "firstName"
6 | }, {
7 | Header: "Last Name",
8 | id: "lastName",
9 | accessor: d => d.lastName
10 | }]
11 | }, {
12 | Header: "Info",
13 | columns: [{
14 | Header: "Age",
15 | accessor: "age"
16 | }, {
17 | Header: "Status",
18 | accessor: "status"
19 | }]
20 | }, {
21 | Header: 'Stats',
22 | columns: [{
23 | Header: "Visits",
24 | accessor: "visits"
25 | }]
26 | }]
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './registerServiceWorker';
6 | import {createStore, combineReducers } from 'redux';
7 | import {Provider} from 'react-redux'
8 | import {todoReducer} from './reducers/todoReducer'
9 | import {usersDataReducer} from './reducers/usersReducer'
10 |
11 | const reducer = combineReducers({
12 | todos: todoReducer,
13 | userData: usersDataReducer
14 | })
15 |
16 | const store = createStore(reducer)
17 |
18 | ReactDOM.render(
19 |
20 |
21 |
22 | , document.getElementById('root'));
23 | registerServiceWorker();
24 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/part1-no-reuse/MainView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {getRandomColorStyleValue} from '../utils'
5 | import TagsInput from 'react-tagsinput'
6 |
7 | export class MainView extends Component {
8 | static propTypes = {
9 | data: PropTypes.array,
10 | tags: PropTypes.array.isRequired,
11 | updateTags: PropTypes.func.isRequired,
12 | color: PropTypes.string,
13 | columns: PropTypes.array
14 | }
15 |
16 | constructor(props) {
17 | super()
18 |
19 | this.state = {
20 | borderColor: props.color
21 | }
22 | }
23 |
24 | updateTags = newTags => {
25 | this.props.updateTags(newTags)
26 | }
27 |
28 | onMouseEnter = () => {
29 | this.setState({borderColor: getRandomColorStyleValue()})
30 | }
31 |
32 | onMouseLeave = () => {
33 | this.setState({borderColor: this.props.color})
34 | }
35 |
36 | render() {
37 | return (
38 |
54 | )
55 | }
56 | }
57 |
58 | const withState = InnerComp => class OuterComp extends Component {
59 | // simplified version of recompose withState
60 |
61 | static propTypes = {
62 | initialStateValue: PropTypes.any
63 | }
64 |
65 | constructor(props) {
66 | super()
67 |
68 | this.state = {myState: props.initialStateValue}
69 | }
70 |
71 | updateState = newVal => this.setState({myState: newVal})
72 |
73 | render() {
74 | const innerCompProps = {
75 | stateValue: this.state.myState,
76 | updateState: this.updateState
77 | }
78 | return ( )
79 | }
80 | }
81 |
82 | class DynamicWithState extends Component {
83 | static propTypes = {
84 | initialState: PropTypes.any,
85 | children: PropTypes.func.isRequired
86 | }
87 |
88 | constructor(props) {
89 | super()
90 |
91 | this.state = {myState: props.initialState}
92 | }
93 |
94 | updateState = newValue => this.setState({myState: newValue})
95 |
96 | render() {
97 | return this.props.children(this.state.myState, this.updateState)
98 | }
99 | }
100 |
101 | DynamicWithState.propTypes = {
102 | initialState: PropTypes.any
103 | }
104 |
--------------------------------------------------------------------------------
/src/part2-simple-composition/MainView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {getRandomColorStyleValue} from '../utils'
5 | import TagsInput from 'react-tagsinput'
6 | import 'react-tagsinput/react-tagsinput.css'
7 |
8 | class WithBorder extends Component {
9 | static propTypes = {
10 | color: PropTypes.string
11 | }
12 |
13 | constructor(props) {
14 | super()
15 |
16 | this.state = {
17 | borderColor: props.color
18 | }
19 | }
20 |
21 | onMouseEnter = () => {
22 | this.setState({borderColor: getRandomColorStyleValue()})
23 | }
24 |
25 | onMouseLeave = () => {
26 | this.setState({borderColor: this.props.color})
27 | }
28 |
29 | render() {
30 | return (
31 |
35 | {this.props.children}
36 |
37 | )
38 | }
39 | }
40 |
41 | export class MainView extends Component {
42 | static propTypes = {
43 | data: PropTypes.array,
44 | tags: PropTypes.array.isRequired,
45 | updateTags: PropTypes.func.isRequired,
46 | color: PropTypes.string,
47 | columns: PropTypes.array
48 | }
49 |
50 | updateTags = newTags => {
51 | this.props.updateTags(newTags)
52 | }
53 |
54 | render() {
55 | return (
56 |
57 |
58 |
59 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | )
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/part3-simple-export/MainView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {getRandomColorStyleValue} from '../utils'
5 | import TagsInput from 'react-tagsinput'
6 | import 'react-tagsinput/react-tagsinput.css'
7 |
8 | class WithBorder extends Component {
9 | static propTypes = {
10 | color: PropTypes.string
11 | }
12 |
13 | constructor(props) {
14 | super()
15 |
16 | this.state = {
17 | borderColor: props.color
18 | }
19 | }
20 |
21 | onMouseEnter = () => {
22 | this.setState({borderColor: getRandomColorStyleValue()})
23 | }
24 |
25 | onMouseLeave = () => {
26 | this.setState({borderColor: this.props.color})
27 | }
28 |
29 | render() {
30 | return (
31 |
35 | {this.props.children}
36 |
37 | )
38 | }
39 | }
40 |
41 | const TableWithBorder = props => (
42 |
43 |
46 |
47 | )
48 |
49 | TableWithBorder.propTypes = {
50 | color: PropTypes.string,
51 | data: PropTypes.array,
52 | columns: PropTypes.array,
53 | defaultPageSize: PropTypes.number
54 | }
55 |
56 | const TagsWithBorder = props => (
57 |
58 |
60 |
61 | )
62 |
63 | TagsWithBorder.propTypes = {
64 | color: PropTypes.string,
65 | value: PropTypes.array,
66 | onChange: PropTypes.func
67 | }
68 |
69 | export class MainView extends Component {
70 | static propTypes = {
71 | data: PropTypes.array,
72 | tags: PropTypes.array.isRequired,
73 | updateTags: PropTypes.func.isRequired,
74 | color: PropTypes.string,
75 | columns: PropTypes.array
76 | }
77 |
78 | updateTags = newTags => {
79 | this.props.updateTags(newTags)
80 | }
81 |
82 | render() {
83 | const {columns, color, data} = this.props
84 | return (
85 |
98 | )
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/part4-hoc-export/MainView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {getRandomColorStyleValue} from '../utils'
5 | import TagsInput from 'react-tagsinput'
6 | import 'react-tagsinput/react-tagsinput.css'
7 |
8 | class WithBorder extends Component {
9 | static propTypes = {
10 | color: PropTypes.string
11 | }
12 |
13 | constructor(props) {
14 | super()
15 |
16 | this.state = {
17 | borderColor: props.color
18 | }
19 | }
20 |
21 | onMouseEnter = () => {
22 | this.setState({borderColor: getRandomColorStyleValue()})
23 | }
24 |
25 | onMouseLeave = () => {
26 | this.setState({borderColor: this.props.color})
27 | }
28 |
29 | render() {
30 | return (
31 |
35 | {this.props.children}
36 |
37 | )
38 | }
39 | }
40 |
41 | const WithBorderHOC = Component => props => (
42 |
43 |
44 |
45 | )
46 |
47 | const TableWithBorder = WithBorderHOC(ReactTable)
48 | const TagsWithBorder = WithBorderHOC(TagsInput)
49 |
50 | export class MainView extends Component {
51 | static propTypes = {
52 | data: PropTypes.array,
53 | tags: PropTypes.array.isRequired,
54 | updateTags: PropTypes.func.isRequired,
55 | color: PropTypes.string,
56 | columns: PropTypes.array
57 | }
58 |
59 | updateTags = newTags => {
60 | this.props.updateTags(newTags)
61 | }
62 |
63 | render() {
64 | const {columns, color, data} = this.props
65 | return (
66 |
79 | )
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/part5-hoc-withState/MainView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {getRandomColorStyleValue} from '../utils'
5 | import TagsInput from 'react-tagsinput'
6 | import 'react-tagsinput/react-tagsinput.css'
7 |
8 | const withState = InnerComponent => class OuterComponent extends Component {
9 | static propTypes = {
10 | color: PropTypes.any
11 | }
12 |
13 | constructor(props) {
14 | super()
15 |
16 | this.state = {myState: props.color}
17 | }
18 |
19 | updateState = newVal => this.setState({myState: newVal})
20 |
21 | render() {
22 | const innerProps = {
23 | stateValue: this.state.myState,
24 | updateState: this.updateState
25 | }
26 | return ( )
27 | }
28 | }
29 |
30 | const StatelessWithBorder = props => (
31 | props.updateState(getRandomColorStyleValue())}
34 | onMouseLeave={() => props.updateState(props.color)}>
35 | {props.children}
36 |
37 | )
38 |
39 | StatelessWithBorder.propTypes = {
40 | stateValue: PropTypes.string,
41 | updateState: PropTypes.func,
42 | color: PropTypes.string
43 | }
44 |
45 | const WithBorder = withState(StatelessWithBorder)
46 |
47 |
48 | const WithBorderHOC = Component => props => (
49 |
50 |
51 |
52 | )
53 |
54 | const TableWithBorder = WithBorderHOC(ReactTable)
55 | const TagsWithBorder = WithBorderHOC(TagsInput)
56 |
57 | export class MainView extends Component {
58 | static propTypes = {
59 | data: PropTypes.array,
60 | tags: PropTypes.array.isRequired,
61 | updateTags: PropTypes.func.isRequired,
62 | color: PropTypes.string,
63 | columns: PropTypes.array
64 | }
65 |
66 | updateTags = newTags => {
67 | this.props.updateTags(newTags)
68 | }
69 |
70 | render() {
71 | const {columns, color, data} = this.props
72 | return (
73 |
86 | )
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/part6-renderCallback-withState/MainView.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types'
3 | import ReactTable from 'react-table';
4 | import {getRandomColorStyleValue} from '../utils'
5 | import TagsInput from 'react-tagsinput'
6 | import 'react-tagsinput/react-tagsinput.css'
7 |
8 | const withState = InnerComponent => class OuterComponent extends Component {
9 | static propTypes = {
10 | color: PropTypes.any
11 | }
12 |
13 | constructor(props) {
14 | super()
15 |
16 | this.state = {myState: props.color}
17 | }
18 |
19 | updateState = newVal => this.setState({myState: newVal})
20 |
21 | render() {
22 | const innerProps = {
23 | stateValue: this.state.myState,
24 | updateState: this.updateState
25 | }
26 | return ( )
27 | }
28 | }
29 |
30 | const StatelessWithBorder = props => (
31 | props.updateState(getRandomColorStyleValue())}
34 | onMouseLeave={() => props.updateState(props.color)}>
35 | {props.children}
36 |
37 | )
38 |
39 | StatelessWithBorder.propTypes = {
40 | stateValue: PropTypes.string,
41 | updateState: PropTypes.func,
42 | color: PropTypes.string
43 | }
44 | const WithBorder = withState(StatelessWithBorder)
45 |
46 | const WithBorderHOC = Component => props => (
47 |
48 |
49 |
50 | )
51 |
52 | const TableWithBorder = WithBorderHOC(ReactTable)
53 |
54 | class DynamicWithState extends Component {
55 | static propTypes = {
56 | initialState: PropTypes.any,
57 | children: PropTypes.func.isRequired
58 | }
59 |
60 | constructor(props) {
61 | super()
62 |
63 | this.state = {myState: props.initialState}
64 | }
65 |
66 | updateState = newValue => this.setState({myState: newValue})
67 |
68 | render() {
69 | return this.props.children(this.state.myState, this.updateState)
70 | }
71 | }
72 |
73 | DynamicWithState.propTypes = {
74 | initialState: PropTypes.any
75 | }
76 |
77 | export class MainView extends Component {
78 | static propTypes = {
79 | data: PropTypes.array,
80 | tags: PropTypes.array.isRequired,
81 | updateTags: PropTypes.func.isRequired,
82 | color: PropTypes.string,
83 | columns: PropTypes.array
84 | }
85 |
86 | updateTags = newTags => {
87 | this.props.updateTags(newTags)
88 | }
89 |
90 | render() {
91 | const {columns, color, data} = this.props
92 | return (
93 |
94 |
100 |
101 |
102 | {(colorInState, updateBorderColor) => (
103 |
106 |
108 |
109 | )}
110 |
111 |
112 |
113 |
114 | )
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/reducers/todoReducer.js:
--------------------------------------------------------------------------------
1 | import {initialState} from '../appInitialState'
2 |
3 | export const todoReducer = (todos = initialState.todos, action) => {
4 | switch (action.type) {
5 | default:
6 | return todos
7 | }
8 | }
--------------------------------------------------------------------------------
/src/reducers/usersReducer.js:
--------------------------------------------------------------------------------
1 | import {initialState} from '../appInitialState'
2 |
3 | export const usersDataReducer = (usersData = initialState.usersData, action) => {
4 | switch (action.type) {
5 | default:
6 | return usersData
7 | }
8 | }
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 | } else {
39 | // Is not local host. Just register service worker
40 | registerValidSW(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | const getRandomColor = () => Math.floor(Math.random() * 255)
2 |
3 |
4 | export function getRandomColorStyleValue() {
5 | const red = getRandomColor()
6 | const green = getRandomColor()
7 | const blue = getRandomColor()
8 |
9 | return `rgb(${red},${green},${blue})`
10 | }
--------------------------------------------------------------------------------