feat: a bunch of examples

This commit is contained in:
2019-03-15 13:32:27 -07:00
parent 8aa4af62af
commit e6c8a5106d
18 changed files with 423 additions and 11 deletions

View File

@@ -31,5 +31,7 @@ module.exports = {
'peerDependencies': false
}
],
'react/jsx-one-expression-per-line': 'off',
'react/prop-types': 'off',
}
}

View File

@@ -4,8 +4,5 @@ module.exports = {
// Our default preset
'poi/babel',
],
plugins: [
// This adds Hot Reloading support
'react-hot-loader/babel',
],
plugins: [],
};

View File

@@ -1,5 +1,5 @@
/* eslint-env node */
module.exports = {
entry: 'src/index',
plugins: ['@poi/plugin-eslint'],
// plugins: ['@poi/plugin-eslint'],
};

39
src/App.jsx Normal file
View File

@@ -0,0 +1,39 @@
import React from 'react';
import Home from './apps/Home';
import UseState from './apps/use-state/App';
import UseReducer from './apps/use-reducer/App';
import Immer from './apps/immer/App';
import ImmerReducer from './apps/immer-reducer/App';
import TodoState from './apps/todo-state/App';
const { pathname } = window.location;
const App = () => {
const getApp = () => {
switch (pathname) {
case '/use-state':
return <UseState />;
case '/use-reducer':
return <UseReducer />;
case '/immer':
return <Immer />;
case '/immer-reducer':
return <ImmerReducer />;
case '/todo-state':
return <TodoState />;
default:
return <Home />;
}
};
return (
<div>
<div className="nav">
<a href="/">Back Home</a>
</div>
{getApp()}
</div>
);
};
export default App();

25
src/apps/Home.jsx Normal file
View File

@@ -0,0 +1,25 @@
import React from 'react';
const Home = () => (
<div>
<ul>
<li>
<a href="/use-state">useState Example</a>
</li>
<li>
<a href="/use-reducer">useReducer Example</a>
</li>
<li>
<a href="/immer">Immer Example</a>
</li>
<li>
<a href="/immer-reducer">Immer Reducer Example</a>
</li>
<li>
<a href="/todo-state">External State Example</a>
</li>
</ul>
</div>
);
export default Home;

View File

@@ -0,0 +1,28 @@
/* eslint no-param-reassign: 0 */
import React from 'react';
import initialState from '../../initial_state';
import TodoList from '../../components/TodoList';
import { useImmerReducer, original } from '../../hooks/use_immer';
const UseReducer = () => {
const [todos, dispatch] = useImmerReducer((state, action) => {
const { todo, completed, name } = action.payload;
const todoIndex = original(state).findIndex(t => t === todo);
if (todoIndex < 0) return;
if (action.type === 'completed') state[todoIndex].completed = completed;
if (action.type === 'rename' && name) state[todoIndex].name = name;
if (action.type === 'remove') state.splice(todoIndex, 1);
}, initialState);
const onChange = action => dispatch(action);
return (
<div>
<h1>State Demo</h1>
<TodoList todos={todos} onChange={onChange} />
</div>
);
};
export default UseReducer;

47
src/apps/immer/App.jsx Normal file
View File

@@ -0,0 +1,47 @@
/* eslint no-param-reassign: 0 */
import React from 'react';
import initialState from '../../initial_state';
import TodoList from '../../components/TodoList';
import { useImmer } from '../../hooks/use_immer';
const Immer = () => {
const [todos, setTodos] = useImmer(initialState);
const onChange = action => {
const { todo, completed, name } = action.payload;
const todoIndex = todos.findIndex(t => t === todo);
if (todoIndex < 0) return;
switch (action.type) {
case 'completed': {
setTodos(draft => {
draft[todoIndex].completed = completed;
});
break;
}
case 'rename': {
if (name)
setTodos(draft => {
draft[todoIndex].name = name;
});
break;
}
case 'remove': {
setTodos(draft => {
draft.splice(todoIndex, 1);
});
break;
}
default:
}
};
return (
<div>
<h1>Immer Demo</h1>
<TodoList todos={todos} onChange={onChange} />
</div>
);
};
export default Immer;

View File

@@ -0,0 +1,25 @@
/* eslint no-param-reassign: 0 */
import React from 'react';
import TodoList from '../../components/TodoList';
import initialState from '../../initial_state';
import { todoState } from './state/todos';
const TodoState = () => {
const [, actions] = todoState(initialState);
const onChange = action => {
const { todo, completed, name } = action.payload;
if (action.type === 'completed') actions.setCompleted(todo, completed);
if (action.type === 'rename') actions.setName(todo, name);
if (action.type === 'remove') actions.remove(todo);
};
return (
<div>
<h1>State Demo</h1>
<TodoList onChange={onChange} />
</div>
);
};
export default TodoState;

View File

