raveclassic / frp-ts Goto Github PK
View Code? Open in Web Editor NEWFunctional reactive values-over-time
License: MIT License
Functional reactive values-over-time
License: MIT License
Hi there!
I'm trying to use your lib with react, but there are no reactions in the component
Does need anything else for reaction?
And one more question, does this library work with React Native?
Sandbox link
https://codesandbox.io/s/dreamy-benz-5utty9?file=/src/App.js
fromObservable
operator should subscribe to observable only if there is a subscription to the property itself.
Now this happens immediately after the call, which deprives all the benefits of the lazy observable.
@raveclassic, hi!
Looks like typescript is finally ready to express combine function using tuple types:
type PropertyValue<A> = A extends Property<infer U> ? U : never;
type MapPropsToValues<PS extends [Property<unknown>, ...Property<unknown>[]]> = {
[K in keyof PS]: PropertyValue<PS[K]>;
};
function combine<PS extends [Property<unknown>, ...Property<unknown>[]], B>(
...args: [...PS, (...values: MapPropsToValues<PS>) => B]
): Property<B>;
Correctly inferred types:
const p1 = constProperty(1);
const p2 = constProperty('');
const p3 = constProperty(true);
const p4 = constProperty(new Date());
const pr = combine(p1, p2, p3, p4, (v1, v2, v3, v4) => {
return { name: 'Bar' };
});
Would you accept a PR that adds combine
to property
API?
One of the examples provided in the readme fails to compile. The following snippet is copy pasted directly from the readme.
import { newAtom, Property } from "@frp-ts/core"
interface Counter extends Property<number> {
readonly inc: () => void
}
const newCounter = (initial: number): Counter => {
const state = newAtom(initial)
const inc = () => state.modify((n) => n + 1)
return {
subscribe: state.subscribe,
get: state.get,
inc,
}
}
The snippet above fails to compile with the following error
Property '[Symbol.observable]' is missing in type '{ subscribe: (observer: Observer<number>) => Subscription; get: () => number; inc: () => void; }' but required in type 'Counter'.
Versions are as follows
"dependencies": {
"@frp-ts/core": "^1.0.0-alpha.13",
"@frp-ts/fp-ts": "^1.0.0-alpha.13",
"@frp-ts/lens": "^1.0.0-alpha.13",
"frp-ts": "^0.0.1"
},
"devDependencies": {
"typescript": "^4.5.5"
}
As mentioned in #29 we need to clearly describe why Property
/Atom
filter out duplicates by default.
Recent migration to nx
+ pnpm
completely disabled coverage threshold check. We need to bring it back.
Hi @raveclassic !
I see that atoms has built-in deduping logic for referentially equal values:
frp-ts/packages/core/src/atom.ts
Lines 20 to 25 in af803aa
Perhaps it's worth to move out filtering logic to dedicated property operator?
For source code license to work correctly, it's worth adding a file LICENSE
describing the type of license and who has these rights.
Currently we create a new Observer
for each dependent Observable
in mergeMany
. This can be optimized to creating a single Observer
for all Observable
s as it's only bound to lastNotifiedTime
and listener
.
Additionally, it might make sense to start lastNotifiedTime
with -Infinity
instead of Infinity
.
To see which packages are published within the repository and which latest versions are available, need to link existed packages to github repository.
Hi there!
I have question regarding proper approach on working with properties which has duped value. How to obtain them when atoms have deduping logic on set? Well pretty easy - just put some complex data structure into atom (e.g. object) and then derive property with primitive value.
So what confusing me is that previously (in 0.0.x versions) we had some operators like map
:
Lines 59 to 65 in 8c97587
Now I see a bit different technique involved in combine
operator (it's essentially the same as map):
frp-ts/packages/core/src/property.ts
Lines 104 to 110 in 6c2dac9
so now combine
would just skip propagating event so observers will not be notified in case of duped value.
Earlier I was thinking that deduplication/filtering is in full responsibility of value producer and should happen right before setting it to atom (except for builtin triple eq checking in atom.set), but now I'm not sure that this approach was right and it's looks like it's totally ok to pull-down circuit breaker somewhere along the way of evaluation chain.
@raveclassic @Fyzu can you share your thoughts please?
Hi @raveclassic!
There is a problem with usePropertyFromProps
- the atom changes after the render and at the moment of the react props change prop !== prop$.get()
:
const Component = ({ prop }) => {
const prop$ = usePropertyFromProps(prop)
prop$.get() === prop // false at the moment of the props change
}
Isn't it better to use the following implementation:
function usePropertyFromProps<Value>(value: Value): Property<Value> {
const atomRef = useRef(newAtom(value))
atomRef.current.set(value)
return atomRef.current
}
Hey @raveclassic!
I've noticed that there possibility of data loss since default clock is always incrementing inner value either it's overflows or not:
Line 10 in 8c97587
While it's pretty uncommon to have such a large amount of updates it is still preferable to have handling logic of big numbers.
Frp almost completely compatible with svelte, except for a moment with a subscription.
Svelte uses simple store contract.
type Store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void }
It seems to me that you can make a separate method for subscribing for a Time
like .watch(time => {})
for internal usage, and adopt subscribe
to .subscribe(value => {})
.
This way we will get compatibility with svelte and it will be more understandable for users who want to work with frp as a reactive library.
Hey, @raveclassic!
I'm developing a chat. It means that I need to subscribe to properties in leaves (messages, etc.) so only necessary parts re-render.
I was testing performance and noticed that old version of useProperty
lost updates (new works fine). This led me to some research. Results are below.
Look like there are three versions of "how to subscribe to external mutable source" in React:
createSubscription
+ useSubscription
useMutableSource
(was renamed to the below)useSyncExternalStore
(package)It turns out that managing frp-ts
like source is not that easy in React because you have to take into account SSR, Suspense and async rendering (coming in React 18).
My suggestion is to use useSyncExternalStore
in frp-ts/react
package so the React team handles all the details (I'll attach reading list at the end for a better context).
I know that this is an anti-pattern and it should not be done (react repo issue). But, now I wonder whether this still can happen in large codebases (sockets, event handlers, whatever).
React team introduces StrictEffects
and this should catch the above. Until that lands, I wonder whether deferring state.set
calls as done in most eliminates the problem completely.
I still need to wrap my head around this. Maybe this case will never happen in a real application. Maybe the only way to trigger this is to directly modify the state in a render function.
Two demos showcasing this:
useSES
:useProperty
hookI tested my component with the hook below and everything seems to work fine.
const bridge = <A,>(
p: Property<A>,
): {
subscribe: (onStoreChange: () => void) => () => void;
getSnapshot: () => A;
} => ({
getSnapshot: () => p.get(),
subscribe: (onChange) => {
const sub = p.subscribe({
next: () => onChange(),
});
return () => sub.unsubscribe();
},
});
const useProperty = <A,>(p: Property<A>): A => {
const { getSnapshot, subscribe } = useMemo(() => bridge(p), [p]);
return useSyncExternalStore(subscribe, getSnapshot);
};
useSyncExternalStore
is the way to go for frp-ts
React integration.frp-ts
).Hi @raveclassic. Thanks for your lib.
I have a next example of usage of your library and wanna propose an operator for this.
const sortedElements = property.combine(data, elements, sortElements);
I have an array of some elements that should be sorted based on data. So when the data is updates i recalculate a new sortedElements, and it may have same order but different reference. So i need an ability to have a custom comparator function to omit some of the Property changes.
Looks like a distinctUntilChanged operator from rxjs can be a good fit for this, but with mandatory comparator, because frp-ts not emit when values has same reference.
I make an implementation of this function as proposal for example #58. So maybe it will be helpful for other devs too.
Can you take a look?
@raveclassic, hi!
Would you accept the below feature as a separate frp-ts package\addition to the core package?
// API
type StateActions<S> = Record<string, (this: S, ...args: any[]) => undefined | void>;
type OmitThis<AS extends StateActions<any>> = {
[K in keyof AS]: AS[K] extends (this: any, ...args: infer U) => void | undefined ? (...args: U) => void : never;
};
export const newState = <S, AS extends StateActions<S>>(initial: S, actions: AS): Property<S> & OmitThis<AS> => {
throw Error('impl is skipped');
};
// USAGE
interface Dog {
name: string;
}
interface House {
dog: Dog;
}
interface AppState {
house: House;
}
const initialState: AppState = { house: { dog: { name: 'Fido' } } };
const store = newState(initialState, {
renameTheDog(newName: string) {
// this is typed correctly : AppState
this.house.dog.name = newName;
},
});
// typed correctly, this is omitted
store.renameTheDog('Odif');
Internally it will use immer to support concise mutation syntax.
I find it convenient to group store\vm state into a single object and expose a single atom in the API. This leads to some boilerplate when you need to modify parts of the state.
Immer helps a lot, but we still need to use modify calls. This change simplifies this use case.
Besides, some libraries provide this out of the box (SolidJS as example).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.