@snort/system-react package
This commit is contained in:
56
packages/system-react/README.md
Normal file
56
packages/system-react/README.md
Normal file
@ -0,0 +1,56 @@
|
||||
## @snort/system-react
|
||||
|
||||
React hooks for @snort/system
|
||||
|
||||
Sample:
|
||||
```js
|
||||
import { useMemo } from "react"
|
||||
import { useRequestBuilder, useUserProfile } from "@snort/system-react";
|
||||
import { FlatNoteStore, NostrSystem, RequestBuilder, TaggedRawEvent } from "@snort/system"
|
||||
|
||||
// singleton nostr system class
|
||||
const System = new NostrSystem({});
|
||||
|
||||
// some bootstrap relays
|
||||
[
|
||||
"wss://relay.snort.social",
|
||||
"wss://nos.lol"
|
||||
].forEach(r => System.ConnectToRelay(r, { read: true, write: false }));
|
||||
|
||||
export function Note({ ev }: { ev: TaggedRawEvent }) {
|
||||
// get profile from cache or request a profile from relays
|
||||
const profile = useUserProfile(System, ev.pubkey);
|
||||
|
||||
return <div>
|
||||
Post by: {profile.name ?? profile.display_name}
|
||||
<p>
|
||||
{ev.content}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function UserPosts(props: { pubkey: string }) {
|
||||
const sub = useMemo(() => {
|
||||
const rb = new RequestBuilder("get-posts");
|
||||
rb.withFilter()
|
||||
.authors([props.pubkey])
|
||||
.kinds([1])
|
||||
.limit(10);
|
||||
|
||||
return rb;
|
||||
}, [props.pubkey]);
|
||||
|
||||
const data = useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, sub);
|
||||
return (
|
||||
<>
|
||||
{data.data.map(a => <Note ev={a} />)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function MyApp() {
|
||||
return (
|
||||
<UserPosts pubkey="63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed" />
|
||||
)
|
||||
}
|
||||
```
|
48
packages/system-react/example/example.tsx
Normal file
48
packages/system-react/example/example.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { useMemo } from "react"
|
||||
import { useRequestBuilder, useUserProfile } from "../src";
|
||||
|
||||
import { FlatNoteStore, NostrSystem, RequestBuilder, TaggedRawEvent } from "@snort/system"
|
||||
|
||||
const System = new NostrSystem({});
|
||||
|
||||
// some bootstrap relays
|
||||
[
|
||||
"wss://relay.snort.social",
|
||||
"wss://nos.lol"
|
||||
].forEach(r => System.ConnectToRelay(r, { read: true, write: false }));
|
||||
|
||||
export function Note({ ev }: { ev: TaggedRawEvent }) {
|
||||
const profile = useUserProfile(System, ev.pubkey);
|
||||
|
||||
return <div>
|
||||
Post by: {profile.name ?? profile.display_name}
|
||||
<p>
|
||||
{ev.content}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function UserPosts(props: { pubkey: string }) {
|
||||
const sub = useMemo(() => {
|
||||
const rb = new RequestBuilder("get-posts");
|
||||
rb.withFilter()
|
||||
.authors([props.pubkey])
|
||||
.kinds([1])
|
||||
.limit(10);
|
||||
|
||||
return rb;
|
||||
}, [props.pubkey]);
|
||||
|
||||
const data = useRequestBuilder<FlatNoteStore>(System, FlatNoteStore, sub);
|
||||
return (
|
||||
<>
|
||||
{data.data.map(a => <Note ev={a} />)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function MyApp() {
|
||||
return (
|
||||
<UserPosts pubkey="63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed" />
|
||||
)
|
||||
}
|
23
packages/system-react/package.json
Normal file
23
packages/system-react/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@snort/system-react",
|
||||
"version": "1.0.0",
|
||||
"description": "React hooks for @snort/system",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"repository": "https://git.v0l.io/Kieran/snort",
|
||||
"author": "Kieran",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@snort/system": "",
|
||||
"@snort/shared": "",
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
3
packages/system-react/src/index.ts
Normal file
3
packages/system-react/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./useRequestBuilder";
|
||||
export * from "./useSystemState";
|
||||
export * from "./useUserProfile";
|
40
packages/system-react/src/useRequestBuilder.tsx
Normal file
40
packages/system-react/src/useRequestBuilder.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { RequestBuilder, EmptySnapshot, NoteStore, StoreSnapshot, SystemInterface } from "@snort/system";
|
||||
import { unwrap } from "@snort/shared";
|
||||
|
||||
/**
|
||||
* Send a query to the relays and wait for data
|
||||
*/
|
||||
const useRequestBuilder = <TStore extends NoteStore, TSnapshot = ReturnType<TStore["getSnapshotData"]>>(
|
||||
system: SystemInterface,
|
||||
type: { new(): TStore },
|
||||
rb: RequestBuilder | null
|
||||
) => {
|
||||
const subscribe = (onChanged: () => void) => {
|
||||
if (rb) {
|
||||
const q = system.Query<TStore>(type, rb);
|
||||
const release = q.feed.hook(onChanged);
|
||||
q.uncancel();
|
||||
return () => {
|
||||
q.cancel();
|
||||
release();
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
// noop
|
||||
};
|
||||
};
|
||||
const getState = (): StoreSnapshot<TSnapshot> => {
|
||||
const q = system.GetQuery(rb?.id ?? "");
|
||||
if (q) {
|
||||
return unwrap(q).feed?.snapshot as StoreSnapshot<TSnapshot>;
|
||||
}
|
||||
return EmptySnapshot as StoreSnapshot<TSnapshot>;
|
||||
};
|
||||
return useSyncExternalStore<StoreSnapshot<TSnapshot>>(
|
||||
v => subscribe(v),
|
||||
() => getState()
|
||||
);
|
||||
};
|
||||
|
||||
export { useRequestBuilder };
|
10
packages/system-react/src/useSystemState.tsx
Normal file
10
packages/system-react/src/useSystemState.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { SystemSnapshot } from "@snort/system";
|
||||
import { ExternalStore } from "@snort/shared";
|
||||
|
||||
export function useSystemState(system: ExternalStore<SystemSnapshot>) {
|
||||
return useSyncExternalStore<SystemSnapshot>(
|
||||
cb => system.hook(cb),
|
||||
() => system.snapshot()
|
||||
);
|
||||
}
|
23
packages/system-react/src/useUserProfile.ts
Normal file
23
packages/system-react/src/useUserProfile.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { HexKey, MetadataCache, NostrSystem } from "@snort/system";
|
||||
|
||||
/**
|
||||
* Gets a profile from cache or requests it from the relays
|
||||
*/
|
||||
export function useUserProfile(system: NostrSystem, pubKey?: HexKey): MetadataCache | undefined {
|
||||
return useSyncExternalStore<MetadataCache | undefined>(
|
||||
h => {
|
||||
if (pubKey) {
|
||||
system.ProfileLoader.TrackMetadata(pubKey);
|
||||
}
|
||||
const release = system.ProfileLoader.Cache.hook(h, pubKey);
|
||||
return () => {
|
||||
release();
|
||||
if (pubKey) {
|
||||
system.ProfileLoader.UntrackMetadata(pubKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
() => system.ProfileLoader.Cache.getFromCache(pubKey)
|
||||
);
|
||||
}
|
19
packages/system-react/tsconfig.json
Normal file
19
packages/system-react/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"target": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitOverride": true,
|
||||
"module": "CommonJS",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSourceMap": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"files": ["src/index.ts"]
|
||||
}
|
Reference in New Issue
Block a user