-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
V2 Basic demo setup for state management #741
Changes from all commits
b897cb9
93b65a7
df5355f
b18afe2
4b44926
f644d75
1a19c1f
7badf45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,14 @@ | ||
"use client"; | ||
|
||
import { useStore } from "@/store/useStore"; | ||
|
||
export default function CreateAccount() { | ||
return <div>Create Account</div>; | ||
const { network } = useStore(); | ||
|
||
return ( | ||
<div> | ||
Create Account | ||
<div>{`Current network: ${network?.id}`}</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,74 @@ | ||
"use client"; | ||
|
||
import { useStore } from "@/store/useStore"; | ||
import { Input, Button, Select } from "@stellar/design-system"; | ||
import { useState } from "react"; | ||
|
||
export default function FundAccount() { | ||
return <div>Fund Account</div>; | ||
const { | ||
account: { value, update, updateNested, reset }, | ||
} = useStore(); | ||
|
||
const [testValue, setTestValue] = useState(value); | ||
|
||
const handleUpdateNested = (event: any) => { | ||
const val = event.target.value; | ||
let submitValue = { | ||
nestedValue1: "AAA", | ||
nestedValue2: 111, | ||
}; | ||
|
||
if (val === "Two") { | ||
submitValue = { | ||
nestedValue1: "BBB", | ||
nestedValue2: 222, | ||
}; | ||
} | ||
|
||
updateNested(submitValue); | ||
}; | ||
|
||
return ( | ||
<div> | ||
Fund Account | ||
<div> | ||
<div>{`Test value: ${value}`}</div> | ||
|
||
<p>Store value is updated on blur</p> | ||
|
||
<Input | ||
id="test-1" | ||
fieldSize="sm" | ||
value={testValue} | ||
onChange={(event) => setTestValue(event.target.value)} | ||
onBlur={(event) => update(event.target.value)} | ||
label="Value" | ||
/> | ||
|
||
<p>Testing nested object update</p> | ||
|
||
<Select | ||
id="select-1" | ||
fieldSize="sm" | ||
onChange={handleUpdateNested} | ||
label="Nested value" | ||
> | ||
<option></option> | ||
<option value="One">One</option> | ||
<option value="Two">Two</option> | ||
</Select> | ||
|
||
<Button | ||
size="sm" | ||
variant="primary" | ||
onClick={() => { | ||
reset(); | ||
setTestValue(""); | ||
}} | ||
> | ||
Reset | ||
</Button> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
"use client"; | ||
|
||
// TODO: update Network selector. This is a demo for state/store. | ||
|
||
import { useCallback, useEffect } from "react"; | ||
import { Select } from "@stellar/design-system"; | ||
import { useStore } from "@/store/useStore"; | ||
|
||
export const NetworkSelector = () => { | ||
const { network, selectNetwork } = useStore(); | ||
|
||
const setNetwork = useCallback(() => { | ||
if (!network?.id) { | ||
// TODO: get from local storage | ||
selectNetwork({ | ||
id: "testnet", | ||
label: "testnet", | ||
url: "", | ||
passphrase: "", | ||
}); | ||
} | ||
}, [network?.id, selectNetwork]); | ||
|
||
// Set default network on launch | ||
useEffect(() => { | ||
setNetwork(); | ||
}, [setNetwork]); | ||
|
||
return ( | ||
<div> | ||
{`Network: ${network?.id}`} | ||
<Select | ||
id="network" | ||
fieldSize="sm" | ||
onChange={(event) => | ||
selectNetwork({ | ||
id: event.target.value, | ||
label: event.target.value, | ||
url: `url-${event.target.value}`, | ||
passphrase: `passphrase-${event.target.value}`, | ||
}) | ||
} | ||
value={network?.id || ""} | ||
> | ||
<option value=""></option> | ||
<option value="testnet">testnet</option> | ||
<option value="mainnet">mainnet</option> | ||
<option value="futurenet">futurenet</option> | ||
</Select> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
"use client"; | ||
import { createContext, ReactNode, useState } from "react"; | ||
import { usePathname, useSearchParams } from "next/navigation"; | ||
import { createStore } from "@/store/createStore"; | ||
|
||
export type StoreType = ReturnType<typeof createStore>; | ||
export const ZustandContext = createContext<StoreType | null>(null); | ||
|
||
export const StoreProvider = ({ children }: { children: ReactNode }) => { | ||
const pathname = usePathname(); | ||
const searchParams = useSearchParams(); | ||
const url = `${pathname}?${searchParams}`; | ||
|
||
const [store] = useState(() => | ||
createStore({ | ||
url, | ||
}), | ||
); | ||
|
||
return ( | ||
<ZustandContext.Provider value={store}>{children}</ZustandContext.Provider> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { create } from "zustand"; | ||
import { immer } from "zustand/middleware/immer"; | ||
import { querystring } from "zustand-querystring"; | ||
import { EmptyObj } from "@/types/types"; | ||
|
||
type Network = { | ||
id: string; | ||
label: string; | ||
url: string; | ||
passphrase: string; | ||
}; | ||
|
||
export interface Store { | ||
// Shared | ||
network: Network | EmptyObj; | ||
selectNetwork: (network: Network) => void; | ||
|
||
// Account | ||
account: { | ||
value: string; | ||
nestedObject: { | ||
nestedValue1: string; | ||
nestedValue2: number; | ||
}; | ||
update: (value: string) => void; | ||
updateNested: (nestedVal: { | ||
nestedValue1: string; | ||
nestedValue2: number; | ||
}) => void; | ||
reset: () => void; | ||
}; | ||
} | ||
|
||
interface CreateStoreOptions { | ||
url?: string; | ||
} | ||
|
||
// Store | ||
export const createStore = (options: CreateStoreOptions) => | ||
create<Store>()( | ||
querystring( | ||
immer((set) => ({ | ||
network: {}, | ||
selectNetwork: (network: Network) => | ||
set((state) => { | ||
state.network = network; | ||
}), | ||
account: { | ||
value: "", | ||
nestedObject: { | ||
nestedValue1: "", | ||
nestedValue2: 0, | ||
}, | ||
update: (value: string) => | ||
set((state) => { | ||
state.account.value = value; | ||
}), | ||
updateNested: (nestedVal: { | ||
nestedValue1: string; | ||
nestedValue2: number; | ||
}) => | ||
set((state) => { | ||
state.account.nestedObject = nestedVal; | ||
}), | ||
reset: () => | ||
set((state) => { | ||
state.account.value = ""; | ||
}), | ||
}, | ||
})), | ||
{ | ||
url: options.url, | ||
// Select what to save in query string | ||
select() { | ||
return { | ||
network: true, | ||
account: true, | ||
}; | ||
Comment on lines
+74
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This part controls what we want to keep in the URL |
||
}, | ||
key: "||", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want this vs. its default There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jeesunikim I thought it was a safer option because we could have |
||
}, | ||
), | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
"use client"; | ||
import { useContext } from "react"; | ||
import { useStore as useZustandStore } from "zustand"; | ||
import { Store } from "@/store/createStore"; | ||
import { ZustandContext } from "@/store/StoreProvider"; | ||
|
||
export const useStore = <T = Store>(selector?: (state: Store) => T) => { | ||
selector ??= (state) => state as T; | ||
const store = useContext(ZustandContext); | ||
if (!store) throw new Error("Store is missing the provider"); | ||
return useZustandStore(store, selector); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type EmptyObj = Record<PropertyKey, never>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1352,6 +1352,11 @@ ignore@^5.2.0: | |
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" | ||
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== | ||
|
||
immer@^10.0.3: | ||
version "10.0.3" | ||
resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.3.tgz#a8de42065e964aa3edf6afc282dfc7f7f34ae3c9" | ||
integrity sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A== | ||
|
||
immutable@^4.0.0: | ||
version "4.3.4" | ||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" | ||
|
@@ -1717,6 +1722,11 @@ locate-path@^6.0.0: | |
dependencies: | ||
p-locate "^5.0.0" | ||
|
||
lodash-es@^4.17.21: | ||
version "4.17.21" | ||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" | ||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== | ||
|
||
lodash.merge@^4.6.2: | ||
version "4.6.2" | ||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" | ||
|
@@ -2583,6 +2593,11 @@ uri-js@^4.2.2: | |
dependencies: | ||
punycode "^2.1.0" | ||
|
||
[email protected]: | ||
version "1.2.0" | ||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" | ||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== | ||
|
||
[email protected]: | ||
version "2.4.0" | ||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" | ||
|
@@ -2708,3 +2723,17 @@ yocto-queue@^0.1.0: | |
version "0.1.0" | ||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" | ||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== | ||
|
||
zustand-querystring@^0.0.19: | ||
version "0.0.19" | ||
resolved "https://registry.yarnpkg.com/zustand-querystring/-/zustand-querystring-0.0.19.tgz#18ac1c9258a21c6345aa09dbf8738133471c2ebd" | ||
integrity sha512-+lLMisaJOgEiEochoCYKKwodR4Gd6RdIcJu4j2uuM5HE/8YzXVwKEAWc/XnU46ixXT8dj33wpedm0+iWqgjOSQ== | ||
dependencies: | ||
lodash-es "^4.17.21" | ||
|
||
zustand@^4.5.0: | ||
version "4.5.0" | ||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.0.tgz#141354af56f91de378aa6c4b930032ab338f3ef0" | ||
integrity sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A== | ||
dependencies: | ||
use-sync-external-store "1.2.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can extract sections into the variables if that's easier to manage.