Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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;
}
}
@rafales

This comment has been minimized.

Copy link

rafales commented Jul 25, 2018

The whole point of having callback to setState is to use state passed as an argument. Otherwise you will run into race conditions. So dispatch should actually be:

dispatch(action) {
this.setState(state => this.props.reducer(state, action);
}
@francisrstokes

This comment has been minimized.

Copy link
Owner Author

francisrstokes commented Jul 25, 2018

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
You can’t perform that action at this time.