Skip to content

Instantly share code, notes, and snippets.

@francisrstokes
Last active May 28, 2019 02:31
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save francisrstokes/5750b58c5799038cf03a55ecadca7d8c to your computer and use it in GitHub Desktop.
Save francisrstokes/5750b58c5799038cf03a55ecadca7d8c to your computer and use it in GitHub Desktop.
Redux without redux - sharing state and one way data flow using only standard react
import React from 'react';
import ReactDOM from 'react-dom';
import {StateDispatcher} from './StateDispatcher';
const initialState = {counter: 0};
const rootReducer = (state, action) => {
switch (action.type) {
case 'INC':
return {...state, counter: state.counter + 1};
case 'DEC':
return {...state, counter: state.counter - 1};
default:
return state;
}
};
ReactDOM.render(
<StateDispatcher state={initialState} reducer={rootReducer}>
{({state: {counter}, dispatch}) => {
return <div>
{counter}
<br/>
<button onClick={() => dispatch({type: 'INC'})}>+</button>
<button onClick={() => dispatch({type: 'DEC'})}>-</button>
</div>
}}
</StateDispatcher>,
document.getElementById('root')
);
// More complex example implementing the classic "todo" application
import React from 'react';
import ReactDOM from 'react-dom';
import {StateDispatcher} from './StateDispatcher';
// Utility function for creating combined reducers. Not necessary, but nice to have
const combineReducers = (reducers) => (state, action) => {
return Object.entries(reducers).reduce((newState, [reducerKey, reducer]) => {
return {
...newState,
[reducerKey]: reducer(state[reducerKey], action)
}
}, state);
};
const initialState = {
todos: [
{
title: 'Do something',
id: -1,
complete: false
}
],
filter: 'all',
newTodoText: ''
};
// Used to generate new ids for the todos
let nextId = 0;
/*
Reducers
*/
const todosReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO': {
return [
...state,
{
title: action.payload,
id: nextId++,
complete: false
}
];
}
case 'TOGGLE_TODO':{
const findTodo = todo => todo.id === action.payload;
const todo = state.find(findTodo);
return [
...state.filter(todo => !findTodo(todo)),
{
...todo,
complete: !todo.complete
}
];
}
default: return state;
}
}
const filterReducer = (state, action) => {
switch (action.type) {
case 'APPLY_FILTER': return action.payload
default: return state;
}
}
const newTodoTextReducer = (state, action) => {
switch (action.type) {
case 'SET_NEW_TODO_TEXT': return action.payload;
case 'ADD_TODO': return '';
default: return state;
}
};
// The final combined reducer
const rootReducer = combineReducers({
todos: todosReducer,
filter: filterReducer,
newTodoText: newTodoTextReducer
});
const applyFilter = (filter, todos) => todos.filter(todo => {
switch (filter) {
case 'completed':
return todo.complete;
case 'uncompleted':
return !todo.complete;
default:
return true;
}
});
ReactDOM.render(
<StateDispatcher state={initialState} reducer={rootReducer}>
{({state, dispatch}) => {
return <div>
<div>
<ul>
{applyFilter(state.filter, state.todos).map(todo =>
<li
key={todo.id}
onClick={() => dispatch({type: 'TOGGLE_TODO', payload: todo.id})}
style={{
textDecoration: todo.complete ? 'line-through' : 'none'
}}
>
{todo.title}
</li>
)}
</ul>
</div>
<div>
<button onClick={() => dispatch({type: 'APPLY_FILTER', payload: 'all'})}>All</button>
<button onClick={() => dispatch({type: 'APPLY_FILTER', payload: 'completed'})}>Completed</button>
<button onClick={() => dispatch({type: 'APPLY_FILTER', payload: 'uncompleted'})}>Uncompleted</button>
</div>
<div>
<input
value={state.newTodoText}
onChange={e => dispatch({type: 'SET_NEW_TODO_TEXT', payload: e.target.value })}
onKeyPress={e =>
(e.key === 'Enter' && state.newTodoText)
? dispatch({type: 'ADD_TODO', payload: state.newTodoText})
: null
}
/>
<button
onClick={() => state.newTodoText && dispatch({type: 'ADD_TODO', payload: state.newTodoText})}
>
Add Todo
</button>
</div>
</div>
}}
</StateDispatcher>,
document.getElementById('root')
);
import React from 'react';
export class StateDispatcher extends React.Component {
constructor(props) {
super(props);
this.state = props.state || {};
this._dispatch = this.dispatch.bind(this);
}
dispatch(action) {
this.setState(state => this.props.reducer(state, action));
}
getArgs() {
return {
state: this.state,
dispatch: this._dispatch
}
}
render() {
const {children} = this.props;
return children
? typeof children === 'function'
? children(this.getArgs())
: React.createElement(children)
: null;
}
}
@francisrstokes
Copy link
Author

Absolutely true - I updated the code. Thanks @rafales.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment