From bf5f5688031382578105b9857806b058a0ce897a Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 5 Jan 2023 21:43:09 +1100
Subject: [PATCH 1/2] Add async example
---
examples/examples/async/github.ts | 48 ++++++++++++++++++++
examples/examples/async/index.tsx | 54 +++++++++++++++++++++++
examples/examples/async/styles.module.css | 11 +++++
examples/next.config.js | 2 +-
examples/pages/async.tsx | 1 +
examples/pages/index.tsx | 4 ++
6 files changed, 119 insertions(+), 1 deletion(-)
create mode 100644 examples/examples/async/github.ts
create mode 100644 examples/examples/async/index.tsx
create mode 100644 examples/examples/async/styles.module.css
create mode 100644 examples/pages/async.tsx
diff --git a/examples/examples/async/github.ts b/examples/examples/async/github.ts
new file mode 100644
index 0000000..b3c60ec
--- /dev/null
+++ b/examples/examples/async/github.ts
@@ -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 };
diff --git a/examples/examples/async/index.tsx b/examples/examples/async/index.tsx
new file mode 100644
index 0000000..acf5c24
--- /dev/null
+++ b/examples/examples/async/index.tsx
@@ -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
Loading...
;
+ if (error) return Oops! Something went wrong.
;
+ if (!data) return null;
+ return (
+ <>
+ Hi {data.name},
+ Here are your top repositories:
+
+ {topRepos?.map(({ id, name, description }) => (
+ -
+ {name}
+ - {description}
+
+ ))}
+
+ >
+ );
+ };
+
+ return (
+
+
+
+ {renderUser()}
+
+ );
+}
diff --git a/examples/examples/async/styles.module.css b/examples/examples/async/styles.module.css
new file mode 100644
index 0000000..7bf9b24
--- /dev/null
+++ b/examples/examples/async/styles.module.css
@@ -0,0 +1,11 @@
+.wrapper {
+ padding: 1rem;
+}
+
+.desc {
+ color: #777;
+}
+
+.userInput {
+ margin: 0 0.5rem;
+}
diff --git a/examples/next.config.js b/examples/next.config.js
index f89c308..953bf58 100644
--- a/examples/next.config.js
+++ b/examples/next.config.js
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
- reactStrictMode: true,
+ reactStrictMode: false,
swcMinify: true
};
diff --git a/examples/pages/async.tsx b/examples/pages/async.tsx
new file mode 100644
index 0000000..86a5c90
--- /dev/null
+++ b/examples/pages/async.tsx
@@ -0,0 +1 @@
+export { default } from '../examples/async';
diff --git a/examples/pages/index.tsx b/examples/pages/index.tsx
index 71e3648..d0f0f9d 100644
--- a/examples/pages/index.tsx
+++ b/examples/pages/index.tsx
@@ -12,6 +12,7 @@ export default function Home() {
+ Reactish-State examples
Counter
@@ -19,6 +20,9 @@ export default function Home() {
Todo
+
+ Async
+
From ae0aea8c4b26d85245505a11457cdd0f9c9856b8 Mon Sep 17 00:00:00 2001
From: Zheng Song <41896553+szhsin@users.noreply.github.com>
Date: Thu, 5 Jan 2023 22:17:51 +1100
Subject: [PATCH 2/2] Update examples
---
examples/examples/counter/Counter.tsx | 36 +++++++------
examples/examples/todo/AddTodo.tsx | 9 +++-
examples/examples/todo/Filters.tsx | 3 +-
examples/examples/todo/TodoList.tsx | 14 ++---
examples/examples/todo/styles.module.css | 36 ++++++++++++-
examples/examples/todo/todo.ts | 5 +-
examples/pages/index.tsx | 8 +--
examples/styles/Home.module.css | 67 +-----------------------
8 files changed, 82 insertions(+), 96 deletions(-)
diff --git a/examples/examples/counter/Counter.tsx b/examples/examples/counter/Counter.tsx
index 75b1e0f..3d36955 100644
--- a/examples/examples/counter/Counter.tsx
+++ b/examples/examples/counter/Counter.tsx
@@ -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);
@@ -67,7 +69,7 @@ const Counter = ({ id = 1 }: { id: number | string }) => {
-
+
diff --git a/examples/examples/todo/AddTodo.tsx b/examples/examples/todo/AddTodo.tsx
index ecc312f..4048995 100644
--- a/examples/examples/todo/AddTodo.tsx
+++ b/examples/examples/todo/AddTodo.tsx
@@ -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 (
-
setText(e.currentTarget.value.trim())} />
+
setText(e.currentTarget.value.trim())}
+ />