Skip to content

value not being updated after calling rerender() #525

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

Closed
steoo opened this issue Jan 7, 2021 · 11 comments
Closed

value not being updated after calling rerender() #525

steoo opened this issue Jan 7, 2021 · 11 comments
Labels
question Further information is requested

Comments

@steoo
Copy link

steoo commented Jan 7, 2021

Hello everyone! I tried to post on stackoverflow with no luck and neither my search through issues/google gave me any result :/

So I'm doing a migration. I've got this custom hook that I'm testing with @testing-library/react-hooks, jasmine and karma. It's an AngularJS/React application.

const useSearchParams = () => {
  const $rootScope = useAngularInjection('$rootScope');
  const stateProvider = useAngularInjection('$location');
  const [ url, setUrl ] = useState(stateProvider.search());

  $rootScope.$on('$locationChangeSuccess', () => {
    setUrl(stateProvider.search());
  });

  return url;
};

Where useAngularInjection is another hook that just injects AngularJS stuff (it works fine).

This is my test:

 it('should update the search query from the url when it changes', () => {
      $location.url('https://localhost:8000?all=true&order=title&start=0');
      $rootScope.$apply();

      const { result, rerender } = renderHook(() => useSearchParams());

      $location.url('https://localhost:8000?unscheduled=true&order=title&start=10');
      $rootScope.$apply();

      rerender();

      expect(result.current).toEqual({
        unscheduled: 'true',
        order: 'title',
        start: '10',
      });
 });

At the moment result.current is still {all: 'true', start: '0', order: 'title'} – it's not updating.

I tried to debug it and it actually goes back into the $locationChangeSuccess branch so I don't understand why it's not updating the value of result.current

$rootScope and $location are injected previously and also work fine.

Any idea?

@steoo steoo added the question Further information is requested label Jan 7, 2021
@joshuaellis
Copy link
Member

Would you please be able to provide a minimal working example either the repo or a code sandbox is fine.

@mpeyper
Copy link
Member

mpeyper commented Jan 7, 2021

A working example would help a lot, but a couple of questions and observations...

Does $rootScope.$apply(); apply synchronously or does it update in the future? If it's asynchronous, you would have to wait for it with something like waitForNextUpdate.

Also, if $rootScope.$apply(); is expected to cause setUrl to be called, then you should wrap it in and act call.

Finally, the rerender should not be necessary as the setUrl call will trigger the hook to rerender automatically.

@steoo
Copy link
Author

steoo commented Jan 7, 2021

Hello, thanks for the super quick replies! I will work on a sandbox example, it's just not easy with the whole AngularJS setup :(

$rootScope.$apply() is synchronous – I tried previously with waitForNextUpdate but it was just failing to call it.

Tried by calling

act(() => {
        $rootScope.$apply();
});

but it's still not working. Plus, I read the act docs multiple times but I don't seem to be getting exactly what is supposed to do?

Debugging it (without act) I can see setUrl being called again. So weird :/

@mpeyper
Copy link
Member

mpeyper commented Jan 7, 2021

what is the console output if you change the locationChangeSuccess to:

$rootScope.$on('$locationChangeSuccess', () => {
  const newUrl = stateProvider.search();
  console.log(newUrl)l
  setUrl(newUrl);
});

Assuming it is getting the new start value, can you give me the output for:

const useSearchParams = () => {
  console.log('hook 1');

  const $rootScope = useAngularInjection('$rootScope');
  const stateProvider = useAngularInjection('$location');
  const [ url, setUrl ] = useState(stateProvider.search());

  console.log('hook 2');

  $rootScope.$on('$locationChangeSuccess', () => {
    console.log('hook 4 - this is it');

    setUrl(stateProvider.search());
  });

  console.log('hook 3');

  return url;
};
import { renderHook, act } from '@testing-library/react-hooks'

it('should update the search query from the url when it changes', () => {
  console.log('test 1');

  $location.url('https://localhost:8000?all=true&order=title&start=0');

  console.log('test 2');

  $rootScope.$apply();

  console.log('test 3');

  const { result, rerender } = renderHook(() => useSearchParams());

  console.log('test 4');

  act(() => {
    $location.url('https://localhost:8000?unscheduled=true&order=title&start=10');
  
    console.log('test 5');
  
    $rootScope.$apply();

    console.log('test 6');
  }


  console.log('test 7');

  expect(result.current).toEqual({
    unscheduled: 'true',
    order: 'title',
    start: '10',
  });
});

Old school, but if we see:

test 1
test 2
test 3
hook 1
hook 2
hook 3
test 4
test 5
hook 4 - this is it
hook 1
hook 2
hook 3
test 6
test 7

then something very wrong is going on. That is the output I would expect for your assertion to pass (I think).

@mpeyper
Copy link
Member

mpeyper commented Jan 7, 2021

I read the act docs multiple times but I don't seem to be getting exactly what is supposed to do?

It flushes any batched updates so that by the time act call returns the state represents what was updated from any actions within the callback. In this case, it is likely irrelevant, but react will spit out warnings at you if state is updated outside of an act call.

@steoo
Copy link
Author

steoo commented Jan 7, 2021

Can confirm that

$rootScope.$on('$locationChangeSuccess', () => {
  const newUrl = stateProvider.search();
  console.log(newUrl)l
  setUrl(newUrl);
});

logs the new value.

This is the output (I see you removed the call to rerender() and so did I)

'test 1'
'test 2'
'test 3'
'act(...) is not supported in production builds of React, and might not behave as expected.'
'hook 1'
'hook 2'
'hook 3'
'test 4'
'test 5'
'hook 4 - this is it'
'test 6'
'test 7'

Just noticed the act(...) issue. We're on Jasmine, Karma and jsDom but I'm starting to think Jasmine could be the issue here?

@mpeyper
Copy link
Member

mpeyper commented Jan 7, 2021

It's odd to say the least. I'm not sure how much more we can help without being able to run it ourselves.

Out if interest, which version of @testing-library/react-hooks are you using?

@steoo
Copy link
Author

steoo commented Jan 7, 2021

I'm on the latest version. Unfortunately, can't figure out a way of building an easy sandbox for this.

Thank you very much for your help anyway! Never had such responsive support from library maintainers.

@joshuaellis
Copy link
Member

Thanks! I'm going to close this now, but if you do manage to make a MWE then we can open this up and have a look! 🚀

@mpeyper
Copy link
Member

mpeyper commented Jan 7, 2021

I'm on the latest version. Unfortunately, can't figure out a way of building an easy sandbox for this.

fwiw, it doesn't need to be a sandbox, just a repo we can clone is enough.

Also, does latest mean the v4 we released just hours before you opened this issue, or are you still on v3?

@steoo
Copy link
Author

steoo commented Jan 12, 2021

Moving to Jest actually solved the issue :)

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

No branches or pull requests

3 participants