profile
viewpoint

jcarver989/dependence.js 9

File dependency for client side javascript

floodfx/chrome-sdb 5

simpledb plugin for chrome

jcarver989/html5-talk 4

upcoming talk

jcarver989/Bizo-API-Gem 3

A Simple Ruby wrapper around the Bizo API

jcarver989/blog-scoreboard 3

Visual Scoreboard for your Blogger Blog

jcarver989/foundation.sass 3

Useful mixins for the sass framework

jcarver989/granite 3

A Typesafe React/Redux like framework for Scala.js

jcarver989/anagram 2

Annotation tool for design reviews

jcarver989/codify 2

jQuery Plugin: automatically show source code for widgets in your documentation

push eventginger-io/beyonce

Josh Carver

commit sha 23b1f9408d5a25d43533fbea6f8cb38f833fdbe8

Configure Jest to work with DynamoDB on CI

view details

push time in 14 days

push eventginger-io/beyonce

Josh Carver

commit sha 8a12cfe94c2143b76d4f2c633aa28a00cd104a5f

fix typo in build step

view details

push time in 14 days

push eventginger-io/beyonce

Josh Carver

commit sha fffa62001bccc6809be1b567746228d71dc4d753

Remove erroneous build step

view details

push time in 14 days

push eventginger-io/beyonce

Josh Carver

commit sha 6eebd95e467ea74865cc346be65e9c550849dcc3

Remove line in README re: unsupported feature that is now supported

view details

push time in 14 days

push eventginger-io/beyonce

Josh Carver

commit sha 2bac928fec09368f3980892d6935ad6d7791d0db

Setup CI

view details

push time in 14 days

delete branch ginger-io/beyonce

delete branch : feature/asCreateTableInput

delete time in 16 days

push eventginger-io/beyonce

Joshua Carver

commit sha 79135c11fe58dd7122c9b3b0d6e79b29a045ccb6

[Beyonce]: Add helper method to Table class to make creating the table easy (#38) * Add method asCreateTableInput to Table

view details

push time in 16 days

PR merged ginger-io/beyonce

Reviewers
[Beyonce]: Add helper method to Table class to make creating the table easy

This PR adds a asCreateTableInput method to our Table class, which can be passed directly to a DynamoDB client to create that table. We might end up needed a separate method for CDK, but that should be pretty easy to add too.

+65 -50

0 comment

4 changed files

jcarver989

pr closed time in 16 days

push eventginger-io/beyonce

Joshua Carver

commit sha 6b3714a7655ea9b9c877bd68fd5c5213cac15e58

[Beyonce]: Support inverted indexes (#37) * Ensure we actually export generated GSI * Support creating an inverted index by allowing pk/sk fields to be specified when creating index

view details

Joshua Carver

commit sha 4d3a02a5f4f8e4ecd4b005990066f8b5fe2666a4

Merge branch 'master' into feature/asCreateTableInput

view details

push time in 16 days

delete branch ginger-io/beyonce

delete branch : feature/support-inverted-index

delete time in 16 days

push eventginger-io/beyonce

Joshua Carver

commit sha 6b3714a7655ea9b9c877bd68fd5c5213cac15e58

[Beyonce]: Support inverted indexes (#37) * Ensure we actually export generated GSI * Support creating an inverted index by allowing pk/sk fields to be specified when creating index

view details

push time in 16 days

PR merged ginger-io/beyonce

Reviewers
[Beyonce]: Support inverted indexes

This PR adds support for creating an "inverted index" -- which is a particular flavor of a GSI.

You can click the link above for more context, but the basic idea is it's often useful to flip your primary table's partition and sort keys in a GSI. Often this "unlocks" several new query patterns -- e.g. querying the "other side" of a relationship.

Previously, when defining a GSI we wouldn't allow you to use "key fields" on a model (i.e. a field that is synthesized when you call FooModel.create({ ... })). Now we allow you to specify your table's partition key or sort key.

A nice future improvement that's out of scope for this PR would be to split model definitions into "data fields" and "key fields". This would allow for defining arbitrary fields on your model that would be automatically synthesized when you call .create({ ... }). You can think of "partitionKey" and "sortKey" as being special cases of that idea -- e.g.:

        Item:
          keys:
            partitionKey: [Item, $id]
            sortKey: [Item, $id]
            index1PartitionKey: [User, $creatorId]
            index1SortKey: [Item, $createdAt, $id]
          fields:
            id: string
            creatorId: string
            createdAt: string // ISO date string
            jsonData: string
GSIs:
  index1:
    partitionKey: $index1PartitionKey
    sortKey: $index1SortKey
const item = Item.create({ 
  id: "...", 
  creatorId: "...", 
  createdAt: "...", 
  jsonData: "..." 
})  // all the keys get auto-appended

+92 -40

0 comment

11 changed files

jcarver989

pr closed time in 16 days

Pull request review commentginger-io/beyonce

[Beyonce]: Add helper method to Table class to make creating the table easy

 export class Table<PK extends string = string, SK extends string = string> {     return new GSIBuilder(this, name)   } -  addToEncryptionBlacklist(field: string) {-    this.encryptionBlacklist.add(field)+  registerGSI(gsi: GSI<string, string, any>) {+    this.gsis.push(gsi)+    this.encryptionBlacklist.add(gsi.partitionKeyName)+    this.encryptionBlacklist.add(gsi.sortKeyName)   }    getEncryptionBlacklist(): Set<string> {     return this.encryptionBlacklist   }++  asCreateTableInput(+    billingMode: DynamoDB.Types.BillingMode+  ): DynamoDB.Types.CreateTableInput {+    const attributeSet = new Set([+      this.partitionKeyName,+      this.sortKeyName,+      "model",+      ...this.gsis.flatMap((_) => [_.partitionKeyName, _.sortKeyName]),+    ])++    const attributeDefinitions: DynamoDB.Types.AttributeDefinitions = Array.from(+      attributeSet+    ).map((attr) => ({+      AttributeName: attr,+      AttributeType: "S",+    }))++    return {+      TableName: this.tableName,++      KeySchema: [+        { AttributeName: this.partitionKeyName, KeyType: "HASH" },+        { AttributeName: this.sortKeyName, KeyType: "RANGE" },+      ],++      AttributeDefinitions: attributeDefinitions,+      GlobalSecondaryIndexes: this.gsis.map(+        ({ name, partitionKeyName, sortKeyName }) => ({+          IndexName: name,+          KeySchema: [+            { AttributeName: partitionKeyName, KeyType: "HASH" },+            { AttributeName: sortKeyName, KeyType: "RANGE" },+          ],+          Projection: {+            ProjectionType: "ALL",

Similar to the above, Beyonce assumes all indexes project all attributes. That makes the typing work out easier (i.e. you don't have to express partial model types). But it'd be nice to support that in the future.

jcarver989

comment created time in 20 days

Pull request review commentginger-io/beyonce

[Beyonce]: Add helper method to Table class to make creating the table easy

 export class Table<PK extends string = string, SK extends string = string> {     return new GSIBuilder(this, name)   } -  addToEncryptionBlacklist(field: string) {-    this.encryptionBlacklist.add(field)+  registerGSI(gsi: GSI<string, string, any>) {+    this.gsis.push(gsi)+    this.encryptionBlacklist.add(gsi.partitionKeyName)+    this.encryptionBlacklist.add(gsi.sortKeyName)   }    getEncryptionBlacklist(): Set<string> {     return this.encryptionBlacklist   }++  asCreateTableInput(+    billingMode: DynamoDB.Types.BillingMode+  ): DynamoDB.Types.CreateTableInput {+    const attributeSet = new Set([+      this.partitionKeyName,+      this.sortKeyName,+      "model",+      ...this.gsis.flatMap((_) => [_.partitionKeyName, _.sortKeyName]),+    ])++    const attributeDefinitions: DynamoDB.Types.AttributeDefinitions = Array.from(+      attributeSet+    ).map((attr) => ({+      AttributeName: attr,+      AttributeType: "S",

There's an existing assumption in Beyonce that all key/indexed attributes are strings. Would be nice to make it smarter there eventually.

jcarver989

comment created time in 20 days

PR opened ginger-io/beyonce

Reviewers
[Beyonce]: Add helper method to Table class to make creating the table easy

This PR adds a asCreateTableInput method to our Table class, which can be passed directly to a DynamoDB client to create that table. We might end up needed a separate method for CDK, but that should be pretty easy to add too.

+65 -50

0 comment

4 changed files

pr created time in 20 days

create barnchginger-io/beyonce

branch : feature/asCreateTableInput

created branch time in 20 days

push eventginger-io/beyonce

Josh Carver

commit sha 4e9da21b496e6bb59f912ed784bb13bf7dbfefa9

fix test name

view details

push time in 20 days

push eventginger-io/beyonce

Josh Carver

commit sha 52c693f29a7dcb51fd3fedd0169300f0aaf7fbdf

remove uneeded type annotations

view details

push time in 20 days

PR opened ginger-io/beyonce

Reviewers
[Beyonce]: Support inverted indexes

This PR adds support for creating an "inverted index" -- which is a particular flavor of a GSI.

You can click the link above for more context, but the basic idea is it's often useful to flip your primary table's partition and sort keys in a GSI. Often this "unlocks" several new query patterns -- e.g. querying the "other side" of a relationship.

Previously, when defining a GSI we wouldn't allow you to use "key fields" on a model (i.e. a field that is synthesized when you call FooModel.create({ ... })). Now we allow you to specify your table's partition key or sort key.

A nice future improvement that's out of scope for this PR would be to split model definitions into "data fields" and "key fields". This would allow for defining arbitrary fields on your model that would be automatically synthesized when you call .create({ ... }). You can think of "partitionKey" and "sortKey" as being special cases of that idea -- e.g.:

        Item:
          keys:
            partitionKey: [Item, $id]
            sortKey: [Item, $id]
            index1PartitionKey: [User, $creatorId]
            index1SortKey: [Item, $createdAt, $id]
          fields:
            id: string
            creatorId: string
            createdAt: string // ISO date string
            jsonData: string
+95 -43

0 comment

12 changed files

pr created time in 20 days

push eventginger-io/beyonce

Josh Carver

commit sha 2b6b54b4ef9f12baf64e6952a73482e2ef99960a

Support creating an inverted index by allowing pk/sk fields to be specified when creating index

view details

Josh Carver

commit sha cccb72f405eee57364ee58c9bafd01db74a82517

bump package version

view details

push time in 20 days

push eventginger-io/beyonce

Josh Carver

commit sha 4a1ac44d6bde9186dcffe5e7ff26aab33358e1f0

Support creating an inverted index by allowing pk/sk fields to be specified when creating index

view details

Josh Carver

commit sha 4e9401cd6c5c8f03b21889e7aee3de6eed7082c2

bump package version

view details

push time in 20 days

create barnchginger-io/beyonce

branch : feature/support-inverted-index

created branch time in 20 days

push eventginger-io/beyonce

Joshua Carver

commit sha 84c46ce82d9d014b95922179a5b5a3c6089bf445

[Beyonce]: Allow Compound Partition/Sort Keys (#34) * Add support for compound partition or sort keys Co-authored-by: Cameron Lopez <cameronjlopez@gmail.com>

view details

push time in 23 days

delete branch ginger-io/beyonce

delete branch : feature/compound-keys

delete time in 23 days

PR merged ginger-io/beyonce

[Beyonce]: Allow Compound Partition/Sort Keys

@cjoelrun started this work during our last hackday in: https://github.com/ginger-io/beyonce/pull/33. This PR splits off an incremental chunk of that work so we can merge a useful feature in.

Previously, Beyonce would force you to structure your partition / sort keys as a tuple-2 composed of [prefix, modelFieldName]. But there are many use-cases in Dynamo where you'd want to have a "compound" key -- i.e. a key formed from not 1, but multiple fields on the model.

As an example, we might have:

  1. A model for SalesRep, where our pk is ["SalesRep", "$id"].
  2. An Sale model that has the same partition key as the rep it's attached to and a sort key that's like [Sale, "$country", "$state", "$city"].

The compound key in 2) would allow us to query for all sales by a rep in a given country, state or city (by using the begins_with key condition in our query operations).

Another use-case: it's common to "invert" a table's keys using a Global Secondary Index (i.e. flip the pk and sk). Using a compound key in the sk is often useful there to ensure that when you swap the keys, a ton of data doesn't wind up in a single partition. As a concrete example, we might have:

  1. An Item model with pk: ["Item", "$id"] and sk: ["Item", "$id", "$version"] (<-- note the compound key here)
  2. A Tag model with pk: ["Item", "$itemId"] and sk: ["Tag", "$name"]

So now, we can .query(...) with an item's partition key to get it + its tags. But we'd need to swap the keys in a GSI to query the other side of the relationship -- i.e. "here's a tag, give me all the items with that tag".

If we didn't use the compound key in 1) and just used : ["Item", "$version"] -- when we flipped the pk / sk in our GSI, we'd end up with massive partitions. All items with the same version number would wind up in the same partition -- which would be ... not good :).

+182 -42

5 comments

9 changed files

jcarver989

pr closed time in 23 days

pull request commentginger-io/beyonce

[Beyonce]: Allow Compound Partition/Sort Keys

@cjoelrun -- thanks for the test. You're right in that it was failing in this PR. Fixed it now (had missed telling our blacklist to look for compound keys as well).

jcarver989

comment created time in 23 days

push eventginger-io/beyonce

Josh Carver

commit sha b3ade3b9a531c23e7d87d0239ad9305286423210

Fix a bug where our blacklist for encrypted fields didnt add compound keys correctly

view details

push time in 23 days

pull request commentginger-io/beyonce

Add test for compound key model generation

Thanks, going to merge this in and take a look.

cjoelrun

comment created time in 23 days

push eventginger-io/beyonce

Cameron Lopez

commit sha 9698617491a417909a1e9906e371996a5df93ef1

add test to for model generation (#36)

view details

push time in 23 days

PR merged ginger-io/beyonce

Add test for compound key model generation

I think there may be an issue with the encryptionBlacklist not grabbing all the keys in the compound key. Adding this test from my other PR.

+46 -0

1 comment

1 changed file

cjoelrun

pr closed time in 23 days

pull request commentginger-io/beyonce

[Beyonce]: Allow Compound Partition/Sort Keys

#36 Added test in this PR to test possible issue in model generation.

Great, thanks will take a look.

jcarver989

comment created time in 23 days

pull request commentginger-io/beyonce

[Beyonce]: Allow Compound Partition/Sort Keys

@cjoelrun :

Do I understand correctly that this allows for creating and querying a compound key, but not for querying the partial of a compound key?

That's right. This PR just adds support for compound keys. But doesn't include support for formulating a partial key.

jcarver989

comment created time in 23 days

PR opened ginger-io/beyonce

[Beyonce]: Allow Compound Partition/Sort Keys

@cjoelrun started this work during our last hackday in: https://github.com/ginger-io/beyonce/pull/33. This PR splits off an incremental chunk of that work so we can merge a useful feature in.

Previously, Beyonce would force you to structure your partition / sort keys as a tuple-2 composed of [prefix, modelFieldName]. But there are many use-cases in Dynamo where you'd want to have a "compound" key -- i.e. a key formed from not 1, but multiple fields on the model.

As an example, we might have:

  1. A model for SalesRep, where our pk is ["SalesRep", "$id"].
  2. An Sale model that has the same partition key as the rep it's attached to and a sort key that's like [Sale, "$country", "$state", "$city"].

The compound key in "#2" would allow us to query for all sales by a rep in a given country, state or city (by using the begins_with key condition in our query operations).

Another use-case: it's common to "invert" a table's keys using a Global Secondary Index (i.e. flip the pk and sk). Using a compound key in the sk is often useful there to ensure that when you swap the keys, a ton of data doesn't wind up in a single partition. As a concrete example, we might have:

  1. An Item model with pk: ["Item", "$id"] and sk: ["Item", "$id", "$version"] (<-- note the compound key here)
  2. A Tag model with pk: ["Item", "$itemId"] and sk: ["Tag", "$name"]

If we didn't use the compound key in "#1" and just used "$version" -- when we flipped the pk / sk in our GSI, we'd end up with massive partitions as we'd stick all items with the same version number into the same partition -- which would be ... not good :).

+120 -33

0 comment

7 changed files

pr created time in a month

push eventginger-io/beyonce

Josh Carver

commit sha 584d55864a22fc408c36e92063dbb264e31ac9da

Remove unused model type

view details

push time in a month

push eventginger-io/beyonce

Josh Carver

commit sha cd83ccbb20f5f818cff91498efdb7b8ce3d90adf

tick package version

view details

push time in a month

create barnchginger-io/beyonce

branch : feature/compound-keys

created branch time in a month

Pull request review commentginger-io/react-use-form

Add ability to reset fields

 describe('useForm', () => {      expect(result.current.validate()).toEqual(false)   })++  it('should reset field value to default', async () => {+    const { result } = render<Widget>({+      name: field(),+      details: {+        description: field(),+        picture: field()+      },+      components: field()+    })++    // update field value

We can drop comments like these across the tests since they aren't really telling us anything the code isn't.

teeeteee

comment created time in a month

Pull request review commentginger-io/react-use-form

Add ability to reset fields

 function runValidation<T>(   return isValid } +function resetForm<T>(+  initialState: FieldsState<T>,+  setState: (f: (state: FieldsState<T>) => FieldsState<T>) => void+) {

Need a return type annotation here.

teeeteee

comment created time in a month

Pull request review commentginger-io/react-use-form

Add ability to reset fields

 export function useForm<T extends Record<string, any>>(     defaultValue   ])   const [state, setState] = useState<FieldsState<T>>(initialState)-  const fields = useMemo(() => createFields(state, setState), [state])+  const fields = useMemo(() => createFields(state, initialState, setState), [+    state+  ])   const validate = useCallback(() => runValidation(setState), [setState])+  const reset = useCallback(() => resetForm(initialState, setState), [setState])

The dependency array here should include initialState

teeeteee

comment created time in a month

delete branch ginger-io/react-use-form

delete branch : feature/top-level-defaults

delete time in a month

push eventginger-io/react-use-form

Joshua Carver

commit sha 2599f0e4184c27b24a1fc9be8b622e924eb207ec

[React Hook Form]: Allow passing a fully-formed T as a top-level default. (#5) * Allow callers to pass a top-level default

view details

push time in a month

PR merged ginger-io/react-use-form

[React Hook Form]: Allow passing a fully-formed T as a top-level default.

This PR adds a feature to allow callers to pass a fully-formed domain object to use as a "default" for your form state. The intended use-case here is for "edit" or "read-only" forms, where you want to pre-populate all the fields.

+135 -18

0 comment

5 changed files

jcarver989

pr closed time in a month

PR opened ginger-io/react-use-form

[React Hook Form]: Allow passing a fully-formed T as a top-level default.

This PR adds a feature to allow callers to pass a fully-formed domain object to use as a "default" for your form state. The intended use-case here is for "edit" or "read-only" forms, where you want to pre-populate all the fields.

+135 -18

0 comment

5 changed files

pr created time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha 1f85117954b42a24c9f310c0a188fd0245c2f17b

Fix hook warning

view details

push time in a month

create barnchginger-io/react-use-form

branch : feature/top-level-defaults

created branch time in a month

delete branch ginger-io/react-use-form

delete branch : bugfix/stale-state-in-validate

delete time in a month

push eventginger-io/react-use-form

Joshua Carver

commit sha dcc883abc33a50a97c76ba6f137d7747408e225e

Fix a bug where validate() would continue to return true if the form was valid, but became invalid on the next render cycle (#4)

view details

push time in a month

PR merged ginger-io/react-use-form

[Bugfix]: Don't allow validate()'s reference state to become invalid.

This PR fixes a bug where validate() might return true if the form was originally valid, but later became invalid. The root cause of the stale state is the same as in: https://github.com/ginger-io/react-use-form/pull/2. I just missed updating the validate method to match oops :).

+175 -21

0 comment

5 changed files

jcarver989

pr closed time in a month

Pull request review commentginger-io/react-use-form

[Bugfix]: Don't allow validate()'s reference state to become invalid.

 export type ValidationRule<T> = (value: T) => string | undefined -export const required: ValidationRule<any> = _ =>-  _ === undefined || (_ === null && _ === '')+export const required: ValidationRule<any> = _ => {

This was obviously wrong before :)

jcarver989

comment created time in a month

PR opened ginger-io/react-use-form

[Bugfix]: Don't allow validate()'s reference state to become invalid.

This PR fixes a bug where validate() might return true if the form was originally valid, but later became invalid. The root cause of the stale state is the same as in: https://github.com/ginger-io/react-use-form/pull/2. I just missed updating the validate method to match oops :).

+175 -21

0 comment

5 changed files

pr created time in a month

create barnchginger-io/react-use-form

branch : bugfix/stale-state-in-validate

created branch time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha d5d14a57be864b7d130b0d361c30edfcdeb190e8

fix typo in package.json version

view details

push time in a month

delete branch ginger-io/react-use-form

delete branch : enhancement/short-circuit-is-empty

delete time in a month

push eventginger-io/react-use-form

Joshua Carver

commit sha e6524bc885da4fda8bc12ccccac225a13559d417

Bail out early if we find any populated value when calculating isEmpty (#3)

view details

push time in a month

PR merged ginger-io/react-use-form

Reviewers
[React useForm]: Short-circuit isEmpty

This PR makes a small optimization resulting from a comment thread in this PR: https://github.com/ginger-io/react-use-form/pull/1#discussion_r450504289

Basically @aboisvert caught me being lazy :). So, here we become less lazy by bailing out early when calculating our isEmpty value.

+36 -15

1 comment

2 changed files

jcarver989

pr closed time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: Short-circuit isEmpty

 function forEach<T>(   } } -function isEmptyArray(obj: any): boolean {-  return Array.isArray(obj) && obj.length === 0+/** Recursively visits each property in object+ *  and returns true as soon as any field matches the passed+ *  predicate, false otherwise.+ */+function exists<T>(

Also yes yes this would technically be faster as a loop with a queue/stack (pick your poison BFS vs DFS) vs recursion -- but I'm pretty sure it's never going to matter ;).

jcarver989

comment created time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: Short-circuit isEmpty

 function forEach<T>(   } } -function isEmptyArray(obj: any): boolean {-  return Array.isArray(obj) && obj.length === 0+/** Recursively visits each property in object+ *  and returns true as soon as any field matches the passed+ *  predicate, false otherwise.+ */+function exists<T>(

So, previously we'd visit every property on our form's state (recursively) and only return at the end. Now we bail out early as soon as we find a property that matches our predicate (e.g. !isEmptyObject)

jcarver989

comment created time in a month

PR opened ginger-io/react-use-form

Reviewers
[React useForm]: Short-circuit isEmpty

This PR makes a small optimization resulting from a comment thread in this PR: https://github.com/ginger-io/react-use-form/pull/1#discussion_r450504289

Basically @aboisvert caught me being lazy :). So, here we become less lazy by bailing out early when calculating our isEmpty value.

+36 -15

0 comment

2 changed files

pr created time in a month

create barnchginger-io/react-use-form

branch : enhancement/short-circuit-is-empty

created branch time in a month

delete branch ginger-io/react-use-form

delete branch : bugfix/multiple-updates-in-same-render-cycle

delete time in a month

push eventginger-io/react-use-form

Joshua Carver

commit sha 8e918f1d1d0db9c5fc9515c68dfe60bcfd8d7c0e

Handle multiple onChange events in the same render cycle (#2)

view details

push time in a month

PR merged ginger-io/react-use-form

Reviewers
[React useForm]: Handle multiple onChange events in the same render cycle

This PR fixes a subtle bug where we'd fail to capture updates due to stale state if you called multiple onChange handlers within the same React render cycle.

To give a concrete example, you might have a React component for Date, and when the date changes you might do something like:

const onClick = () => { // somebody clicked a date in our date picker widget
  fields.day.onChange(...)
  fields.month.onChange(...)
  fields.year.onChange(...)
}

Prior to this PR, that would fail and you'd only see year get set with the other two fields being undefined. This would occur because the state variable we were using would become stale, but only within the same render cycle.

+21 -21

0 comment

3 changed files

jcarver989

pr closed time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: Handle multiple onChange events in the same render cycle

 describe('useForm', () => {      act(() => {       result.current.fields.name.onChange('Widget A')-    })

To give context on why the test update looks like this, act(() => {}) is a React testing thing that says "hey do these side-effects within a render cycle".

So previously, we were splitting our updates across render cycles. And now I collapsed everything into a single act(() => {}) -- i.e. do all the updates in the same render cycle.

jcarver989

comment created time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: Handle multiple onChange events in the same render cycle

 function createFields<T>(           value,           error,           touched,+          // For onChange / onBlur, we use a "functional" setState call+          // (https://reactjs.org/docs/hooks-reference.html#functional-updates)+          // This avoids issues related to stale state when multiple onChange functions are+          // invoked within the same render cycle -- e.g.+          // const onClick = () => { date.day.onChange(...); date.day.onChange(...) }:           onChange: (updatedValue: any) => {-            const updatedState = produce(state, updatedState => {-              set(updatedState, [...path, 'value'], updatedValue)-              set(updatedState, [...path, 'touched'], true)-            })-            setState(updatedState)+            setState(currentState =>+              produce(currentState, updatedState => {

This produce function is from Immer. Basically it gives you an efficient way to perform immutable updates on JS objects.

And under the covers it generates a proxy object (updatedState here), observes what you do with it, and then captures the diff.

jcarver989

comment created time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: Handle multiple onChange events in the same render cycle

 function createFields<T>(           value,           error,           touched,+          // For onChange / onBlur, we use a "functional" setState call+          // (https://reactjs.org/docs/hooks-reference.html#functional-updates)+          // This avoids issues related to stale state when multiple onChange functions are+          // invoked within the same render cycle -- e.g.+          // const onClick = () => { date.day.onChange(...); date.day.onChange(...) }:           onChange: (updatedValue: any) => {-            const updatedState = produce(state, updatedState => {-              set(updatedState, [...path, 'value'], updatedValue)-              set(updatedState, [...path, 'touched'], true)-            })-            setState(updatedState)+            setState(currentState =>

So the bug was that our state variable would become stale if we tried to change it multiple times within the same tick in the event loop. But React allows us to pass a function to setState, which gives us the up-to-date value

jcarver989

comment created time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: Handle multiple onChange events in the same render cycle

 function getInitialState<T>(fieldDefs: FieldDefinitions<T>): FieldsState<T> {  function createFields<T>(   state: FieldsState<T>,-  setState: (state: FieldsState<T>) => void+  setState: (f: (state: FieldsState<T>) => FieldsState<T>) => void

The setState function React gives us is a union type, so here I just switch the param type to the other side of the union.

jcarver989

comment created time in a month

PR opened ginger-io/react-use-form

Reviewers
[React useForm]: Handle multiple onChange events in the same render cycle

This PR fixes a subtle bug where we'd fail to capture updates due to stale state if you called multiple onChange handlers within the same React render cycle.

To give a concrete example, you might have a React component for Date, and when the date changes you might do something like:

const onClick = () => { // somebody clicked a date in our date picker widget
  fields.day.onChange(...)
  fields.month.onChange(...)
  fields.year.onChange(...)
}

Prior to this PR, that would fail and you'd only see year get set with the other two fields being undefined

+21 -21

0 comment

3 changed files

pr created time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: add isEmpty field to hook

 export function useForm<T extends Record<string, any>>(   const fields = useMemo(() => createFields(state, setState), [state])   const validate = useCallback(() => runValidation(state, setState), [state]) +  const isEmpty = useMemo(() => {+    let isEmpty = true+    forEach<FieldsState<T>>(state, (_, { value }: any) => {

Yea -- good question. I consider a form with pre-filled / default values to be non-empty -- e.g. if it's a text box it might start with a value of "foo". But if the user deletes that, then the form becomes "empty".

For additional context, I added isEmpty because it gives us an easy way to determine if we should run validation against an entity in a list or filter it out (b/c it's "empty").

jcarver989

comment created time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: add isEmpty field to hook

 export function useForm<T extends Record<string, any>>(   const fields = useMemo(() => createFields(state, setState), [state])   const validate = useCallback(() => runValidation(state, setState), [state]) +  const isEmpty = useMemo(() => {+    let isEmpty = true+    forEach<FieldsState<T>>(state, (_, { value }: any) => {

It'd be pretty easy to write a findFirst or similarly named function that recursively traverses an object's properties but bails as soon as a passed predicate evaluates to true -- will add that in a subsequent PR.

jcarver989

comment created time in a month

Pull request review commentginger-io/react-use-form

[React useForm]: add isEmpty field to hook

 export function useForm<T extends Record<string, any>>(   const fields = useMemo(() => createFields(state, setState), [state])   const validate = useCallback(() => runValidation(state, setState), [state]) +  const isEmpty = useMemo(() => {+    let isEmpty = true+    forEach<FieldsState<T>>(state, (_, { value }: any) => {

Ah yea should have left a comment about this. I had forEach laying around in here because elsewhere we have to traverse every property on an object (e.g. to convert the caller's per-field definitions to our per-field state).

So this is a lazy re-use, that while theoretically is inefficient, probably won't really be noticeable until we have really big forms (e.g 100 fields). Still, it'd be nice to short-circuit.

jcarver989

comment created time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha fbe0174beb682ec80d195ca23330897d13e91c86

Add isEmpty field to useForm

view details

Josh Carver

commit sha 106b666059446f08084598bb85406c73c385375a

Formatting changes to appease the husky hook

view details

Josh Carver

commit sha 3cec1ad5f5ff96a5466d7556c90a225ea7e7ab29

bump version

view details

Joshua Carver

commit sha 08b481c614508fa96bdff7c3a47f25301fffb8be

Merge pull request #1 from ginger-io/feature/isEmpty [React useForm]: add isEmpty field to hook

view details

push time in a month

PR merged ginger-io/react-use-form

[React useForm]: add isEmpty field to hook

This PR adds an isEmpty value which makes it easy to determine if the form state has any value populated or none.

+75 -4

0 comment

5 changed files

jcarver989

pr closed time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha 3cec1ad5f5ff96a5466d7556c90a225ea7e7ab29

bump version

view details

push time in a month

PR opened ginger-io/react-use-form

[React useForm]: add isEmpty field to hook

This PR adds an isEmpty value which makes it easy to determine if the form state has any value populated or none.

+75 -4

0 comment

5 changed files

pr created time in a month

create barnchginger-io/react-use-form

branch : feature/isEmpty

created branch time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 export type ExtractKeyType<T> = T extends PartitionAndSortKey<infer U> export type GroupedModels<T extends TaggedModel> = {   [K in T["model"]]: T extends { model: K } ? T[] : never }++export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]

Hmm... This ends up generating a pretty crazy type that's hard to read. And it appears to be basically equivalent to Partial<{ [X in U]: string }> -- i.e. you can specify one or more of the key parts.

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 Tables:    expect(lines).toContainEqual(`name: BestNameEvah`) })++it("should generate a complex key model", () => {+  const result = generateCode(`+Tables:+  ComplexLibrary:+    Partitions:+      ComplexAuthors:+        ComplexAuthor:+          partitionKey: [Author, $id]

Would like to add a test for partition keys as well, since it looks like this PR supports complex keys for both.

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 Tables:    expect(lines).toContainEqual(`name: BestNameEvah`) })++it("should generate a complex key model", () => {

Nice test, thanks!

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 export class Model<     }   } -  private buildKey(prefix: string, key: string): string {+  /* TODO: Had issue mapping unioned fields of T & V */+  private _buildPartitionKey(model: AtLeastOne<{ [X in U]: string }>, fields: U | U[]): string | string[] {

The _ prefix in function names is usually a result of host languages not supporting a private keyword / feature -- e.g. Python. Since TypeScript already has a private keyword, there's no need to prefix private methods with _.

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 export class Model<     } = this     return new PartitionAndSortKey(       this.table.partitionKeyName,-      this.buildKey(partitionKeyPrefix, params[partitionKeyField]),+      this.buildKey(+        partitionKeyPrefix,+        this._buildPartitionKey(params, partitionKeyField)),        this.table.sortKeyName,-      this.buildKey(sortKeyPrefix, params[sortKeyField]),+      this.buildKey(+        sortKeyPrefix,+        this._buildSortKey(params, sortKeyField)),+      this.modelTag+    )+  }++  partialKey(+    params: AtLeastOne<{ [X in U]: string }> & AtLeastOne<{ [Y in V]: string }>

Hmm... I think the types we'd want to express here would be a union like:

{ country: string } | { country: string, state: string } | { country: string, state: string, city: string }

This is tricky to do as is since:

  1. We have a generic model class
  2. All it knows inside here is that partitionKeys / sortKeys are a union type.
  3. Afaik there's not a way to extract keys of a union type in-order and re-combine them to what we'd want (maybe there is, I just don't know about it).

This would be easier to express if we explicitly codegenned each model vs having a generic model class as we do today. Since then, we could just codegen the right types directly

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 export class Model<     }   } -  private buildKey(prefix: string, key: string): string {+  /* TODO: Had issue mapping unioned fields of T & V */+  private _buildPartitionKey(model: AtLeastOne<{ [X in U]: string }>, fields: U | U[]): string | string[] {+    if (Array.isArray(fields)) {

I think this is another place we're we'd benefit from normalizing non-array keys into arrays. For example, we could transform keys to arrays internally in the model's constructor.

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 import { PartitionAndSortKey, PartitionKeyAndSortKeyPrefix } from "./keys" import { Table } from "./Table"-import { TaggedModel } from "./types"+import { TaggedModel, AtLeastOne } from "./types"+import { key } from "aws-sdk/clients/signer"

Don't think you intended to import this?

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 export function groupBy<T extends { [key: string]: any }, U extends keyof T>(    return Object.entries(groupedItems) }++export function replaceKeyDollar(key: string | string[]) {+  if (Array.isArray(key)) {+    return key.map((k) => k.replace("$", ""))+  }+  return key.replace("$", "")+}++export function buildKey(key: string | string[]) {+  const cleanKey = replaceKeyDollar(key);+  if (Array.isArray(cleanKey)) {+    const keys = cleanKey.map((k) => `"${k}"`).join(', ')+    return `[${keys}]`+  }+  return `"${cleanKey.replace("$", "")}"`

This seems to do what your replaceKeyDollar function does above. I think replaceKeyDollar can go away if we transform string types to arrays at the top per my previous comment.

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 export function groupBy<T extends { [key: string]: any }, U extends keyof T>(    return Object.entries(groupedItems) }++export function replaceKeyDollar(key: string | string[]) {

Hmm...we're threading this string | string[] type through multiple places in the code. I'd prefer we only do this check once at the "top level" and just transform string types into string[]. That way, everything that comes after can just assume the type is string[]

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 export function groupBy<T extends { [key: string]: any }, U extends keyof T>(    return Object.entries(groupedItems) }++export function replaceKeyDollar(key: string | string[]) {+  if (Array.isArray(key)) {+    return key.map((k) => k.replace("$", ""))+  }+  return key.replace("$", "")+}++export function buildKey(key: string | string[]) {+  const cleanKey = replaceKeyDollar(key);+  if (Array.isArray(cleanKey)) {+    const keys = cleanKey.map((k) => `"${k}"`).join(', ')

Not clear on why we put a dollar sign on each key part, and then just remove the first one below.

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 export function groupBy<T extends { [key: string]: any }, U extends keyof T>(    return Object.entries(groupedItems) }++export function replaceKeyDollar(key: string | string[]) {

Style: all function types outside of tests should explicitly specify their return types. This avoids accidentally getting the wrong type inferred.

For inline lambdas, e.g. const f = () => { ... }, it's fine to leave the return type off since they're usually very simple .

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 import { Fields, Table } from "./types"+import {buildKey, replaceKeyDollar} from "./util"

These imports seem not be used?

cjoelrun

comment created time in 2 months

Pull request review commentginger-io/beyonce

Partial Key Queries

 function generateEncryptionBlacklist(table: Table): string {     .forEach((model) => {       const [, pk] = model.keys.partitionKey       const [, sk] = model.keys.sortKey-      encryptionBlacklistSet.add(pk.replace("$", ""))-      encryptionBlacklistSet.add(sk.replace("$", ""))++      const pks = buildKeyArray(model.keys.partitionKey[1])+      const sks = buildKeyArray(model.keys.sortKey[1])+      pks.forEach((key: string) => encryptionBlacklistSet.add(key))+      sks.forEach((key: string) => encryptionBlacklistSet.add(key))     })    table.gsis.forEach(({ name, partitionKey, sortKey }) => {-    encryptionBlacklistSet.add(partitionKey.replace("$", ""))-    encryptionBlacklistSet.add(sortKey.replace("$", ""))+    const pk = buildKeyArray(partitionKey)

Nit: would just inline to: buildKeyArray(partitionKey).forEach(key => encryptionBlacklist.add(key))

cjoelrun

comment created time in a month

Pull request review commentginger-io/beyonce

Partial Key Queries

 function generateEncryptionBlacklist(table: Table): string {     .forEach((model) => {       const [, pk] = model.keys.partitionKey       const [, sk] = model.keys.sortKey-      encryptionBlacklistSet.add(pk.replace("$", ""))-      encryptionBlacklistSet.add(sk.replace("$", ""))++      const pks = buildKeyArray(model.keys.partitionKey[1])

Looks like you can just use the pk and sk variables above as they're already bound to model.keys.partitionKey[1] and model.keys.sortKey[1]

cjoelrun

comment created time in 2 months

push eventginger-io/react-use-form

Josh Carver

commit sha 276c777aac43dea141783d6df3f26cc4e7395b44

Update docs to use Person/Address instead of Widget

view details

push time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha 4e63146087c956bfd8832fa63542a447060fefe8

export types from index + update imports in readme

view details

push time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha 3c62b50bb2b828e92292939439e900ebab83be4e

update docs

view details

push time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha 2efa02ebd8993972b14a5ba9a847240eb01fa3d3

rename internal method: forEveryProp => forEach

view details

push time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha 4f83f05296bfd9e58ce51c293785d8b200f9dce3

tweak readme

view details

push time in a month

push eventginger-io/react-use-form

Josh Carver

commit sha 91a8af5f52555877e562207512789fddb0737532

Commit config files

view details

Josh Carver

commit sha 7c8ea11c216a54c1087d09a609ced1e883a21825

Initial commit of source files

view details

push time in a month

create barnchginger-io/react-use-form

branch : master

created branch time in a month

created repositoryginger-io/react-use-form

A react hook that makes building forms easier.

created time in a month

pull request commentginger-io/beyonce

Partial Key Queries

Nice @cjoelrun ! Going to try to get to reviewing this ~soon.

cjoelrun

comment created time in 2 months

pull request commentstephenh/ts-proto

support bytes as Buffer by Env option

I think the browser/node option and cast makes sense...at least after a cursory look.

One interesting thing worth pointing out: is you can generate an env=browser package and use it both in the browser and in node.

This is because Uint8Array is a superclass of node's Buffer, and so even though the underlying type parsed by protobufjs in node is actually a Buffer, the "browser" interface's Uint8Array type still works.

dolsup

comment created time in 2 months

more