Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions redux-operations-DrorT/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"presets": ["react", "es2015", "stage-0"],
"plugins": [
["transform-decorators-legacy"],
["add-module-exports"]
]
}
4 changes: 4 additions & 0 deletions redux-operations-DrorT/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.log
.idea

11 changes: 11 additions & 0 deletions redux-operations-DrorT/DevTools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { createDevTools } from 'redux-devtools';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';

export default createDevTools(
<DockMonitor toggleVisibilityKey='ctrl-h'
changePositionKey='ctrl-q'>
<LogMonitor />
</DockMonitor>
);
51 changes: 51 additions & 0 deletions redux-operations-DrorT/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Solution with Scalable frontend, with Elm or Redux using **Redux Operations**

# redux-operations package

- The original package is at - https://github.com/mattkrick/redux-operations which combines ideas that myself and Matt had
- General description and idea behind it can be found at - https://medium.com/@matt.krick/solving-redux-s-shortcoming-in-150-locs-540979ce6cf9#.6kl8l9dic

# general ideas -
- reducers define an API (can be founed at the API section of the state)
- each reducer can define functions to be called for specific dispatch calls
- priority decides the order of actions to be called
- data is past from one reducer to another as they are called for a specific dispatch
- 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

# Two solutions for the challenge -

# Common to both solutions -
- 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
- 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
- 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
- the update button for each RandomGif component dispatches the UPDATE_GIF action, which calls an async fetch and on data received calls NEW_GIF
- on NEW_GIF the randomGif reducer is called to update the image source
- **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

# 1. action chaining
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
- 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
- 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

# Observations for 1st solution
- RandomGif component is not aware of any of the rest of the implementation
- Button is only aware of a UPDATE_GIF call, does not know what it does or how it connects to everything
- Counter listens to the UPDATE_GIF call and has the addition implementation
- 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

# 2. component location
- 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):
{
gifCounter:{
counter: 0,
button: false
}
}
- Than we point a regular counter to use location ["gifCounter","counter"] and a regular button to ["gifCounter","button"]
- 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.

# Observations for 2nd solution
- Both counter and button do not need to know about each other, the gifCounter reducer or the different RandomGif components.
- 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.


26 changes: 26 additions & 0 deletions redux-operations-DrorT/components/Button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { Component, PropTypes } from 'react'
import {walkState} from 'redux-operations';
import { clickButton, button } from '../ducks/button';
import {connect} from 'react-redux';

const mapStateToProps = (state, props) => {
return {
button: props.location ? walkState(props.location, state, button) : state.button
}
};

@connect(mapStateToProps)
export default class Button extends Component {
render() {
const { location, button, dispatch } = this.props;
const style = {
"background-color": button ? 'green' : 'red'
};
const text = button ? "On" : "Off";
return (
<div>
<button onClick={() => dispatch(clickButton(location, 'button'))} style={style}>{text}</button>
</div>
)
}
}
38 changes: 38 additions & 0 deletions redux-operations-DrorT/components/Counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { Component, PropTypes } from 'react'
import {walkState} from 'redux-operations';
import {increment, incrementAsync, decrement, incrementIfOdd, setCounter, setFromFetch, counter} from '../ducks/counter';
import {connect} from 'react-redux';

const mapStateToProps = (state, props) => {
return {
counter: props.location ? walkState(props.location, state, counter) : state.counter
}
};

@connect(mapStateToProps)
export default class Counter extends Component {
render() {
const { location, counter, dispatch } = this.props;
return (
<div>
<p>
Value: {counter} times
{' '}
<button onClick={() => dispatch(increment(location, 'counter'))}>+</button>
{' '}
<button onClick={() => dispatch(decrement(location, 'counter'))}>-</button>
{' '}
<button onClick={() => dispatch(incrementIfOdd(location, 'counter'))}>+ if odd</button>
{' '}
<button onClick={() => dispatch(incrementAsync(location, 'counter'))}>Async +</button>
{' '}
<button onClick={() => dispatch(setFromFetch(location, 'counter'))}>Fetch random promise</button>
{' '}
<input type="text" ref="setInput" size="3" defaultValue="0"/>
<button onClick={() => dispatch(setCounter(this.refs['setInput'].value,location, 'counter'))}>Set input</button>
{' '}
</p>
</div>
)
}
}
38 changes: 38 additions & 0 deletions redux-operations-DrorT/components/Counter2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { Component, PropTypes } from 'react'
import {walkState} from 'redux-operations';
import {increment, incrementAsync, decrement, incrementIfOdd, setCounter, setFromFetch, counter} from '../ducks/counter2';
import {connect} from 'react-redux';

const mapStateToProps = (state, props) => {
return {
counter: props.location ? walkState(props.location, state, counter) : state.counter
}
};

@connect(mapStateToProps)
export default class Counter extends Component {
render() {
const { location, counter, dispatch } = this.props;
return (
<div>
<p>
Value: {counter} times
{' '}
<button onClick={() => dispatch(increment(location, 'counter'))}>+</button>
{' '}
<button onClick={() => dispatch(decrement(location, 'counter'))}>-</button>
{' '}
<button onClick={() => dispatch(incrementIfOdd(location, 'counter'))}>+ if odd</button>
{' '}
<button onClick={() => dispatch(incrementAsync(location, 'counter'))}>Async +</button>
{' '}
<button onClick={() => dispatch(setFromFetch(location, 'counter'))}>Fetch random promise</button>
{' '}
<input type="text" ref="setInput" size="3" defaultValue="0"/>
<button onClick={() => dispatch(setCounter(this.refs['setInput'].value,location, 'counter'))}>Set input</button>
{' '}
</p>
</div>
)
}
}
28 changes: 28 additions & 0 deletions redux-operations-DrorT/components/PairOfRandomGifs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import RandomGif from '../components/RandomGif'

