question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Race condition in unsubcription of useEffect in useAbility

See original GitHub issue

Describe the bug

Sporadically I get the following error from useAbility:

react_devtools_backend.js:2842 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    at Inner (https://localhost:3000/src/components/Control.tsx?t=1628859377104:22:3)
    at UiPathContextProvider (https://localhost:3000/src/contexts/UiPathContextProvider.tsx:19:3)
    at div
    at Control (https://localhost:3000/src/components/Control.tsx?t=1628859377104:43:3)
    at InfoField (https://localhost:3000/src/components/numeric/InfoField.tsx?t=1628859377104:22:3)
    at EmptyField
    at Loading (https://localhost:3000/src/components/RenderTag.tsx?t=1628859377104:41:3)

To Reproduce

When I trigger an ability change via an AbilityContext upstream in the UI tree, every 5 to 10 re-renders the error occurs. useAbility is used by a primitive UI control wrapper component used a lot in my project ( I use CASL ability for controlling write access, which re-renders UI controls to a read-only state when use level is not high enough).

Expected behavior No race condition

Interactive example (optional, but highly desirable) I have a large hierarchy and large parts of the UI tree gets sometimes unmounted as a response of an ability change. So it is really difficult to reproduce this error in a minimal project.

As an alternative I tried already a bug fix which seems to work. I replace the original implementation of the useAbility:

import React from 'react';
import { AnyAbility } from '@casl/ability';

export function useAbility<T extends AnyAbility>(context: React.Context<T>): T {
  if (process.env.NODE_ENV !== 'production' && typeof React.useContext !== 'function') {
    /* istanbul ignore next */
    throw new Error('You must use React >= 16.8 in order to use useAbility()');
  }

  const ability = React.useContext<T>(context);
  const [rules, setRules] = React.useState<T['rules']>();

  React.useEffect(() => ability.on('updated', (event) => {
    if (event.rules !== rules) {
      setRules(event.rules);
    }
  }), []);

  return ability;
}

with this:

import { AnyAbility } from '@casl/ability';
import React, { useContext, useEffect, useRef, useState } from 'react';

function useAbility<T extends AnyAbility>(context: React.Context<T>): T {
  if (process.env.NODE_ENV !== 'production' && typeof useContext !== 'function') {
    /* istanbul ignore next */
    throw new Error('You must use React >= 16.8 in order to use useAbility()');
  }

  const ability = useContext<T>(context);
  const [rules, setRules] = useState<T['rules']>();
  const subscribed = useRef(false);

  useEffect(() => {
    const unsubscribe = ability.on('updated', event => {
      if (subscribed.current && event.rules !== rules) {
        setRules(event.rules);
      }
    });
    subscribed.current = true;
    return function () {
      subscribed.current = false;
      unsubscribe();
    };
  }, [ability, rules]);

  return ability;
}

export default useAbility;

Basically I currently suspect the race condition somewhere inside the unsubscribe function returned from ability.on and just don’t call setRules (which triggers a setState and the bug) anymore as soon as the component is unmounted.

This fixes the racing condition 100%

CASL Version@casl/ability”: “^5.4.0”, “@casl/react”: “^2.3.0”,

Environment: NodeJS on Windows, newest Chrome

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:22 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
gunters63commented, Aug 17, 2021
import { defineAbility } from '@casl/ability';

const ability = defineAbility((can, cannot) => {
  can('manage', 'all');
  cannot('delete', 'User');
});

ability.on('updated', event => {
  console.log('1');
});
ability.on('updated', event => {
  console.log('2');
});
ability.on('updated', event => {
  console.log('3');
});
ability.on('updated', event => {
  console.log('4');
});

const unsubscribeHead = ability.on('updated', event => {
  console.log('5');
});

ability.update(ability.rules);

unsubscribeHead();

ability.update(ability.rules);

Output is:

5
4
3
2
1
5

After unsubscribing the head element the other event handlers are lost

If you unsubscribe any other elements except the head its working

0reactions
stalniycommented, Aug 17, 2021

AWESOME! Thank you @gunters63 very much for being so responsive and helpful!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Fixing Race Conditions in React with useEffect - Max Rozen
You would typically notice a race condition (in React) when two slightly different requests for data have been made, and the application displays...
Read more >
Avoiding race conditions and memory leaks in React useEffect
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application....
Read more >
React - Race Condition in multiple useEffect's cleanup function
I have a component that. Initiate a websocket ref on mount ( endpoint prop doesn't change); When component comes into screen view, ...
Read more >
The Complete React Bootcamp 2022 (w - Skillshare
Videos we will check will use conditional rendering to render, Let's say, a race to mop race to Jan. Six elements and we...
Read more >
Avoiding useEffect race conditions with a custom hook
This will happen when the component is unmounted before the data is returned. But there is also a separate issue here too; if...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found