T
TanStack•11mo ago
correct-apricot

Converting a custom Link component to use createLink after moving to a monorepo structure

Hello 🙂 I am hoping for some advice as I am in over my head and have become stuck on this: I am converting to a monorepo structure, and I have started encountering TS issues related to it, where I had no issue before. I have a custom Link component, code:
import * as Headless from "@headlessui/react"
import { forwardRef } from "react"
import { Link as RouterLink, LinkProps } from "@tanstack/react-router"
import { router } from "@/router"

type CustomLinkProps = Omit<
React.ComponentPropsWithoutRef<typeof RouterLink>,
"to" | "href" | "params" | "search"
> & {
href: LinkProps<typeof router>["to"]
params?: LinkProps<typeof router>["params"]
search?: LinkProps<typeof router>["search"]
}

export const Link = forwardRef<HTMLAnchorElement, CustomLinkProps>(
function Link({ href, params, search, ...rest }, ref) {
return (
<Headless.DataInteractive>
<RouterLink
to={href}
ref={ref}
params={params}
search={search}
{...rest}
/>
</Headless.DataInteractive>
)
}
)
import * as Headless from "@headlessui/react"
import { forwardRef } from "react"
import { Link as RouterLink, LinkProps } from "@tanstack/react-router"
import { router } from "@/router"

type CustomLinkProps = Omit<
React.ComponentPropsWithoutRef<typeof RouterLink>,
"to" | "href" | "params" | "search"
> & {
href: LinkProps<typeof router>["to"]
params?: LinkProps<typeof router>["params"]
search?: LinkProps<typeof router>["search"]
}

export const Link = forwardRef<HTMLAnchorElement, CustomLinkProps>(
function Link({ href, params, search, ...rest }, ref) {
return (
<Headless.DataInteractive>
<RouterLink
to={href}
ref={ref}
params={params}
search={search}
{...rest}
/>
</Headless.DataInteractive>
)
}
)
This came from tailwind's catalyst UI set of components, and was refactored to work with tanstack router. The current implementation above was perfect before I converted to a monorepo. To be clear, my router is in apps/web because it is not needed anywhere else in the monorepo I now get this TS error on export const Link
The inferred type of 'Link' cannot be named without a reference to '../../../../../node_modules/@tanstack/react-router/dist/esm/link'. This is likely not portable. A type annotation is necessary.ts(2742)
The inferred type of 'Link' cannot be named without a reference to '../../../../../node_modules/@tanstack/react-router/dist/esm/link'. This is likely not portable. A type annotation is necessary.ts(2742)
I have been trying to refactor it to utilise createLink but have been banging my head against a wall trying to get it to work and would really appreciate a dig out Thanks a million
24 Replies
correct-apricot
correct-apricotOP•11mo ago
Move to a monorepo they said, it'll make your life easier they said
equal-jade
equal-jade•11mo ago
what does you tsconfig look like?
correct-apricot
correct-apricotOP•11mo ago
Thanks for the reply @Manuel Schiller 🙂 Here is my project level tsconfig:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
// Module Resolution
"moduleResolution": "bundler",
"module": "ESNext",

// JSX Support
"jsx": "react-jsx",

// Isolated Modules
"isolatedModules": true,

// No Emit
"noEmit": true,

// Type Definitions
"types": ["vite/client", "node"],

// Library
"lib": ["ES2022", "DOM", "DOM.Iterable"],

// Paths and Base URL
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@hooks/*": ["./src/hooks/*"],
"@utils/*": ["./src/utils/*"],
"@shared/schemas/*": ["../../packages/schemas/src/*"],
"@shared/utils/*": ["../../packages/utils/src/*"]
}
},
"include": ["src/**/*", "*.d.ts"],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "../../packages/schemas" },
{ "path": "../../packages/utils" }
]
}
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
// Module Resolution
"moduleResolution": "bundler",
"module": "ESNext",

// JSX Support
"jsx": "react-jsx",

// Isolated Modules
"isolatedModules": true,

// No Emit
"noEmit": true,

// Type Definitions
"types": ["vite/client", "node"],

// Library
"lib": ["ES2022", "DOM", "DOM.Iterable"],