@@ -0,0 +1,46 @@
/* eslint no-param-reassign: 0 */
import { useEffect } from 'react';
import { useImmerReducer, original } from '../../../hooks/use_immer';
let currentState;
export const setState = state => {
currentState = state;
};
export function todoState(initialState) {
if (!currentState && initialState) setState(initialState);
const [todos, dispatch] = useImmerReducer(
(state, action) => {
const { todo, completed, name } = action.payload;
const todoIndex = original(state).findIndex(t => t === todo);
if (todoIndex < 0) return;
if (action.type === 'setState') state = action.payload;
if (action.type === 'completed') state[todoIndex].completed = completed;
if (action.type === 'rename' && name) state[todoIndex].name = name;
if (action.type === 'remove') state.splice(todoIndex, 1);
},
currentState,
state => state || []
);
useEffect(() => {
dispatch({ type: 'setState', currentState });
}, [currentState]);
const actions = {
setCompleted(todo, completed = true) {
dispatch({ type: 'completed', payload: { todo, completed } });
},
setName(todo, name) {
dispatch({ type: 'rename', payload: { todo, name } });
},
remove(todo) {
dispatch({ type: 'remove', payload: { todo } });
},
};
return [todos, actions];
}

View File

@@ -0,0 +1,44 @@
import React, { useReducer } from 'react';
import initialState from '../../initial_state';
import TodoList from '../../components/TodoList';
const UseReducer = () => {
const [todos, dispatch] = useReducer((state, action) => {
const { todo, completed, name } = action.payload;
const todoIndex = state.findIndex(t => t === todo);
if (todoIndex < 0) return state;
switch (action.type) {
case 'completed': {
const newState = [...state];
newState[todoIndex].completed = completed;
return newState;
}
case 'rename': {
if (!name) return state;
const newState = [...state];
newState[todoIndex].name = name;
return newState;
}
case 'remove': {
const newState = [...state];
newState.splice(todoIndex, 1);
return newState;
}
default:
return state;
}
}, initialState);
const onChange = action => dispatch(action);
return (
<div>
<h1>State Demo</h1>
<TodoList todos={todos} onChange={onChange} />
</div>
);
};
export default UseReducer;

View File

@@ -0,0 +1,44 @@
import React, { useState } from 'react';
import initialState from '../../initial_state';
import TodoList from '../../components/TodoList';
const UseState = () => {
const [todos, setTodos] = useState(initialState);
const onChange = action => {
const { todo, completed, name } = action.payload;
const todoIndex = todos.findIndex(t => t === todo);
if (todoIndex < 0) return;
switch (action.type) {
case 'completed': {
const newState = [...todos];
newState[todoIndex].completed = completed;
setTodos(newState);
break;
}
case 'rename': {
const newState = [...todos];
newState[todoIndex].name = name;
setTodos(newState);
break;
}
case 'remove': {
const newState = [...todos];
newState.splice(todoIndex, 1);
setTodos(newState);
break;
}
default:
}
};
return (
<div>
<h1>State Demo</h1>
<TodoList todos={todos} onChange={onChange} />
</div>
);
};
export default UseState;

View File

@@ -0,0 +1,34 @@
body {
font-size: 16px;
}
button {
border: 1px solid #666;
padding: 10px;
font-size: 1.2em;
}
button.completed {
background-color: green;
color: white;
}
button.edit {
color: blue;
font-weight: bold;
border: 0;
}
button.remove {
color: red;
font-weight: bold;
border: 0;
}
ul {
list-style: none;
}
ul li {
margin: 4px 0;
}

View File

@@ -0,0 +1,26 @@
import React from 'react';
const TodoItem = ({ todo, onComplete, onRename, onRemove }) => {
const className = todo.completed ? 'completed' : 'incomplete';
const editName = () => {
const name = window.prompt('Enter new name'); // eslint-disable-line no-alert
onRename(name);
};
return (
<li>
<button type="button" className={className} onClick={onComplete}>
{todo.name}
</button>
<button type="button" className="edit" onClick={editName}>
Edit
</button>
<button type="button" className="remove" onClick={onRemove}>
X
</button>
</li>
);
};
export default TodoItem;

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { todoState } from '../apps/todo-state/state/todos';
import TodoItem from './TodoItem';
const TodoList = ({ onChange }) => {
const [todos] = todoState();
const onComplete = todo => () => {
onChange({ type: 'completed', payload: { todo, completed: !todo.completed } });
};
const onRemove = todo => () => {
onChange({ type: 'remove', payload: { todo } });
};
const onRename = todo => name => {
onChange({ type: 'rename', payload: { todo, name } });
};
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.name}
todo={todo}
onComplete={onComplete(todo)}
onRemove={onRemove(todo)}
onRename={onRename(todo)}
/>
))}
</ul>
);
};
export default TodoList;

19
src/hooks/use_immer.js Normal file
View File

@@ -0,0 +1,19 @@
import React from 'react';
import { produce } from 'immer';
// useful in useReducer since draft state is a proxy
export { original } from 'immer';
export function useImmer(initialValue) {
const [val, updateValue] = React.useState(initialValue);
return [
val,
updater => {
updateValue(produce(updater));
},
];
}
export function useImmerReducer(reducer, initialState, initialAction) {
return React.useReducer(produce(reducer), initialState, initialAction);
}

View File

@@ -1,5 +1,5 @@
import ReactDOM from 'react-dom';
import './assets/css/style.css';
import App from './views/App';
import App from './App';
ReactDOM.render(App, document.getElementById('app'));

6
src/initial_state.js Normal file
View File

@@ -0,0 +1,6 @@
const items = ['eat', 'sleep', 'breath'].map(t => ({
name: t,
complete: false,
}));
export default items;

View File

@@ -1,5 +0,0 @@
import React from 'react';
const App = () => <div>Hello World!</div>;
export default App();