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

Typescript "This expression is not callable. Type 'never' has no call signatures." #802

Closed
hoetmaaiers opened this issue Oct 26, 2021 · 18 comments

Comments

@hoetmaaiers
Copy link

When letting Typescript derive the type from useAtom, it warns me with the message "This expression is not callable.
Type 'never' has no call signatures.".

This is a code example:

interface CitySelection {
  title: string;
}
const citySelectionAtom = atom<CitySelection>(null);

When used in a component, the Setter is type as never:

const [citySelection, setCitySelection] = useAtom(citySelectionAtom);

calling setCitySelection triggers the Typescript warning because it's automatically inferred type is never.

@dai-shi
Copy link
Member

dai-shi commented Oct 26, 2021

Can you try this?

const citySelectionAtom = atom<CitySelection | null>(null);

@dai-shi
Copy link
Member

dai-shi commented Oct 26, 2021

Oh, but in this case, it should warn there. So, I guess your tsconfig doesn't enable strictNullChecks. Can you please confirm?

@dai-shi
Copy link
Member

dai-shi commented Oct 26, 2021

typescript playground

If you remove strict and strictNullChecks checks, the atom will be read-only type.

@dai-shi
Copy link
Member

dai-shi commented Oct 26, 2021

Previous discussion: #550

As we understand, this is not solvable from the library perspective.
Options are:

  • enable strictNullChecks (or more preferably enable strict)
  • don't use null (use false instead, for example)

@hoetmaaiers
Copy link
Author

Thank you @dai-shi , the project was an older one being converted to modern times. Setting strict: true solved the issue.

@sangdth
Copy link

sangdth commented Oct 29, 2021

Sorry for hijacking this topic, but my issue is similar to this one. Could someone help me understand why do I have the never issue in this case? I believe the types are good :(

https://codesandbox.io/s/optimistic-carson-3c3bq?file=/src/App.tsx

@dai-shi
Copy link
Member

dai-shi commented Oct 29, 2021

Maybe, it doesn't like the use of conditional focusAtom.
And/or, optic.path(string) doesn't seem to type correctly.

@sangdth
Copy link

sangdth commented Oct 29, 2021

yeah, look like it, I remove the optics now, use pure atom works perfectly, sorry for unneccesery notification

@cgrewon
Copy link

cgrewon commented Dec 28, 2022

I am facing same issue but I can't solve this as above.
Even actually simple atom and useAtom.

@dai-shi
Copy link
Member

dai-shi commented Dec 29, 2022

Can you open a new discussion with a reproduction?

@dn-l
Copy link

dn-l commented Mar 9, 2023

Looks ugly but the best workaround I use is a wrap:

const someAtom = atom<{ some: Some | null }>({ some: null })

@max-programming
Copy link

Enabled strict in tsconfig.json and works like charm now

@BobbyRaduloff
Copy link

BobbyRaduloff commented Jan 16, 2024

I know this is marked as closed, but I want to leave this comment in case someone runs into the same issues that I did.

If you want to pass atoms to components in React JSX, you might do something like this:

interface Props {
    atom: Atom<File | null>;
}

export default function UploadPictureModal(props: Props) {
    const [file, set_file] = useAtom(props.atom);
                 // ?^ = never
}

This will break even with strict: true. You have to change it to:

interface Props {
  atom: PrimitiveAtom<File | null>;
}

I believe this applies to #550 and #838 as well.

@Snouzy
Copy link

Snouzy commented Jun 10, 2024

Sorry @BobbyRaduloff but i don't get it, the question is why would you need to pass atoms though components ? it look likes a no sens for me

@BobbyRaduloff
Copy link

@Snouzy what if you have a component that can apply changes to multiple atoms? for example, an file upload drag and drop modal which can put the file in any atom you pass so you can reuse the component in all file upload locations of your application. i'd be happy to hear a more idiomatic way to do this with jotai, but, regardless, the issue with atom typing remains and my code solves it.

@Snouzy
Copy link

Snouzy commented Jun 10, 2024

Hi @BobbyRaduloff,

I guess you can use the Jotai Provider which avoids passing them directly as props to components.
This is beneficial because your current pattern increases coupling between components and the state manager, making testing and reuse more difficult imo

@Aadmaa
Copy link

Aadmaa commented Oct 26, 2024

Very helpful @BobbyRaduloff . FWIW @Snouzy .. I also find it is sometime very helpful to pass atoms to components. For example I have a table component that is very similar for various entities; and I want to save the sorting, filtering and other states in atoms specific to each component - so I can just pass them in to a common table component, and it works. I don't want a different table component for each entity type; and I don't want to manage table logic outside the table component; passing atoms is the perfect mechanism...

@Snouzy
Copy link

Snouzy commented Oct 26, 2024

@Aadmaa While I understand the use case for passing atoms as props to manage specific states like sorting or filtering for each table instance, a solution that might combine the best of both worlds is to use a Provider to centralize the global state, while creating an atomFamily for each specific table instance. I am still not a fan to have a strong couple between state and components.

With atomFamily, you can generate dynamic atoms for each table, allowing each component to maintain a localized and unique state without needing to manually pass atoms as props. This provides control over each individual table without needing multiple Providers or overloading the global configuration.

Here's a quick example:

import { atomFamily } from 'jotai';

// manage unique states for each table
const tableStateFamily = atomFamily((id) => ({
  sort: 'asc',
  filter: '',
}));

//  in each table component you can do this now 
const TableComponent = ({ tableId }) => {
  const [tableState, setTableState] = useAtom(tableStateFamily(tableId));
  
  // Use tableState.sort, tableState.filter, etc.
  ...
};

this approach allows you to manage sorting and filtering states for each table instance while keeping the architecture modular and testable imho

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

No branches or pull requests

9 participants