Skip to content

"Property does not exist on type 'never'" with optional chaining #58733

Closed as not planned

Description

🔎 Search Terms

optional chaining, null, undefined, property does not exist on type never

🕗 Version & Regression Information

  • This is a crash
  • This changed between versions ______ and _______
  • This changed in commit or PR _______
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________
  • I was unable to test this on prior versions because _______

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/C4TwDgpgBACgTgewFYQMbCgXigbwFBRQCWAJgFxQB2ArgLYBGEcBUwRwANhBQM7BxFKAcwA0LDoIDWvfoNEsSEHqgFg2CSjIHC8AXzx4uGMIhToK8ZGgwAfKtQ4cs9xwG4DqDTwRcAdBwQhAAoTK3QAfl82TggASnc8IA

💻 Code

type Project = {
  id: number
  title: string,
  link: string,
  description: string
}

let project: Project | null = null;

console.log(project?.title);

🙁 Actual behavior

TypeScript is not recognizing that project can be of type Project, giving a type error:

Property 'title' does not exist on type 'never'.

🙂 Expected behavior

I would expect for TypeScript to accept Project as a possible type for the variable.

Additional information about the issue

No response

Activity

  1. fatcerberus commented on Jun 1, 2024

    @fatcerberus

    Variables are narrowed on assignment/initialization. TS knows project is always null here.

  2. JorensM commented on Jun 1, 2024

    @JorensM
    Author

    Bruce Pascoe (@fatcerberus) maybe it does, but not the case in my code. I have some code that fetches project data and assigns the data to the project variable, but I'm still getting this error

  3. JorensM commented on Jun 1, 2024

    @JorensM
    Author

    Here is the full code for the page:

    // Core
    import { redirect } from 'next/navigation';
    
    // Util
    import { createClient } from '@/utils/supabase/server';
    
    // Types
    import { Project } from '@/types/Project';
    
    export default async function ProjectPage( { params }: { params: { id: string }}) {
        const projectID = params.id;
    
        let project: Project | null = null;
        
    
        const supabase = createClient();
    
        const { data: { user } } = await supabase.auth.getUser();
    
        let isOwnProject = true;
    
        
    
        const fetchProjectIfNotNew = async () => {
            if(projectID != 'new') {
                const { data } = await supabase.from('projects').select().eq('id', projectID).single();
                if(data && data.user_id == user?.id) {
                    console.log('is own project')
                    isOwnProject = true;
                    project = data;
                } else {
                    isOwnProject = false;
                }
            }
        }
    
        await fetchProjectIfNotNew();
    
        const handleSubmit = async (formData: FormData) => {
            "use server"
    
            const supabase = createClient();
    
            console.log(formData.get('title'))
    
            if(projectID == 'new') {
                const res = await supabase
                    .from('projects')
                    .insert({
                        title: formData.get('title'),
                        link: formData.get('link'),
                        description: formData.get('description')
                    })
                    .select()
                const newlyCreatedProjectID = res.data![0].id;
                redirect('/project/' + newlyCreatedProjectID);
            } else {
                const res = await supabase
                    .from('projects')
                    .update({
                        title: formData.get('title'),
                        link: formData.get('link'),
                        description: formData.get('description')
                    })
                    .eq('id', projectID)
                    .select();
    
                console.log('updated');
                console.log(res);
    
                redirect('/project/' + projectID)
            }
        }
        return(
            <div className='w-full'>
                { isOwnProject && project ? 
                    <>
                        <h1 className='text-2xl mb-4'>{projectID == 'new' ? 'Create new' : 'Edit'} project</h1>
                        <form action={handleSubmit} className='flex flex-col'>
                            <label>Title</label>
                            <input name='title' id='title' defaultValue={project?.title || ''}></input>
                            <label>Link</label>
                            <input name='link' id='link' defaultValue={project?.link || ''}></input>
                            <label>Description</label>
                            <textarea name='description' id='description' rows={8} defaultValue={project?.description || ''}></textarea>
                            <button className='bg-blue-400 hover:bg-blue-500 w-fit px-6 py-2 rounded-md mt-2 ml-auto'>Save</button>
                        </form>
                    </>
                    
                ? !project : <h1>Could not find project</h1>
                : <h1>You do not have access to this project</h1>}
                
            </div>
        )
    }
    
  4. fatcerberus commented on Jun 1, 2024

    @fatcerberus

    I couldn't follow that whole snippet but basically TS can't follow the control flow there because it involves a lot of callbacks and closures. See #9998. Not sure if this would be fixed by #58729 (but probably not)

    Workaround:

    let project = null as Project | null;
  5. JorensM commented on Jun 1, 2024

    @JorensM
    Author

    Bruce Pascoe (@fatcerberus) Ok, thank you very much!

  6. typescript-bot commented on Jun 10, 2024

    @typescript-bot
    Contributor

    This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  7. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions