Skip to content

Commit

Permalink
Merge pull request #16 from szhsin/docs/async-example
Browse files Browse the repository at this point in the history
docs: async example
  • Loading branch information
szhsin authored Jan 5, 2023
2 parents e9fc9b4 + ae0aea8 commit feb021c
Show file tree
Hide file tree
Showing 13 changed files with 200 additions and 96 deletions.
48 changes: 48 additions & 0 deletions examples/examples/async/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { createState, selector } from 'reactish-state';
import { reduxDevtools } from 'reactish-state/middleware';

const state = createState({ middleware: reduxDevtools({ name: 'github' }) });

interface UserState {
loading?: boolean;
error?: unknown;
data?: {
name: string;
repos: { id: number; name: string; description: string; stargazers_count: number }[];
};
}

const user = state(
{} as UserState,
(set) => ({
fetch: async (userName: string) => {
set({ loading: true }, 'user/fetch/pending');

try {
const userRes = await (await fetch(`https://api.github.com/users/${userName}`)).json();
const repoRes = await (await fetch(userRes.repos_url)).json();
set(
{
data: {
name: userRes.name,
repos: repoRes
}
},
'user/fetch/fulfilled'
);
} catch (error) {
set({ error }, 'user/fetch/rejected');
}
}
}),
{ key: 'user' }
);

const topRepositories = selector(user, (user) =>
user.data?.repos
.slice()
.sort((repo1, repo2) => repo2.stargazers_count - repo1.stargazers_count)
.slice(0, 5)
);

export { user, topRepositories };
54 changes: 54 additions & 0 deletions examples/examples/async/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useState, useEffect } from 'react';
import { useSnapshot } from 'reactish-state';
import { user, topRepositories } from './github';
import styles from './styles.module.css';

const { fetch } = user.actions;

export default function AsyncExample() {
const [userName, setUserName] = useState('szhsin');
const { loading, data, error } = useSnapshot(user);
const topRepos = useSnapshot(topRepositories);

useEffect(() => {
fetch('szhsin');
}, []);

const renderUser = () => {
if (loading) return <h3>Loading...</h3>;
if (error) return <h3>Oops! Something went wrong.</h3>;
if (!data) return null;
return (
<>
<h3>Hi {data.name},</h3>
<div>Here are your top repositories:</div>
<ul>
{topRepos?.map(({ id, name, description }) => (
<li key={id}>
{name}
<span className={styles.desc}> - {description}</span>
</li>
))}
</ul>
</>
);
};

return (
<div className={styles.wrapper}>
<label>
Type a github user or organization name:
<input
className={styles.userInput}
type="text"
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
</label>
<button onClick={() => fetch(userName)} disabled={loading || !userName.length}>
Fetch
</button>
{renderUser()}
</div>
);
}
11 changes: 11 additions & 0 deletions examples/examples/async/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.wrapper {
padding: 1rem;
}

.desc {
color: #777;
}

.userInput {
margin: 0 0.5rem;
}
36 changes: 19 additions & 17 deletions examples/examples/counter/Counter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,40 @@ const reducer = (state: number, { type, by = 1 }: { type: ActionTypes; by?: numb

const persistMiddleware = persist({ prefix: 'counter-', getStorage: () => sessionStorage });

const counterState = createState({
const counter = createState({
middleware: applyMiddleware([reduxDevtools({ name: 'counterApp-state' }), persistMiddleware])
})(
0,
(set, get) => ({
// The function updater of `set` receives the current state and should return a new state
increase: () => set((i) => i + 1),
// The current state can be also retrieved with the `get`
increaseBy: (by: number) => set(get() + by),
reset: () => set(0),
// The redux style dispatch function
dispatch: (action: { type: ActionTypes; by?: number }) =>
set((state) => reducer(state, action), action)
}),
{ key: 'count' }
);

const doubleCount = selector(counterState, (count) => count * 2);
const quadrupleCount = selector(doubleCount, (count) => count * 2);
const countSummary = selector(
counterState,
doubleCount,
quadrupleCount,
(count, doubleCount, quadrupleCount) => ({
doubleCount,
quadrupleCount,
sum: count + doubleCount + quadrupleCount
})
);
// selector is a piece of derived state from one or more states
const double = selector(counter, (state) => state * 2);

// selector can be derived from other selectors
const quadruple = selector(double, (state) => state * 2);
const summarySelector = selector(counter, double, quadruple, (count, double, quadruple) => ({
count,
double,
quadruple,
sum: count + double + quadruple
}));

const Counter = ({ id = 1 }: { id: number | string }) => {
const [step, setStep] = useState(1);
const count = useSnapshot(counterState);
const summary = useSnapshot(countSummary);
const { increase, increaseBy, reset, dispatch } = counterState.actions;
const count = useSnapshot(counter);
const summary = useSnapshot(summarySelector);
const { increase, increaseBy, reset, dispatch } = counter.actions;

console.log(`#${id} count: ${count}`, 'summary:', summary);

Expand All @@ -67,7 +69,7 @@ const Counter = ({ id = 1 }: { id: number | string }) => {
</div>

<div>
<button onClick={() => counterState.set(count - step)}>- {step}</button>
<button onClick={() => counter.set(count - step)}>- {step}</button>
<button onClick={() => increaseBy(step)}>+ {step}</button>
<button onClick={() => increase()}>+ 1</button>
<button onClick={() => dispatch({ type: 'INCREASE', by: 7 })}>+ 7</button>
Expand Down
9 changes: 8 additions & 1 deletion examples/examples/todo/AddTodo.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { useState } from 'react';
import { todoListState } from './todo';
import styles from './styles.module.css';

const AddTodo = () => {
const [text, setText] = useState('');

return (
<div>
<input type="text" value={text} onChange={(e) => setText(e.currentTarget.value.trim())} />
<input
type="text"
placeholder="Add an item"
value={text}
onChange={(e) => setText(e.currentTarget.value.trim())}
/>
<button
className={styles.add}
disabled={!text.trim().length}
onClick={() => {
todoListState.actions.addItem(text);
Expand Down
3 changes: 2 additions & 1 deletion examples/examples/todo/Filters.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { useSnapshot } from 'reactish-state';
import { visibilityFilterState, VisibilityFilter } from './todo';
import styles from './styles.module.css';

const filterList: VisibilityFilter[] = ['ALL', 'IN_PROGRESS', 'COMPLETED'];

const Filters = () => {
const visibilityFilter = useSnapshot(visibilityFilterState);

return (
<div>
<div className={styles.filters}>
{filterList.map((filter) => (
<label key={filter}>
<input
Expand Down
14 changes: 8 additions & 6 deletions examples/examples/todo/TodoList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ const TodoList = () => {
const { toggleItem, deleteItem } = todoListState.actions;

return (
<ul>
{todos.map(({ id, text, isCompleted: isDone }) => (
<ul className={styles.todos}>
{todos.map(({ id, text, isCompleted }) => (
<li key={id} className={styles.todo}>
<label>
<input type="checkbox" checked={isDone} onChange={() => toggleItem(id)} />
{text}
<label className={styles.todoLabel}>
<input type="checkbox" checked={isCompleted} onChange={() => toggleItem(id)} />
<span className={isCompleted ? styles.completed : ''}>{text}</span>
</label>
<button onClick={() => deleteItem(id)}>Delete</button>
<button className={styles.delete} onClick={() => deleteItem(id)}>
Delete
</button>
</li>
))}
</ul>
Expand Down
36 changes: 35 additions & 1 deletion examples/examples/todo/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
.app {
max-width: 480px;
max-width: 360px;
margin: 0 auto;
padding: 1rem;
}

.todos {
padding: 0;
}

.todo {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 0.25rem;
}

.todo:hover {
background-color: rgba(128, 128, 128, 0.2);
}

.todo:hover .delete {
opacity: 1;
}

.todoLabel {
flex-grow: 1;
}

.completed {
text-decoration: line-through;
}

.add {
margin-left: 0.5rem;
}

.delete {
opacity: 0;
}

.filters {
margin: 1rem 0;
}
5 changes: 4 additions & 1 deletion examples/examples/todo/todo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ const todoListState = state(
[] as Todo[],
(set, get) => ({
addItem: (text: string) =>
// The function updater of `set` receives the current state and should return a new state immutably
set((todos) => [...todos, { id: Date.now(), text, isCompleted: false }], 'todos/addItem'),
toggleItem: (id: number) =>
// The current state can be also retrieved with the `get`
set(
get().map((item) => (item.id === id ? { ...item, isCompleted: !item.isCompleted } : item)),
{ type: 'todos/toggleItem', id }
),
deleteItem: (id: number) =>
// You can also mutate the state because we have set up the `immer` middleware
set(
(todos) => {
const index = todos.findIndex((item) => item.id === id);
Expand All @@ -37,7 +40,7 @@ const todoListState = state(
);

type VisibilityFilter = 'ALL' | 'COMPLETED' | 'IN_PROGRESS';
const visibilityFilterState = state('IN_PROGRESS' as VisibilityFilter, null, { key: 'filter' });
const visibilityFilterState = state('ALL' as VisibilityFilter, null, { key: 'filter' });

const selector = createSelector({ plugin: devtoolsPlugin({ name: 'todoApp-selector' }) });
const visibleTodoList = selector(
Expand Down
2 changes: 1 addition & 1 deletion examples/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
reactStrictMode: false,
swcMinify: true
};

Expand Down
1 change: 1 addition & 0 deletions examples/pages/async.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '../examples/async';
10 changes: 8 additions & 2 deletions examples/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@ import Head from 'next/head';
import Link from 'next/link';
import styles from '../styles/Home.module.css';

const TITLE = 'Reactish-State examples';

export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Examples</title>
<meta name="description" content="reactish-state examples" />
<title>{TITLE}</title>
<meta name="description" content={TITLE} />
<link rel="icon" href="/favicon.ico" />
</Head>

<main className={styles.main}>
<h1>{TITLE}</h1>
<div className={styles.grid}>
<Link href="/counter" className={styles.card}>
<h2>Counter</h2>
</Link>
<Link href="/todo" className={styles.card}>
<h2>Todo</h2>
</Link>
<Link href="/async" className={styles.card}>
<h2>Async</h2>
</Link>
</div>
</main>
</div>
Expand Down
Loading

0 comments on commit feb021c

Please sign in to comment.