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

[Frame] hookify Loading #2303

Closed
wants to merge 3 commits into from
Closed

Conversation

sijad
Copy link
Contributor

@sijad sijad commented Oct 16, 2019

WHY are these changes introduced?

related to #1995

WHAT is this pull request doing?

change Frame.Loading to use react hooks

🎩 checklist

@sijad sijad force-pushed the hookify-frame-loading branch 4 times, most recently from 90e8085 to 72ced88 Compare October 16, 2019 20:48
@sijad
Copy link
Contributor Author

sijad commented Oct 16, 2019

I'm not sure how tests can improved?

});
}
const ariaValuenow = useLazyRef(() =>
debounce((progress: number) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this aproach component will only re-rendered when progress changes, unlike the old aproach which would re-render with each requestAnimationFrame call. I guess it'd be safe to remove debounce and calculate the valenow inline.

Copy link
Member

Choose a reason for hiding this comment

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

This is a a pure function that doesn't depend on anything in the outer closure, so there's no need to put it inside the component or do any of the lazyRef stuff, just define it as a regular function outside of the component function


That said, now that we're only rendering this once per progress change I'm good with just defining this value inline. If I've understood your other changes - where progress is now a number between 0 and 1, rounded to two decimal places this should do:

aria-valuenow={progress * 100}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll refactor it and remove debounce, just FTR if we move that function outer closure, multiple instance of this component will use same debounced ref and it might cause data leak between those instance (they mill use each other values).

</div>
);
}
animation = requestAnimationFrame(increment);
Copy link
Member

Choose a reason for hiding this comment

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

Setting the value of a variable outside of the current scope is surpising and we should try and avoid such mutation if possible. Instead of setting the value of an variable outside the current scope, return the value from requestAnimationFrame

Suggested change
animation = requestAnimationFrame(increment);
return requestAnimationFrame(increment);

Then below set the value of animation

const animation = increment();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we should track latest animation for calling cancelAnimationFrame in un-mounting, I'm not sure if const animation = increment(); will works.
should we use ref?

Copy link
Member

Choose a reason for hiding this comment

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

Just chiming in const animation = increment(); won't keep track of the recursed animation as the assignment will