// Paths and Base URL
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@hooks/*": ["./src/hooks/*"],
"@utils/*": ["./src/utils/*"],
"@shared/schemas/*": ["../../packages/schemas/src/*"],
"@shared/utils/*": ["../../packages/utils/src/*"]
}
},
"include": ["src/**/*", "*.d.ts"],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "../../packages/schemas" },
{ "path": "../../packages/utils" }
]
}
and my tsconfig.node.json
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
And my root monorepo tsconfig.base.json
{
"compilerOptions": {
// Target and Module
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020"],
"moduleResolution": "Node",

// Strictness Options
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,

// Code Quality
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,

// Module Import Options
"importHelpers": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,

// Paths and Base URL
"baseUrl": ".",
"paths": {
"@shared/schemas": ["packages/schemas/src"],
"@shared/utils": ["packages/utils/src"]
},

// Build Options
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"composite": true,

// Miscellaneous Options
"resolveJsonModule": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"incremental": true
}
}
{
"compilerOptions": {
// Target and Module
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020"],
"moduleResolution": "Node",

// Strictness Options
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,

// Code Quality
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,

// Module Import Options
"importHelpers": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,

// Paths and Base URL
"baseUrl": ".",
"paths": {
"@shared/schemas": ["packages/schemas/src"],
"@shared/utils": ["packages/utils/src"]
},

// Build Options
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"composite": true,

// Miscellaneous Options
"resolveJsonModule": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"incremental": true
}
}
equal-jade
equal-jade•11mo ago
I think isolatedModules is the problem here? might be off here totally though @Chris Horobin should have a look
equal-aqua
equal-aqua•11mo ago
This usually occurs when ts is configured with declarations. composite or declaration does this and ts cannot reference a type in a declaration because its not exported. Simple explanation could be we havent exported something
equal-jade
equal-jade•11mo ago
@Joseph did you consider using createLink instead of handling types yourself?
correct-apricot
correct-apricotOP•11mo ago
Cheers @Manuel Schiller and @Chris Horobin 🙂 Yes indeed, I actually tried it with isolatedmodules disabled and it didn't change anything And yes in my OP I was trying to change the implemention to use createLink but ended up not getting anywhere with it :/
equal-jade
equal-jade•11mo ago
we should revisit this then because createLink should take away all the type pain from you 😄
correct-apricot
correct-apricotOP•11mo ago
Agreed I am guessing it was just that I didn't implement it correctly
equal-aqua
equal-aqua•11mo ago
Try turning composite off to see if it works And declaration
correct-apricot
correct-apricotOP•11mo ago
Do you mean composite to false in the tsconfig.node.json? It causes an error in the tsconfig.json when I tried that, or do you mean to override the tsconfig.base.json in the project level tsconfig.json? Ok when I added:
"composite": false,
"declaration": false,
"declarationMap": false,
"composite": false,
"declaration": false,
"declarationMap": false,
To the project level tsconfig.json, the errors disappeared Could that have other unintended consequences though?
equal-aqua
equal-aqua•11mo ago
Ah thats likely because youre using project references My point was really that it is these two flags. It means theres a type that is not exported that should be in router library
equal-jade
equal-jade•11mo ago
how can we narrow that type down? is there an analytical way?
equal-aqua
equal-aqua•11mo ago
Reproducer would help But honestly we should just consider exporting all types
correct-apricot
correct-apricotOP•11mo ago
Is there anything I can do to assist? My understanding is that setting these settings to false is masking the underlying issue and will cause problems between projects
equal-aqua
equal-aqua•11mo ago
Yeah, forking an example and a minimal reproduction from it will help a lot
correct-apricot
correct-apricotOP•11mo ago
Okay I will do my best to do that... In the meantime is there an alternative workaround/solution rather than disabling those settings, that you might suggest?
equal-jade
equal-jade•11mo ago
correctly use createLink? 😄 if that does not suffer from the same problem
correct-apricot
correct-apricotOP•11mo ago
Yeah, I think actually that it is also suffering the same problem, I thought I had done something wrong in the implementation, but it is fairly straightforward, so having the same error as before must be related ot the same problem Ok for me to create an example for you, do you want me to fork from my repo, or try to come up with a simple demo repo that has the same problem?
equal-jade
equal-jade•11mo ago
the simpler the better
correct-apricot
correct-apricotOP•10mo ago
Ok, phew. I hope I've done this the right way @Chris Horobin @Manuel Schiller 🙂 https://github.com/josephharkins/tanstack-router-example
GitHub
GitHub - josephharkins/tanstack-router-example
Contribute to josephharkins/tanstack-router-example development by creating an account on GitHub.
correct-apricot
correct-apricotOP•10mo ago
You'll see the error I am getting in apps/web/src/components/common My friend suggested this refactor (changes to the export const Link line)
import * as Headless from "@headlessui/react"
import { forwardRef, ForwardRefExoticComponent, RefAttributes, ComponentPropsWithoutRef } from "react"
import { Link as RouterLink, LinkProps } from "@tanstack/react-router"
import { router } from "@/router"

type CustomLinkProps = Omit<
ComponentPropsWithoutRef<typeof RouterLink>,
"to" | "href" | "params" | "search"
> & {
href: LinkProps<typeof router>["to"]
params?: LinkProps<typeof router>["params"]
search?: LinkProps<typeof router>["search"]
}

export const Link: ForwardRefExoticComponent<CustomLinkProps & RefAttributes<HTMLAnchorElement>> = forwardRef<HTMLAnchorElement, CustomLinkProps>(
function Link({ href, params, search, ...rest }, ref) {
return (
<Headless.DataInteractive>
<RouterLink
to={href}
ref={ref}
params={params}
search={search}
{...rest}
/>
</Headless.DataInteractive>
)
}
)
import * as Headless from "@headlessui/react"
import { forwardRef, ForwardRefExoticComponent, RefAttributes, ComponentPropsWithoutRef } from "react"
import { Link as RouterLink, LinkProps } from "@tanstack/react-router"
import { router } from "@/router"

type CustomLinkProps = Omit<
ComponentPropsWithoutRef<typeof RouterLink>,
"to" | "href" | "params" | "search"
> & {
href: LinkProps<typeof router>["to"]
params?: LinkProps<typeof router>["params"]
search?: LinkProps<typeof router>["search"]
}

export const Link: ForwardRefExoticComponent<CustomLinkProps & RefAttributes<HTMLAnchorElement>> = forwardRef<HTMLAnchorElement, CustomLinkProps>(
function Link({ href, params, search, ...rest }, ref) {
return (
<Headless.DataInteractive>
<RouterLink
to={href}
ref={ref}
params={params}
search={search}
{...rest}
/>
</Headless.DataInteractive>
)
}
)
Thoughts?
equal-jade
equal-jade•10mo ago
can you please create a GitHub issue to properly track this?

Did you find this page helpful?