twenty icon indicating copy to clipboard operation
twenty copied to clipboard

ESLint rule: enforce usage of .getLoadable() + .getValue() to get atoms

Open lucasbordeau opened this issue 1 year ago • 7 comments

Scope & Context

When working in useRecoilCallback, we have to get atoms that corresponds to the current state of the recoil store.

There are two ways to get an atom's value from the store : synchronous (with getLoadable) and asynchronous (with getPromise).

We want to enforce the usage of synchronous get, because in practice, we don't use async selectors or getters in our recoil atoms.

The problem with mixing asynchronous and synchronous getters, without any specific pattern, is that some getters will get executed before others and some getters won't get executed at all.

With synchronous getters, everything stays deterministic, as we really don't need more complexity in our state management layer.

We want to avoid this :

const savedFilters = await snapshot
  .getPromise(savedFiltersFamilyState(viewId));

And enforce this :

const savedFilters = snapshot
  .getLoadable(savedFiltersFamilyState(viewId))
  .getValue();

The existing .getPromise() have already been replaced in the codebase.

lucasbordeau avatar Oct 26 '23 13:10 lucasbordeau

Hi @lucasbordeau Can I work on this issue?

Kanav-Arora avatar Oct 29 '23 16:10 Kanav-Arora

Hi @Kanav-Arora, sure please take on this issue, please join the Discord if you haven't already so we can discuss the details : https://discord.gg/bNgBwJny

lucasbordeau avatar Oct 30 '23 12:10 lucasbordeau

Hi @lucasbordeau Is there a directory for keeping custom eslint rules?

Kanav-Arora avatar Oct 31 '23 09:10 Kanav-Arora

Sure you'll find plenty of examples in packages/eslint-plugin-twenty/src/rules

lucasbordeau avatar Oct 31 '23 13:10 lucasbordeau

@Kanav-Arora should we unsassign you from this?

Here are suggestions from ChatGPT:

import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';

export const RULE_NAME = 'no-getpromise';

export const rule = ESLintUtils.RuleCreator(name => name)({
  name: RULE_NAME,
  meta: {
    type: 'problem',
    docs: {
      description: 'Enforce use of .getLoadable().getValue() instead of .getPromise()',
    },
    messages: {
      noGetPromise: 'Use .getLoadable().getValue() instead of .getPromise()',
    },
    schema: [],
    fixable: 'code',
  },
  defaultOptions: [],
  create: context => {
    return {
      MemberExpression(node: TSESTree.MemberExpression) {
        if (node.property.type === 'Identifier' && node.property.name === 'getPromise') {
          context.report({
            node,
            messageId: 'noGetPromise',
          });
        }
      },
    };
  },
});

And the spec file:

import { TSESLint } from '@typescript-eslint/experimental-utils';
import { rule, RULE_NAME } from './no-getpromise';

const ruleTester = new TSESLint.RuleTester({
  parser: require.resolve('@typescript-eslint/parser'),
});

ruleTester.run(RULE_NAME, rule, {
  valid: [
    {
      code: 'const savedFilters = snapshot.getLoadable(savedFiltersFamilyState(viewId)).getValue();',
    },
  ],
  invalid: [
    {
      code: 'const savedFilters = snapshot.getPromise(savedFiltersFamilyState(viewId));',
      errors: [
        {
          messageId: 'noGetPromise',
        },
      ],
    },
  ],
});

Warning: Not tested / this could be completely wrong

FelixMalfait avatar Jan 25 '24 15:01 FelixMalfait

Hi I tried this but I'll give it a try again

Kanav-Arora avatar Jan 25 '24 15:01 Kanav-Arora

Cool thanks (again and again!) @Kanav-Arora

FelixMalfait avatar Jan 25 '24 15:01 FelixMalfait

Here is the GitStart Ticket for this issue: https://clients.gitstart.com/twenty/5449/tickets/TWNTY-2244

gitstart-app[bot] avatar Feb 20 '24 09:02 gitstart-app[bot]