Closed jbhoot closed 3 weeks ago
Is there a way to simplify this interface?
I think so. Could you please provide a concrete example so that we can suggest a less verbose approach? Or perhaps we can come up with a less verbose API.
Sure, here is an example. Forgive any syntax error in the code; my original code is in ReScript (a language that transpiles to JavaScript), and I translated it to JavaScript here for convenience.
let [sValueChanged, handleValueChange] = createSignal(e => getEventValue(e));
let [useRawValue, ssRawValue] = bind(sValueChanged, '');
let [useParsedValue, ssParsedValue] = bind(
ssRawValue.pipe(
map(v => {
// return Ok(int) or Error(string).
}),
),
)
const Threshold = () => {
const rawValue = useRawValue();
const parsedValue = useParsedValue();
const id = "ThresholdInput";
return <div>
<label htmlFor={id}>Threshold you would like to cover</label>
<div>
<input
type="text"
id={id}
name={id}
defaultValue={rawValue}
onChange={handleValueChange}
/>
<span>tonne/hectare</span>
</div>
{parsedValue.error && <p>{parsedValue.error}</p>}
</div>
}
Please let me know if you need more input from me.
I personally prefer the newer API that uses useStateObservable
hook instead - It's probably due to personal preference, but that decreases the amount of different variables you'd be using. With this you could rerwite it to:
import { state, useStateObservable } from '@react-rxjs/core'
import { createSignal } from '@react-rxjs/utils';
const [sValueChanged, handleValueChange] = createSignal(e => getEventValue(e));
const ssRawValue = state(sValueChanged, '');
const ssParsedValue = ssRawValue.pipeState(
map(v => {
// return Ok(int) or Error(string).
}),
)
const Threshold = () => {
const rawValue = useStateObservable(ssRawValue);
const parsedValue = useStateObservable(ssParsedValue);
const id = "ThresholdInput";
return <div>
<label htmlFor={id}>Threshold you would like to cover</label>
<div>
<input
type="text"
id={id}
name={id}
defaultValue={rawValue}
onChange={handleValueChange}
/>
<span>tonne/hectare</span>
</div>
{parsedValue.error && <p>{parsedValue.error}</p>}
</div>
}
As for getting it more concise, something that we've seen a common pattern is the createSignal + state
combo, but I feel like it's trivial to build abstractions that fill their own needs, so at the moment I'd rather not increase the API surface. But having something like:
export const createStatefulSignal = <T, I>(mapper: (input: I) => T, defaultValue: T) => {
const [valueChange$, setValue] = createSignal(mapper);
const state$ = state(valueChange$, defaultValue);
return [state$, setValue]
}
Might be enough for your use case. But again, it's not something we want to add at this moment because there are different versions which might fit better different use cases (such as also exporting the valueChange$
, in regards to the mapper, or defaultValue
, or having it reset, etc.), so I think it's better to let everyone deal their own utilities for this, and leave React-RxJS with smaller composable primitives.
With this utility the code would be reduced to:
const [ssRawValue, handleValueChange] = createStatefulSignal(e => getEventValue(e), '');
const ssParsedValue = ssRawValue.pipeState(
map(v => {
// return Ok(int) or Error(string).
}),
)
Wow. So, state
does the same thing as bind
.
I personally prefer the newer API that uses useStateObservable hook instead.
I looked in the docs further, and saw this in the bind
API page:
function bind<T>(
observable: Observable<T>,
defaultValue?: T,
) {
const state$ = state(observable, defaultValue);
return [
() => useStateObservable(state$),
state$
];
}
So, state
is a lower-level abstraction used by bind
, even though the former feels simpler to use.
My guess is when you referred to the state APIs as the newer API, you might have been talking about useStateObservable
, not the state()
function.
createSignal + state combo : createStatefulSignal()
I've given this a shot, but as you described, this abstraction faltered for a few scenarios, like producing two state streams that depend on a single valueChange$
.
However, now that I'm writing this, I realised that I can just use the shared stream returned by createStatefulSignal
to produce two state streams. I'm missing something here.
Anyway, state + useStateObservable
is a much more intuitive API than bind + useNameAHandlerForEveryStream
API to me. A Jotai user would object less to the former API.
I use this library to manage state in a project at work. I love it.
Though I wish that the react-rxjs integration interface were simpler and less verbose than it is.
Currently, a minimum of 4 variables need to be tracked for one state stream:
useX
hook to access a value from the shared streamThis hurdle becomes too much for newcomers.
Is there a way to simplify this interface?