grammarly / focal Goto Github PK
View Code? Open in Web Editor NEWProgram user interfaces the FRP way.
License: Apache License 2.0
Program user interfaces the FRP way.
License: Apache License 2.0
This is a question, I am sure I saw some info on how to do this (maybe Reactive2017 conf?).
How can I use third-party React components with Focal? For example https://github.com/MadRabbit/a-plus-forms.
This works, but is there another way?
<F.div>
You have clicked this button {count} time(s).
<Form onSubmit={...}>
<TextInput name="username" label={<F.span>{count}</F.span>} />
</Form>
</F.div>
This fails (as excpected I guess):
<F.div>
You have clicked this button {count} time(s).
<Form onSubmit={...}>
<TextInput name="username" label={count} />
</Form>
</F.div>
This shows the first count value, but does not update after first render.
<F.div>
You have clicked this button {count} time(s).
<Form onSubmit={...}>
<TextInput name="username" label={count.get()} />
</Form>
</F.div>
Do you have any examples for syncing local UI state and server state?
I see some examples for fetching data from server (github search for example)
But there is no example for crud operations.
I can see some possibilities here:
What do you think about these possiblities? What's better from your point of view? Or maybe you have some other experience?
It would be great if you add some examples for novices
Would it make sense to have a react hook, for example useAtom
that would subscribe to the atom, and return the latest value?
For example, you'd use it something like this:
import * as React from 'react'
import { Atom } from '@grammarly/focal'
const store = {
count: Atom.create(0)
}
const Counter = () => {
const count = useAtom(store.count)
return (
<div>Count: {count}</div>
)
}
When a Lens created through a Lens.index(...)
prism with default value V it sets associated array element into undefined in being set with that value V.
E.g.:
const states = Atom.create([]) // Array of strings
states.set(['hidden', 'hidden'])
const lens = states.lens(Lens.index(0).compose(Lens.withDefault('hidden')))
// ...
lens.set('visible') // works just fine
lens.set('hidden') // sets states[0] to `undefined`
Two points here:
lens.set()
states.lens(Lens.index(0))
is not very convenient IMO"dependencies": {
"@grammarly/focal": "^0.7.0",
"preact": "^8.3.1",
"ramda": "^0.25.0",
"recompose": "^0.30.0",
"rxjs": "^6.3.2",
"undux": "^3.2.1"
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.1.0",
"@types/chrome": "~0.0.72",
"@types/jest": "^23.3.2",
"@types/node": "^10.11.3",
"@types/ramda": "^0.25.38",
"@types/rx": "^4.1.1",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^4.5.2",
"css-loader": "^1.0.0",
"jest": "^23.6.0",
"preact-compat": "^3.18.4",
"style-loader": "^0.23.0",
"ts-jest": "23.1.4",
"ts-loader": "~5.0.0",
"typescript": "^3.1.1",
"webpack": "~4.17.2",
"webpack-archive-plugin": "^3.0.0",
"webpack-cli": "~3.1.0",
"webpack-merge": "~4.1.4",
"webpack-zip-files-plugin": "^1.0.0"
}
When you quickly update an atom with a previous value right after updating with a new one - the last change is ignored
The code sample can be found here
Hi, I find focal very interesting, since it tries to use both imperative and functional approaches.
I played around and wanted to implement a pattern, where a click merely proposes new state value. Then a central function decides if the state has to be modified. It seems to work surprisingly, if I directly mutate the state in a state-subscription.
Do you think this is very wrong and that it is a "loophole" in the API that might not work correctly? I just tried the example from your Readme, where I added a Toggle component.
(Btw. I do not use Typescript)
import * as React from "react";
import { Atom, F } from "@grammarly/focal";
export default ({ shim }) => {
const Counter = ({ count, onClick }) => (
<F.div>
You have clicked this button {count} time(s).
<button onClick={onClick}>Click again?</button>
</F.div>
);
const Toggle = ({ value, onClick }) => (
<F.div>
clickValue: {value}
<button onClick={onClick}>toggle</button>
</F.div>
);
const App = ({ state }) => (
<div>
Hello, world!
<Counter
count={state.view(x => x.count)}
onClick={() => state.lens(x => x.count).modify(x => x + 1)}
/>
<Toggle
value={state.view(x => (x.toggle.value ? "Foo" : "Bar"))}
onClick={() =>
state.lens(x => x.proposal).modify(x => ({ toggle: !x.toggle }))}
/>
</div>
);
const state = Atom.create({
count: 0,
toggle: { value: true },
proposal: {}
});
state.subscribe(x => {
console.log(`New app state: ${JSON.stringify(x)}`);
});
state
.skip(1)
.distinct(x => x.proposal)
.do(x => {
console.log("Proposal changed", x.proposal);
if (x.proposal.toggle !== undefined) {
x.toggle.value = !x.toggle.value; // This seems to actually work.
}
})
.subscribe();
return <App state={state} />;
};
Hello!
yarn audit tells me this repo has some malware:
https://www.npmjs.com/advisories/1079192
https://www.npmjs.com/advisories/1079200
I love the concept of Atom
and wish I could use it on non-React projects. Unfortunately, using this library forces me to pull in all of React to compile.
Hi!
I'm seriously considering using Focal, but couldn't find docs to answer two of our higher-priorities:
<F.div>
component looks like a pretty serious investment of time for your team to have produced. I couldn't find an analog for React native in your repo anywhere and assume you aren't currently using Focal with React Native. Am I mistaken? If not, have you any plans to create an observer <F.view>
or the like?Thank you for the documentation! It's very concise! I'm very excited to see movement in this area!
After unsubscribing from ReadonlyAtom
(created by .view) it still calling function inside, fails and unsubscribing.
In other words - if we unsubscribe from ReadonlyAtom - it don't unsubscribe from inner subscription.
const state = Atom.create([{ id: 1 }])
const view = state.view(state => state[0].id) //if we replace view with map - it works
const sub = view.subscribe(s => {
console.log(s)
})
state.modify(s => {
sub.unsubscribe()
return []
})
This causing a problem with reactiveList
.
{reactiveList(
state.map(s => s.map((_, ind) => ind)),
ind => {
return <El el={state.view(s => s[ind].id)} /> //view is already unsubscribed from this observable, but `.view` is still calling.
}
)}
I guess problem is here https://github.com/grammarly/focal/blob/master/src/atom/base.ts#L378
Trying to use focal
with Typescript 3.4.3, I get
node_modules/@grammarly/focal/dist/src/react/react.d.ts:103:5 - error TS2411: Property 'mount' of type 'undefined' is not assignable to string index type '((e: SyntheticEvent<any, Event>) => void) | ((domElement: Element | null) => void)'.
103 mount?: undefined;
~~~~~
and a similar error for ref
. I'm able to get rid of the error if I turn off strictNullChecks
in my tsconfig.json
, but I don't want to do that :(.
Also I'm confused because the tsconfig.json
s for focal
has strictNullChecks
turned on; in fact, I get the same error when I try to build the examples following the instructions.
Another way I found to fix it (without turning off strictNullChecks
) is to remove the mount?: undefined
and ref?: undefined
lines from the type of bindElementProps
in react.d.ts
. I don't understand where those lines come from, given the code of bindElementProps
; seems like a possible Typescript bug?
The current instanceof Observable
check that is used in the implementation of lifted component relies on the class instance check, which in turn requires that the instance be checked against the same constructor it was created with.
However it is possible that user-created Observables come from a different version or just a different instance of the rxjs
package. In that case the instanceof Observable
check will always fail.
It seems that the issue of several rxjs
packages in a bundle should not be affecting Focal, or if it does there should be a quick way to diagnose the issue.
One solution that I see is the adoption of symbol-observable
check like it's done in Redux, etc.
Hello!
I use Create React App. On dev environment everything goes well, but the production build goes broken.
base.ts:292 Uncaught TypeError: f.a.key is not a function
at t.lens (base.ts:292)
at Module.71 (state-app.ts:26)
at l ((index):2)
at Object.66 (main.c2bc3c36.chunk.js:1)
at l ((index):2)
at a ((index):2)
at Array.e [as push] ((index):2)
at main.c2bc3c36.chunk.js:1
Here is base.ts
source map:
AbstractAtom.prototype.lens = function (arg1) {
// tslint:disable no-use-before-declare
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
// lens(prop expr) case
return typeof arg1 === 'function'
? new LensedAtom(this, Lens.prop(arg1), structEq)
// lens('key') case
: typeof arg1 === 'string'
? new LensedAtom(this, Lens.compose.apply(Lens, [Lens.key(arg1)].concat(args.map(function (k) { return Lens.key(k); }))), structEq)
// lens(lens) case
: new LensedAtom(this, arg1, structEq);
// tslint:enable no-use-before-declare
};
return AbstractAtom;
}(AbstractReadOnlyAtom));
Problem happens in this string:
? new LensedAtom(this, Lens.compose.apply(Lens, [Lens.key(arg1)].concat(args.map(function (k) { return Lens.key(k); }))), structEq)
More precisely this sub-string: Lens.compose.apply(Lens, [Lens.key(arg1)]
Where is Lens.key is not a function.
I've tried to turn off mangle in TerserPlugin which minify the code and it had no results.
Now I'm thinking about the sequence of function calls, mb it works differently in dev and prod env.
I hope you have some ideas what should I try next to make it works.
You can explore the source-code here: https://github.com/sukazavr/dego.app
And of course you can build it on your local machine and test it out by yourself.
Thank you!
Currently, there is an inconsistency between the single vs. multiple Observable props warning policies.
Namely, Focal will warn about empty observable in props only when there's more than one observable passed. The example is available here (thanks @oleksiilevzhynskyi).
It looks like it's because RenderMany
class reports the warning right after walking the observables, while RenderOne
will warn the user only after Observable's completion, which may not happen at all.
Is there any specific reason for waiting for the completion in case of a single prop? It feels a bit misleading that the same issue is treated differently.
Is there going to be any support for React 17?
Due to the way setKey
is written, the original prototype is forgotten. This means all of the functions disappear as well.
focal/packages/focal/src/utils.ts
Lines 7 to 19 in 281b70f
I was just curious how you guys implement persisting data to a backend. Being able to update a global data store locally is great, but how do you persist these changes to storage? Do you track changes and send the appropriate requests? Do you use some kind of operational transformation protocol?
Thanks!
Just stopping through reading your tutorial.
Lines 68 to 72 in 271e37d
type PropPath = (string | number | symbol)[]
function extractPropertyPath<Arg, Ret>(getter: PropExpr<Arg, Ret>): PropPath {
const errorMessage = `Expected a property expression, got "${getter}"...`
const props: PropPath = []
getter(new Proxy(Object.create(null), {
get(_, prop, proxy) {
if (prop === Symbol.toPrimitive) {
throw TypeError(errorMessage)
}
props.push(prop)
return proxy
},
}) as Arg)
return props
}
I found this codesandbox that was shared by a Grammarly in an engineering post
In line 89 and 90, there is a Typescript compilation error
const alerts = props.state.lens("alerts");
const highlights = props.state.lens("highlights");
Property 'lens' does not exist on type 'ReadOnlyAtom<State>'
The code still works but I was intrigued by that knowing that method lens
doesn't seem to appear in ReadOnlyAtom
as reference here
Can someone explain what I could be missing here?
Should the object be Atom
instead of ReadOnlyAtom
?
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.