import React, { Component } from 'react'

const leftGif = ['gifs', 'left'];
const rightGif = ['gifs', 'right'];

export default class PairOfRandomGifs extends Component {
render() {
const style={
"float":"left"
}
const divStyle ={
"clear":"both"
}
const {locationAddition} = this.props
return (
<div style={divStyle}>
<span style={style}>
<RandomGif location={locationAddition ? leftGif.concat(locationAddition) : leftGif} topic="funny cats"/>
</span>
<span style={style}>
<RandomGif location={locationAddition ? rightGif.concat(locationAddition) : rightGif} topic="hampster"/>
</span>
</div>
)
}
}
36 changes: 36 additions & 0 deletions redux-operations-DrorT/components/RandomGif.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { Component, PropTypes } from 'react'
import {walkState} from 'redux-operations';
import { updateGif, randomGif} from '../ducks/randomGif';
import {connect} from 'react-redux';

const mapStateToProps = (state, props) => {
return {
randomGif: props.location ? walkState(props.location, state, randomGif) : state.randomGif
}
};

@connect(mapStateToProps)
export default class RandomGif extends Component {
componentWillMount() {
const { location, dispatch, topic } = this.props;
dispatch(updateGif(topic,location, 'randomGif'));
}

render() {
const { location, randomGif, dispatch, topic } = this.props;
const imageStyle = {
height: "200px",
width: "300px"
}
return (
<div>
<p>
Topic: <input type="text" ref="topic" size="3" defaultValue={topic} size="20"/>
<button onClick={() => dispatch(updateGif(this.refs['topic'].value,location, 'randomGif'))}>update</button>
<br />
<img src={randomGif} style={imageStyle}/>
</p>
</div>
)
}
}
14 changes: 14 additions & 0 deletions redux-operations-DrorT/configureStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createStore, compose } from 'redux'
import {reduxOperations} from 'redux-operations';
import rootReducer from './rootReducer'
import DevTools from './DevTools';


const enhancer = compose(
reduxOperations(),
DevTools.instrument()
);

export default function configureStore(initialState) {
return createStore(rootReducer, initialState, enhancer);
}
56 changes: 56 additions & 0 deletions redux-operations-DrorT/containers/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Counter from '../components/Counter'
import Counter2 from '../components/Counter2'
import Button from '../components/Button'
import RandomGif from '../components/RandomGif'
import PairOfRandomGifs from '../components/PairOfRandomGifs'

import React, { Component } from 'react'

export default class Counters extends Component {
render() {
const divStyle ={
"clear":"both"
}
return (
<div>
<div className="instructions">
Explore the api in devtools to see operation flow, args, and descriptions ---->
<div>Your normal state is under userState</div>
</div>
<div className="plain-counter">
<h2>1. Plain counter</h2>
<Counter/>
</div>
<div className="button">
<h2>2. Button that changes colors when clicked</h2>
<Button />
</div>
<div className="randomGif1">
<h2>3. A randomGif - using the defualt reducer location</h2>
<RandomGif topic="Cats" />
</div>
<div className="pairOfRandomGif">
<h2>3. A pair of randomGifs - with state locations of -["gifs", "leftGif","main"], ["gifs", "rightGif","main"]</h2>
<PairOfRandomGifs locationAddition="main"/>
</div>
<div className="2pairOfRandomGif" style={divStyle}>
<h2>4. two pairs of randomGifs - with state locations of -["gifs", "leftGif","top"], ["gifs", "rightGif","top"], ["gifs", "leftGif","bottom"], ["gifs", "rightGif","bottom"]</h2>
<PairOfRandomGifs locationAddition="top"/>
<PairOfRandomGifs locationAddition="bottom"/>
</div>
<div style={divStyle}>
<h1>2nd Solution - original components are not chnaged </h1>
<h2>In this solution another reducer is using the data from the counter and button components and listens to the one action</h2>
<div className="plain-counter">
<h2>1. Plain counter</h2>
<Counter2 location={["gifCounter","counter"]}/>
</div>
<div className="button">
<h2>2. Button that changes colors when clicked</h2>
<Button location={["gifCounter","button"]}/>
</div>
</div>
</div>
)
}
}
26 changes: 26 additions & 0 deletions redux-operations-DrorT/ducks/button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {INIT_REDUX_OPERATIONS} from 'redux-operations';
import {UPDATE_GIF} from './randomGif'
export const CLICK_BUTTON = 'CLICK_BUTTON'

export function clickButton(location, name) {
return {
type: CLICK_BUTTON,
meta: {location, name}
}
}

// state represent if button is clicked
export const button = (state = false, action) => {
if (action.type !== INIT_REDUX_OPERATIONS) return state;
return {
CLICK_BUTTON: {
resolve: (state = false, action)=> !state
},
UPDATE_GIF:{
priority: 5,
resolve: (state = false, action)=> state
},
signature: '@@reduxOperations'
}
};

Loading