Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fix for issue 2996 #3046

Closed
wants to merge 20 commits into from
Closed

Add fix for issue 2996 #3046

wants to merge 20 commits into from

Conversation

puckowski
Copy link

  • Add fix for issue 2996. Watch magic had an issue with value reference.

Should address: #2996

Cypress tests replicated here and all passing using the patch:
https://codepen.io/puckowski/pen/poLRwEr?editors=1111
39 assertions passing.

User reported in #2996 that the fix solved their issue.

* Add fix for issue 2996. Watch magic had an issue with value reference.
@puckowski
Copy link
Author

image

Fix works for user reported Code Pen at: https://codepen.io/dinyo/pen/eYVaKgj?editors=1111
User Code Pen from: #2996

@stepanjakl
Copy link
Contributor

I am facing the same issue this.$watch('filter.active', (value, oldValue) => { this.onFilterChange(value, oldValue) }), where deeply nested values don't retain the oldValue. This PR should help.

@ovenum
Copy link

ovenum commented Aug 5, 2022

Thanks for creating this pull request @puckowski.
Currently also entering a hole-of-dev-doom when using $watch.… : )

I’ve looked at the PR and when calling JSON.parse(JSON.stringify(value)) on an array of File objects it will return an array of empty JS objects. This might not be what people using it are expecting.

If this PR gets accepted, the documenation should explain the capabilites/limitation for $watch a bit better.

@SimoTod
Copy link
Collaborator

SimoTod commented Aug 5, 2022

Most recent browsers support structuredClone but it's quite new and you would need a polyfill for the rest (https://github.com/zloirock/core-js#structuredclone). I suppose $watch could warn the user that they should include a polyfill when watching an object then use structuredClone if it exists or your solution otherwise.

