Skip to content

Commit 3b76732

Browse files
committed
Merge pull request #16 from DrorT/master
Solution usign redux-operations
2 parents b93f39c + c7b3eb9 commit 3b76732

22 files changed

+823
-0
lines changed

redux-operations-DrorT/.babelrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"presets": ["react", "es2015", "stage-0"],
3+
"plugins": [
4+
["transform-decorators-legacy"],
5+
["add-module-exports"]
6+
]
7+
}

redux-operations-DrorT/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
.log
3+
.idea
4+

redux-operations-DrorT/DevTools.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
import { createDevTools } from 'redux-devtools';
3+
import LogMonitor from 'redux-devtools-log-monitor';
4+
import DockMonitor from 'redux-devtools-dock-monitor';
5+
6+
export default createDevTools(
7+
<DockMonitor toggleVisibilityKey='ctrl-h'
8+
changePositionKey='ctrl-q'>
9+
<LogMonitor />
10+
</DockMonitor>
11+
);

redux-operations-DrorT/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Solution with Scalable frontend, with Elm or Redux using **Redux Operations**
2+
3+
# redux-operations package
4+
5+
- The original package is at - https://github.com/mattkrick/redux-operations which combines ideas that myself and Matt had
6+
- General description and idea behind it can be found at - https://medium.com/@matt.krick/solving-redux-s-shortcoming-in-150-locs-540979ce6cf9#.6kl8l9dic
7+
8+
# general ideas -
9+
- reducers define an API (can be founed at the API section of the state)
10+
- each reducer can define functions to be called for specific dispatch calls
11+
- priority decides the order of actions to be called
12+
- data is past from one reducer to another as they are called for a specific dispatch
13+
- reducer functionality is not limited to its "standard" location in state and can be changed by defining a location the reducer/component affect - for example the pair of Random Gifs works by using a normal RandomGif component and giving it a different location in state
14+
15+
# Two solutions for the challenge -
16+
17+
# Common to both solutions -
18+
- counter is used from our redux-operations example at https://github.com/mattkrick/redux-operations-counter-example, so it has more functions than a regular actions
19+
- components reusability is simple and allow for more flexibility and less code written, also reduces amounts of data needed to be passed from component to child components
20+
- the pair of random gifs and the two pairs are simply created by using the RandomGif component and pointing at different locations in the state
21+
- the update button for each RandomGif component dispatches the UPDATE_GIF action, which calls an async fetch and on data received calls NEW_GIF
22+
- on NEW_GIF the randomGif reducer is called to update the image source
23+
- **async** - is written as part of the reducer and invoked as part and not in middleware, so that all application logic is in the reducers
24+
25+
# 1. action chaining
26+
In this solution we use redux-operations ability to call different functions from different reducers to the same action in a specific order passing one function result to the next
27+
- on NEW_GIF action after the randomGif reducer is called the button reducer is called, making no change to state but is used so that the counter reducer can get access to the button state
28+
- last the counter reducer is called (on the same NEW_GIF action) and using the button state passed through the action.meta decides by how much to increase the counter value
29+
30+
# Observations for 1st solution
31+
- RandomGif component is not aware of any of the rest of the implementation
32+
- Button is only aware of a UPDATE_GIF call, does not know what it does or how it connects to everything
33+
- Counter listens to the UPDATE_GIF call and has the addition implementation
34+
- The API section of the state gives a clear view of how the different reducers react to each action, in what order, and even details about expected arguments
35+
36+
# 2. component location
37+
- Redux-opertaions allows for components and the reducers they call through dispatch to act on specific location in the state, so we create part of the state of the following structure (with given defualts):
38+
{
39+
gifCounter:{
40+
counter: 0,
41+
button: false
42+
}
43+
}
44+
- Than we point a regular counter to use location ["gifCounter","counter"] and a regular button to ["gifCounter","button"]
45+
- a new reducer is added called gifCounter - this reducer listens to UPDATE_GIF action and when called uses the counter and button values in its state to increase the counter.
46+
47+
# Observations for 2nd solution
48+
- Both counter and button do not need to know about each other, the gifCounter reducer or the different RandomGif components.
49+
- Each component can be written by completely different groups, knowledge of how the components/reducers work together is only at the APP level and the gifCounter reducer.
50+
51+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import {walkState} from 'redux-operations';
3+
import { clickButton, button } from '../ducks/button';
4+
import {connect} from 'react-redux';
5+
6+
const mapStateToProps = (state, props) => {
7+
return {
8+
button: props.location ? walkState(props.location, state, button) : state.button
9+
}
10+
};
11+
12+
@connect(mapStateToProps)
13+
export default class Button extends Component {
14+
render() {
15+
const { location, button, dispatch } = this.props;
16+
const style = {
17+
"background-color": button ? 'green' : 'red'
18+
};
19+
const text = button ? "On" : "Off";
20+
return (
21+
<div>
22+
<button onClick={() => dispatch(clickButton(location, 'button'))} style={style}>{text}</button>
23+
</div>
24+
)
25+
}
26+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import {walkState} from 'redux-operations';
3+
import {increment, incrementAsync, decrement, incrementIfOdd, setCounter, setFromFetch, counter} from '../ducks/counter';
4+
import {connect} from 'react-redux';
5+
6+
const mapStateToProps = (state, props) => {
7+
return {
8+
counter: props.location ? walkState(props.location, state, counter) : state.counter
9+
}
10+
};
11+
12+
@connect(mapStateToProps)
13+
export default class Counter extends Component {
14+
render() {
15+
const { location, counter, dispatch } = this.props;
16+
return (
17+
<div>
18+
<p>
19+
Value: {counter} times
20+
{' '}
21+
<button onClick={() => dispatch(increment(location, 'counter'))}>+</button>
22+
{' '}
23+
<button onClick={() => dispatch(decrement(location, 'counter'))}>-</button>
24+
{' '}
25+
<button onClick={() => dispatch(incrementIfOdd(location, 'counter'))}>+ if odd</button>
26+
{' '}
27+
<button onClick={() => dispatch(incrementAsync(location, 'counter'))}>Async +</button>
28+
{' '}
29+
<button onClick={() => dispatch(setFromFetch(location, 'counter'))}>Fetch random promise</button>
30+
{' '}
31+
<input type="text" ref="setInput" size="3" defaultValue="0"/>
32+
<button onClick={() => dispatch(setCounter(this.refs['setInput'].value,location, 'counter'))}>Set input</button>
33+
{' '}
34+
</p>
35+
</div>
36+
)
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import {walkState} from 'redux-operations';
3+
import {increment, incrementAsync, decrement, incrementIfOdd, setCounter, setFromFetch, counter} from '../ducks/counter2';
4+
import {connect} from 'react-redux';
5+
6+
const mapStateToProps = (state, props) => {
7+
return {
8+
counter: props.location ? walkState(props.location, state, counter) : state.counter
9+
}
10+
};
11+
12+
@connect(mapStateToProps)
13+
export default class Counter extends Component {
14+
render() {
15+
const { location, counter, dispatch } = this.props;
16+
return (
17+
<div>
18+
<p>
19+
Value: {counter} times
20+
{' '}
21+
<button onClick={() => dispatch(increment(location, 'counter'))}>+</button>
22+
{' '}
23+
<button onClick={() => dispatch(decrement(location, 'counter'))}>-</button>
24+
{' '}
25+
<button onClick={() => dispatch(incrementIfOdd(location, 'counter'))}>+ if odd</button>
26+
{' '}
27+
<button onClick={() => dispatch(incrementAsync(location, 'counter'))}>Async +</button>
28+
{' '}
29+
<button onClick={() => dispatch(setFromFetch(location, 'counter'))}>Fetch random promise</button>
30+
{' '}
31+
<input type="text" ref="setInput" size="3" defaultValue="0"/>
32+
<button onClick={() => dispatch(setCounter(this.refs['setInput'].value,location, 'counter'))}>Set input</button>
33+
{' '}
34+
</p>
35+
</div>
36+
)
37+
}
38+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import RandomGif from '../components/RandomGif'
2+
3+
import React, { Component } from 'react'
4+
5+
const leftGif = ['gifs', 'left'];
6+
const rightGif = ['gifs', 'right'];
7+
8+
export default class PairOfRandomGifs extends Component {
9+
render() {
10+
const style={
11+
"float":"left"
12+
}
13+
const divStyle ={
14+
"clear":"both"
15+
}
16+
const {locationAddition} = this.props
17+
return (
18+
<div style={divStyle}>
19+
<span style={style}>
20+
<RandomGif location={locationAddition ? leftGif.concat(locationAddition) : leftGif} topic="funny cats"/>
21+
</span>
22+
<span style={style}>
23+
<RandomGif location={locationAddition ? rightGif.concat(locationAddition) : rightGif} topic="hampster"/>
24+
</span>
25+
</div>
26+
)
27+
}
28+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React, { Component, PropTypes } from 'react'
2+
import {walkState} from 'redux-operations';
3+
import { updateGif, randomGif} from '../ducks/randomGif';
4+
import {connect} from 'react-redux';
5+
6+
const mapStateToProps = (state, props) => {
7+
return {
8+
randomGif: props.location ? walkState(props.location, state, randomGif) : state.randomGif
9+
}
10+
};
11+
12+
@connect(mapStateToProps)
13+
export default class RandomGif extends Component {
14+
componentWillMount() {
15+
const { location, dispatch, topic } = this.props;
16+
dispatch(updateGif(topic,location, 'randomGif'));
17+
}
18+
19+
render() {
20+
const { location, randomGif, dispatch, topic } = this.props;
21+
const imageStyle = {
22+
height: "200px",
23+
width: "300px"
24+
}
25+
return (
26+
<div>
27+
<p>
28+
Topic: <input type="text" ref="topic" size="3" defaultValue={topic} size="20"/>
29+
<button onClick={() => dispatch(updateGif(this.refs['topic'].value,location, 'randomGif'))}>update</button>
30+
<br />
31+
<img src={randomGif} style={imageStyle}/>
32+
</p>
33+
</div>
34+
)
35+
}
36+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createStore, compose } from 'redux'
2+
import {reduxOperations} from 'redux-operations';
3+
import rootReducer from './rootReducer'
4+
import DevTools from './DevTools';
5+
6+
7+
const enhancer = compose(
8+
reduxOperations(),
9+
DevTools.instrument()
10+
);
11+
12+
export default function configureStore(initialState) {
13+
return createStore(rootReducer, initialState, enhancer);
14+
}

0 commit comments

Comments
 (0)