src/components/Frame/components/Loading/Loading.tsx Outdated Show resolved Hide resolved
});
}
const ariaValuenow = useLazyRef(() =>
debounce((progress: number) => {
Copy link
Member

Choose a reason for hiding this comment

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

This is a a pure function that doesn't depend on anything in the outer closure, so there's no need to put it inside the component or do any of the lazyRef stuff, just define it as a regular function outside of the component function


That said, now that we're only rendering this once per progress change I'm good with just defining this value inline. If I've understood your other changes - where progress is now a number between 0 and 1, rounded to two decimal places this should do:

aria-valuenow={progress * 100}

src/components/Frame/components/Loading/Loading.tsx Outdated Show resolved Hide resolved
}
}
const increment = () => {
if (currentProgress >= STUCK_THRESHOLD) {
Copy link
Member

Choose a reason for hiding this comment

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

You seem to have changed the progress from being an integer between 0 an 100, to a number rounded to 2 decimal places between 0 and 1. I'm ok with that, but make sure that you update the stuck threshold and anything else like how the next step is calculated to account for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

currentProgress still go from 0 - 100 with not limited decimal point (e.g. 20, 21.332232332, 22.12321, 23.5565, etc)
nextProgress and previousProgress goes from 0 - 1 with only two decimal point (e.g. 0.12, 0.13, 0.14, 0.15)
with this approach set state called only when nextProgress and previousProgress are not equal which save redundant re-rendering. if we set currentProgress in state, it'd re-render more often (due its decimals changes with each requestAnimationFrame call)

componentWillUnmount() {
const {animation} = this.state;
useEffect(() => {
let animation: number | undefined;
Copy link
Member

Choose a reason for hiding this comment

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

Should animation, step currentProgress and previousProgress be refs? It seems to work ok in this closure but I'm not sure why.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as we don't need those outside of useEffect I moved them all there, a race condition might occurs?

Copy link
Member

Choose a reason for hiding this comment

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

Should animation, step currentProgress and previousProgress be refs? It seems to work ok in this closure but I'm not sure why.

From the current implementation, it should work as-is from what I can tell. We're creating a closure, recursing and mutating the original values. setProgress will be consistently called with the correct values, and when the component is dismounted it'll have the current animation in scope to clear.

@sijad sijad force-pushed the hookify-frame-loading branch from 72ced88 to 64e93ee Compare October 17, 2019 16:55
@AndrewMusgrave
Copy link
Member

Sorry for the wait @sijad @BPScott is busy at the moment and won't be able to review this again. If it's ready for another review let me know and I can take look!

@sijad
Copy link
Contributor Author

sijad commented Nov 21, 2019

no worries, take your time
there're few unresolved conversations but it'd be great if you could review this again

@AndrewMusgrave AndrewMusgrave self-requested a review November 21, 2019 20:44
src/components/Frame/components/Loading/Loading.tsx Outdated Show resolved Hide resolved
componentWillUnmount() {
const {animation} = this.state;
useEffect(() => {
let animation: number | undefined;
Copy link
Member

Choose a reason for hiding this comment

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

Should animation, step currentProgress and previousProgress be refs? It seems to work ok in this closure but I'm not sure why.

From the current implementation, it should work as-is from what I can tell. We're creating a closure, recursing and mutating the original values. setProgress will be consistently called with the correct values, and when the component is dismounted it'll have the current animation in scope to clear.

</div>
);
}
animation = requestAnimationFrame(increment);
Copy link
Member

Choose a reason for hiding this comment

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

Just chiming in const animation = increment(); won't keep track of the recursed animation as the assignment will

@sijad sijad force-pushed the hookify-frame-loading branch from 64e93ee to a889964 Compare November 23, 2019 09:59
@sijad
Copy link
Contributor Author

sijad commented Nov 23, 2019

let me know if there's anything else than can be improved.
I'd also work on tests but as this component uses requestAnimationFrame I couldn't figure it out how?

@AndrewMusgrave
Copy link
Member

Mocking can be done a bunch of ways in jest, however, for RAF you'll probably want to go with one of these two depending on what you're trying to do.

describe('suite', () => {
  let requestAnimationFrameSpy: jest.SpyInstance;

  beforeEach(() => {
    requestAnimationFrameSpy = jest.spyOn(window, 'requestAnimationFrame');
    requestAnimationFrameSpy.mockImplementation((cb) => {
      cb();
      // unique id used to cancel rafs
      return 1;
    });
  });

  afterEach(() => {
    requestAnimationFrameSpy.mockRestore();
  });

  ...
  it statements
  ...
})

or

const origRequestAnimationFrame = window.requestAnimationFrame;
describe('suite', () => {
  afterEach(() => {
    // This might require Object.defineProperty
    window.requestAnimationFrame = origRequestAnimationFrame;
  });

  ...
  it('statement', () => {
    // This might require Object.defineProperty
    window.requestAnimationFrame = someMockRaf;
  })
  ...
})

// Object.defineProperty usage
// Object.defineProperty(window, 'requestAnimationFrame', {
//   configurable: true,
//   writable: true,
//   value: someMockFn,
// })

If you have any specific questions I'm happy to help :)

@sijad sijad force-pushed the hookify-frame-loading branch 3 times, most recently from 69de5ec to e05f0c2 Compare November 27, 2019 19:31
@sijad
Copy link
Contributor Author

sijad commented Nov 27, 2019

@AndrewMusgrave thanks for the tips, I get Maximum call stack size exceeded error with recursive requestAnimationFrameSpy method, I went and used animationFrame

@sijad sijad force-pushed the hookify-frame-loading branch 3 times, most recently from 9c66eab to a06acac Compare November 27, 2019 20:54
@sijad sijad force-pushed the hookify-frame-loading branch from a06acac to 6e85a5f Compare November 27, 2019 21:04
@sijad sijad force-pushed the hookify-frame-loading branch from 6e85a5f to d8ba2fc Compare November 27, 2019 21:16

it('mounts', () => {
expect(loading.exists()).toBe(true);
for (let i = 0; i <= 100; i++) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

is this a good approach?
we can also change 100 to 100,000 to have a 100% code coverage but it took ~7 seconds in my computer...

@ghost
Copy link

ghost commented May 25, 2020

This issue has been inactive for 180 days and labeled with Icebox. It will be closed in 7 days if there is no further activity.

@ghost ghost added the Icebox label May 25, 2020
@ghost ghost closed this Jun 1, 2020
@sijad
Copy link
Contributor Author

sijad commented Jun 2, 2020

this is very disappointing, at first I thought this is a community driven project, but now I realized I was wrong.

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

Successfully merging this pull request may close these issues.

3 participants