-
-
Save kachar/028b6994eb6b160e2475c1bb03e33e6a to your computer and use it in GitHub Desktop.
import { LinkProps, Link as MuiLink } from '@mui/material' | |
import NextLink from 'next/link' | |
export default function Link(props: LinkProps<'a'>) { | |
return <MuiLink component={NextLink} {...props} /> | |
} |
// Plain JS version + prop-types | |
// Thanks to @IvanAdmaers | |
import PropTypes from 'prop-types'; | |
import { forwardRef } from 'react'; | |
import NextLink from 'next/link'; | |
import { Link as MuiLink } from '@material-ui/core'; | |
const Link = forwardRef(({ href, as, prefetch, ...props }, ref) => { | |
return ( | |
<NextLink href={href} as={as} prefetch={prefetch} passHref> | |
<MuiLink ref={ref} {...props} /> | |
</NextLink> | |
); | |
}); | |
Link.displayName = 'Link'; | |
Link.defaultProps = { | |
href: '#', | |
prefetch: false, | |
}; | |
Link.propTypes = { | |
href: PropTypes.string, | |
as: PropTypes.string, | |
prefetch: PropTypes.bool, | |
}; | |
export default Link; |
// MaterialUI v4 | |
import React, { forwardRef, Ref } from 'react' | |
import Link, { LinkProps } from 'next/link' | |
import { Link as MuiLink, LinkProps as MuiLinkProps } from '@material-ui/core' | |
type LinkRef = HTMLAnchorElement | |
type NextLinkProps = Omit<MuiLinkProps, 'href' | 'classes'> & | |
Pick<LinkProps, 'href' | 'as' | 'prefetch'> | |
const NextLink = ({ href, as, prefetch, ...props }: LinkProps, ref: Ref<LinkRef>) => ( | |
<Link href={href} as={as} prefetch={prefetch} passHref> | |
<MuiLink ref={ref} {...props} /> | |
</Link> | |
) | |
export default forwardRef<LinkRef, NextLinkProps>(NextLink) |
// MaterialUI v5 | |
// Thanks to @bryantobing12 | |
import React from "react"; | |
import { Link as LinkMUI, LinkProps as LinkMUIProps } from "@mui/material"; | |
import NextLink, { LinkProps as NextLinkProps } from "next/link"; | |
export type LinkProps = Omit<LinkMUIProps, "href" | "classes"> & | |
Pick<NextLinkProps, "href" | "as" | "prefetch">; | |
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>( | |
({ href, as, prefetch, ...props }, ref) => ( | |
<NextLink href={href} as={as} prefetch={prefetch} passHref> | |
<LinkMUI ref={ref} {...props} /> | |
</NextLink> | |
) | |
); |
// @source https://github.com/mui/material-ui/tree/master/examples/nextjs-with-typescript | |
// Updated in this gist by @alecriarstudio | |
import * as React from 'react'; | |
import clsx from 'clsx'; | |
import { useRouter } from 'next/router'; | |
import NextLink, { LinkProps as NextLinkProps } from 'next/link'; | |
import MuiLink, { LinkProps as MuiLinkProps } from '@mui/material/Link'; | |
import { styled } from '@mui/material/styles'; | |
// Add support for the sx prop for consistency with the other branches. | |
const Anchor = styled('a')({}); | |
type NextLinkComposedProps = { | |
to: NextLinkProps["href"]; | |
linkAs?: NextLinkProps["as"]; | |
} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href"> & | |
Omit<NextLinkProps, "href" | "as">; | |
export const NextLinkComposed = React.forwardRef<HTMLAnchorElement, NextLinkComposedProps>( | |
function NextLinkComposed(props, ref) { | |
const { to, linkAs, replace, scroll, shallow, prefetch, locale, ...other } = props; | |
return ( | |
<NextLink | |
href={to} | |
prefetch={prefetch} | |
as={linkAs} | |
replace={replace} | |
scroll={scroll} | |
shallow={shallow} | |
passHref | |
locale={locale} | |
> | |
<Anchor ref={ref} {...other} /> | |
</NextLink> | |
); | |
}, | |
); | |
export type LinkProps = { | |
activeClassName?: string; | |
as?: NextLinkProps['as']; | |
href: NextLinkProps['href']; | |
linkAs?: NextLinkProps['as']; // Useful when the as prop is shallow by styled(). | |
noLinkStyle?: boolean; | |
} & Omit<NextLinkComposedProps, 'to' | 'linkAs' | 'href'> & | |
Omit<MuiLinkProps, 'href'>; | |
// A styled version of the Next.js Link component: | |
// https://nextjs.org/docs/api-reference/next/link | |
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link(props, ref) { | |
const { | |
activeClassName = 'active', | |
as, | |
className: classNameProps, | |
href, | |
linkAs: linkAsProp, | |
locale, | |
noLinkStyle, | |
prefetch, | |
replace, | |
role, // Link don't have roles. | |
scroll, | |
shallow, | |
...other | |
} = props; | |
const router = useRouter(); | |
const pathname = typeof href === 'string' ? href : href.pathname; | |
const className = clsx(classNameProps, { | |
[activeClassName]: router.pathname === pathname && activeClassName, | |
}); | |
const isExternal = | |
typeof href === 'string' && (href.indexOf('http') === 0 || href.indexOf('mailto:') === 0); | |
if (isExternal) { | |
if (noLinkStyle) { | |
return <Anchor className={className} href={href} ref={ref} {...other} />; | |
} | |
return <MuiLink className={className} href={href} ref={ref} {...other} />; | |
} | |
const linkAs = linkAsProp || as; | |
const nextjsProps = { to: href, linkAs, replace, scroll, shallow, prefetch, locale }; | |
if (noLinkStyle) { | |
return <NextLinkComposed className={className} ref={ref} {...nextjsProps} {...other} />; | |
} | |
return ( | |
<MuiLink | |
component={NextLinkComposed} | |
className={className} | |
ref={ref} | |
{...nextjsProps} | |
{...other} | |
/> | |
); | |
}); | |
export default Link; |
@ivanzotov I've just updated to the latest version of the snippet I use.
The difference is in the type of LinkRef
from any
to more specific HTMLAnchorElement
Also added a version for next.js link + mui button
@kachar Thank you so much, you've saved my day )
@kachar https://gist.github.com/kachar/028b6994eb6b160e2475c1bb03e33e6a#file-linkbutton-tsx-L5
Probably should be changed to HTMLButtonElement
@ivanzotov Thanks! I've updated it
Actually it seems this wasn't a problem locally because we always apply <LinkButton component="a" />
which makes the underlying component a link and it makes sense to passHref
only to a link, not a button.
You won a BIG Start!
wonderful ~!
The same without typescript.
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
import NextLink from 'next/link';
import { Link as MuiLink } from '@material-ui/core';
const Link = forwardRef(({ href, as, prefetch, ...props }, ref) => {
return (
<NextLink href={href} as={as} prefetch={prefetch} passHref>
<MuiLink ref={ref} {...props} />
</NextLink>
);
});
Link.displayName = 'Link';
Link.defaultProps = {
href: '#',
prefetch: false,
};
Link.propTypes = {
href: PropTypes.string,
as: PropTypes.string,
prefetch: PropTypes.bool,
};
export default Link;
LinkButton Plain Js
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
import NextLink from 'next/link';
import { Button as MuiButton } from '@material-ui/core';
const Button = forwardRef(({ href, as, prefetch, locale, ...props }, ref) => {
return (
<NextLink href={href} as={as} prefetch={prefetch} locale={locale} passHref>
<MuiButton buttonRef={ref} {...props} />
</NextLink>
);
});
Button.displayName = 'Button';
Button.defaultProps = {
href: '#',
prefetch: false,
};
Button.propTypes = {
href: PropTypes.string,
locale: PropTypes.string,
as: PropTypes.string,
prefetch: PropTypes.bool,
};
export default Button;
Mui v5
Example with ts
to support passing LinkMuiProps like variant
color
etc
import React from "react";
import { Link as LinkMUI, LinkProps as LinkMUIProps } from "@mui/material";
import NextLink, { LinkProps as NextLinkProps } from "next/link";
export type LinkProps = Omit<LinkMUIProps, "href" | "classes"> &
Pick<NextLinkProps, "href" | "as" | "prefetch">;
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
({ href, as, prefetch, ...props }, ref) => (
<NextLink href={href} as={as} prefetch={prefetch} passHref>
<LinkMUI ref={ref} {...props} />
</NextLink>
)
);
Thanks @bryantobing12
Added your v5 contribution to the gist
There is a cool example of usage NextJS Link and MUI 5 Link components together in the official MUI repository - https://github.com/mui/material-ui/blob/master/examples/nextjs/src/Link.js
OfficialLinkMuiv5.tsx
interface NextLinkComposedProps
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>,
Omit<NextLinkProps, 'href' | 'as'> {
to: NextLinkProps['href'];
linkAs?: NextLinkProps['as'];
}
The following 2 errors was displayed at interface NextLinkComposedProps on VSCode
Interface 'NextLinkComposedProps' cannot simultaneously extend types 'Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">' and 'Omit<InternalLinkProps, "href" | "as">'.
Named property 'onClick' of types 'Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">' and 'Omit<InternalLinkProps, "href" | "as">' are not identical. ts(2320)
Interface 'NextLinkComposedProps' cannot simultaneously extend types 'Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">' and 'Omit<InternalLinkProps, "href" | "as">'.
Named property 'onMouseEnter' of types 'Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href">' and 'Omit<InternalLinkProps, "href" | "as">' are not identical. ts(2320)
The following are candidates for correction
type NextLinkComposedProps = {
to: NextLinkProps["href"];
linkAs?: NextLinkProps["as"];
} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href"> &
Omit<NextLinkProps, "href" | "as">;
Thanks for the suggestion @alecriarstudio
I've updated this gist for anyone, but the official version is at https://github.com/mui/material-ui/tree/master/examples/nextjs-with-typescript
You might send a PR to the MUI repo to adapt your suggestion as well.
With the update brought by Next13 we're able to simplify the next/link
+ mui link/button process a lot:
import { Button, ButtonProps } from '@mui/material'
import NextLink from 'next/link'
export default function LinkButton(props: ButtonProps<'a'>) {
return <Button component={NextLink} {...props} />
}
import { LinkProps, Link as MuiLink } from '@mui/material'
import NextLink from 'next/link'
export default function Link(props: LinkProps<'a'>) {
return <MuiLink component={NextLink} {...props} />
}
Read more at https://stackoverflow.com/a/74419666/668245
Thanks @kachar
Latest approach is indeed simple but we are not able to pass href as an object. It only accepts string
type. Given that NextLink accepts a URL object as href, how can we get same behaviour here?
@vighnesh153 I've tried to make it work with LinkProps['href']
but the MuiButton expectation is a hardcoded { href: string }
so I don't think it's currently possible:
// node_modules/@mui/material/Button/Button.d.ts
export type ExtendButton<M extends OverridableTypeMap> = ((
props: { href: string } & OverrideProps<ExtendButtonBaseTypeMap<M>, 'a'>,
) => JSX.Element) &
OverridableComponent<ExtendButtonBaseTypeMap<M>>;
What I end up doing in multiple projects is having a general routes.ts
file that contains all the app links as follows:
export const routes = {
post: {
byId: (id: string) => `/post/${id}`,
}
}
and use it as:
<LinkButton color="secondary" variant="contained" href={routes.post.byId(query.id)}>
View post
</LinkButton>
Thanks!