@@ -17,10 +17,10 @@ magic('watch', (el, { evaluateLater, effect }) => (key, callback) => {
queueMicrotask(() => {
callback(value, oldValue)

oldValue = value
oldValue = JSON.parse(JSON.stringify(value))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON.stringify is expensive, the code already does it at line 12 so you can just assign it to a temp variable and reuse that in here?

})
} else {
oldValue = value
oldValue = JSON.parse(JSON.stringify(value))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

* Use strucutredClone for improved watch to avoid object by reference
  issues.
@puckowski
Copy link
Author

puckowski commented Aug 6, 2022

I reworked my fix to use structuredClone, but it seems Cypress doesn't have access to structuredClone some some checks have failed with the latest push.

Users would need to polyfill structuredClone for older browser support from core-js (https://www.npmjs.com/package/core-js) in order for this solution to work.

image

* Add warning and try to update Cypress.
* Update focus-trap to version 6.9.4.
* Try to fix focus trap when no autofocus defined.
* Working on focus trap.
* Fixes for focus-trap.
@puckowski
Copy link
Author

Will scrutinize the changes I have made later today. Had to update Cypress to get access to structuredClone, and also updated focus-trap with a minor code change in the focus index.js.

* Add Cypress test for watch nested arrays.
* Add test for structuredClone support and warn.
* Add comments for focus handling of initial focus.
* Refactor to use method to reduce duplicate lines of code.
@puckowski
Copy link
Author

puckowski commented Aug 7, 2022

To summarize, I used structuredClone instead of JSON.parse(JSON.stringify()) which means that users of watch will need to polyfill structuredClone for older browser support. I log a warning if structuredClone is not defined on the window object.

I had to update Cypress from 5.5.0 to 9.7.0 in order for the tests to pass (newer version of browser with structuredClone support).

Updating Cypress seems to have broken some tests for focus, so I updated focus-trap to 6.9.4 from 6.6.1 and made a small code change with comments to focus index.js.

@SimoTod

Cypress tests passing and passing in this CodePen.
https://codepen.io/puckowski/pen/poLRwEr?editors=1111

Accidentially closed this pull request for a moment. Still would like to see it merged.

FIx also still works for https://codepen.io/dinyo/pen/eYVaKgj?editors=1111

@puckowski puckowski closed this Aug 7, 2022
@puckowski puckowski reopened this Aug 7, 2022
* Improve watch edge cases when trying strucutredClone on some proxied objects.
* Add watch test for array with objects.
@puckowski
Copy link
Author

  • Simple watch case working in https://codepen.io/puckowski/pen/VwXBKWZ?editors=1111
  • All watch tests passing, plus some logic for watching File objects working in https://codepen.io/puckowski/pen/VwXBKWZ?editors=1111 (CodePen does not fully log File objects, add the code to a local IDE to test more thoroughly).
  • Added test for watch with arrays containing objects.
  • Removed unnecessary JSON.stringify call for performance.
  • Added improved handling for cloning some proxied objects. Prefer structuredClone first. If that fails, shallow clone a new object and then perform a structuredClone on the shallow copy. Logic is working.

// JSON.stringify touches every single property at any level enabling deep watching
JSON.stringify(value)
const shallowClone = (obj) => {
if (typeof obj !== 'object') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi all — just implemented this PR in my own project and got a JS error for my troubles. It was due to this line assuming that if obj is an object, then it can be parsed by Object.keys — This is incorrect, as null is also an object, but cannot be parsed:

Object.keys(null)
> Uncaught TypeError: can't convert null to object

I would recommend you add another check for null, e.g:

if (
    typeof obj !== 'object' ||
    obj === null
) {

(There may be other clauses needed here but this one got it working for me).

Also: Is it odd that it tested? Apologies, but I didn't have time to look at your test suites.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Not doing this as an official review as I don't know your contrib process. Sorry if I'm stepping on toes here!)

Copy link

@njpanderson njpanderson Aug 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies guys but I've also noticed that the watcher only seems to detect the change in value once. No subsequent changes are detected. I've not got a lot of time to look into this properly but thought you should know regardless. Thanks!

* Add support for edge case with null object.
* Add two tests for watch for Cypress that deal with null object.
* Update Cypress magic tests to ensure watched value is updated.
* Update Cypress magic tests. Verify old value is being updated
  correctly.
* Update Cypress magic tests.
* Fix callback not being triggered.
* Update focus for tests.
* Revert focus on element change.
@puckowski
Copy link
Author

@njpanderson Let me know if this resolves your issue.

Old:

  • Simple watch case working in https://codepen.io/puckowski/pen/VwXBKWZ?editors=1111
  • All watch tests passing, plus some logic for watching File objects working in https://codepen.io/puckowski/pen/VwXBKWZ?editors=1111 (CodePen does not fully log File objects, add the code to a local IDE to test more thoroughly).
  • Added test for watch with arrays containing objects.
  • Removed unnecessary JSON.stringify call for performance.
  • Added improved handling for cloning some proxied objects. Prefer structuredClone first. If that fails, shallow clone a new object and then perform a structuredClone on the shallow copy. Logic is working.

@njpanderson
Copy link

I've given the latest push a few run throughs @puckowski and not seeing any further issues. Nice work!

@kenset
Copy link

kenset commented Oct 8, 2022

Apologies if this is not the right place to ask, but is there a timeline for this being merged and released?

@ekwoka
Copy link
Contributor

ekwoka commented Oct 8, 2022

@kenset

No. Caleb has been very busy with the Livewire rewrite and has a been a bit of an absent father to little Alpine. It may not be until after the new Livewire is released.

@joshhanley
Copy link
Collaborator

Thanks for the PR and all the support!

It's well thought out and makes sense. There are a couple of concerns with this PR. One is the size of the implementation, it adds quite a few lines of code, which increases the package size. And the other is using an API that is less than a year old, which may cause issues for end users.

We're going to leave it open for the moment, so it can be reviewed in detail.

@joshhanley
Copy link
Collaborator

As a further follow up, we're going to leave this PR open for now, as Caleb wants to review it in detail, but currently doesn't have time whilst working on Livewire V3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants