Last active
November 10, 2024 13:37
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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') | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Absolutely true - I updated the code. Thanks @rafales.