Files
boris/node_modules/observable-hooks/src/use-observable-eager-state.ts
Gigi 5d53a827e0 feat: initialize markr nostr bookmark client
- Add project structure with TypeScript, React, and Vite
- Implement nostr authentication using browser extension (NIP-07)
- Add NIP-51 compliant bookmark fetching and display
- Create minimal UI with login and bookmark components
- Integrate applesauce-core and applesauce-react libraries
- Add responsive styling with dark/light mode support
- Include comprehensive README with setup instructions

This is a minimal MVP for a nostr bookmark client that allows users to
view their bookmarks according to NIP-51 specification.
2025-10-02 07:17:07 +02:00

112 lines
2.8 KiB
TypeScript

import { useDebugValue, useEffect, useRef, useState } from "react";
import { Observable } from "rxjs";
import { useForceUpdate, useIsomorphicLayoutEffect } from "./helpers";
/**
* Optimized for safely getting synchronous values from hot or pure observables
* without triggering extra initial re-rendering.
*
* ⚠ If the observable is cold and with side effects
* they will be performed at least twice!
*
* By default this hook will subscribe to the observable at least twice.
* The first time is for getting synchronous value to prevent extra initial re-rendering.
* In concurrent mode this may happen more than one time.
*
* @template TState State.
*
* @param state$ An observable of state value.
*/
export function useObservableEagerState<TState>(
state$: Observable<TState>
): TState {
const forceUpdate = useForceUpdate();
const state$Ref = useRef(state$);
const errorRef = useRef<Error | null>();
const isAsyncEmissionRef = useRef(false);
const didSyncEmit = useRef(false);
const [state, setState] = useState<TState>(() => {
let state: TState;
state$
.subscribe({
next: value => {
didSyncEmit.current = true;
state = value;
},
error: error => {
errorRef.current = error;
},
})
.unsubscribe();
return state!;
});
// update the latest observable
// synchronously after render being committed
useIsomorphicLayoutEffect(() => {
state$Ref.current = state$;
});
useEffect(() => {
errorRef.current = null;
// keep in closure for checking staleness
const input$ = state$Ref.current;
let secondInitialValue = state;
const subscription = input$.subscribe({
next: value => {
if (input$ !== state$Ref.current) {
// stale observable
return;
}
if (isAsyncEmissionRef.current) {
// ignore synchronous value
// prevent initial re-rendering
setState(() => value);
} else {
secondInitialValue = value;
}
},
error: error => {
if (input$ !== state$Ref.current) {
// stale observable
return;
}
errorRef.current = error;
forceUpdate();
},
});
if (!isAsyncEmissionRef.current) {
// fix #86 where sync emission may happen before useEffect
if (secondInitialValue !== state) {
setState(() => secondInitialValue);
}
}
isAsyncEmissionRef.current = true;
return () => {
subscription.unsubscribe();
};
}, [state$]);
if (errorRef.current) {
// Let error boundary catch the error
throw errorRef.current;
}
if (didSyncEmit.current) {
useDebugValue(state);
return state;
} else {
throw new Error("Observable did not synchronously emit a value.");
}
}