feat: a bunch of examples
This commit is contained in:
@@ -31,5 +31,7 @@ module.exports = {
|
|||||||
'peerDependencies': false
|
'peerDependencies': false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
'react/jsx-one-expression-per-line': 'off',
|
||||||
|
'react/prop-types': 'off',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,5 @@ module.exports = {
|
|||||||
// Our default preset
|
// Our default preset
|
||||||
'poi/babel',
|
'poi/babel',
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [],
|
||||||
// This adds Hot Reloading support
|
|
||||||
'react-hot-loader/babel',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-env node */
|
/* eslint-env node */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: 'src/index',
|
entry: 'src/index',
|
||||||
plugins: ['@poi/plugin-eslint'],
|
// plugins: ['@poi/plugin-eslint'],
|
||||||
};
|
};
|
||||||
|
|||||||
39
src/App.jsx
Normal file
39
src/App.jsx
Normal 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
25
src/apps/Home.jsx
Normal 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;
|
||||||
28
src/apps/immer-reducer/App.jsx
Normal file
28
src/apps/immer-reducer/App.jsx
Normal 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
47
src/apps/immer/App.jsx
Normal 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;
|
||||||
25
src/apps/todo-state/App.jsx
Normal file
25
src/apps/todo-state/App.jsx
Normal 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;
|
||||||
46
src/apps/todo-state/state/todos.js
Normal file
46
src/apps/todo-state/state/todos.js
Normal 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];
|
||||||
|
}
|
||||||
44
src/apps/use-reducer/App.jsx
Normal file
44
src/apps/use-reducer/App.jsx
Normal 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;
|
||||||
44
src/apps/use-state/App.jsx
Normal file
44
src/apps/use-state/App.jsx
Normal 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;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
26
src/components/TodoItem.jsx
Normal file
26
src/components/TodoItem.jsx
Normal 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;
|
||||||
35
src/components/TodoList.jsx
Normal file
35
src/components/TodoList.jsx
Normal 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
19
src/hooks/use_immer.js
Normal 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);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './assets/css/style.css';
|
import './assets/css/style.css';
|
||||||
import App from './views/App';
|
import App from './App';
|
||||||
|
|
||||||
ReactDOM.render(App, document.getElementById('app'));
|
ReactDOM.render(App, document.getElementById('app'));
|
||||||
|
|||||||
6
src/initial_state.js
Normal file
6
src/initial_state.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const items = ['eat', 'sleep', 'breath'].map(t => ({
|
||||||
|
name: t,
|
||||||
|
complete: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default items;
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
const App = () => <div>Hello World!</div>;
|
|
||||||
|
|
||||||
export default App();
|
|
||||||
Reference in New Issue
Block a user