className is missing for styled component - javascript

I have defined such "Logo" component:
const Logo = ({ className }: { className: string }) => (
<Link to="/" className={className}>
<img src={logo} alt="logo" />
</Link>
);
Which has the open possibility to be styled with passing down className.
But when I'm using it with the typescript like that:
const StyledLogo = styled(Logo)`
margin-left: 4.5rem;
`;
... somewhere in render:
<StyledLogo />
Then I've got such error:
Error:(33, 6) TS2741: Property 'className' is missing in type '{}' but
required in type 'Pick &
Partial>, "className">'.
How to let know typescript that all my styled components will pass down this className property and it will be always there?

It should be possible to resolve this issue by marking the className parameter of Logo as optional by doing the following:
/* Add ? after className */
const Logo = ({ className }: { className?: string }) => (
<Link to="/" className={className}>
<img src={logo} alt="logo" />
</Link>
);
const StyledLogo = styled(Logo)`
margin-left: 4.5rem;
`;
/* No ts error reported now */
<StyledLogo />
Hope that helps!
Update
Alternatively, to avoid the optional className method above you can take this approach:
/* Define type to minimise verbosity */
type LogoProps = { className: string };
/* Define strongly typed Logo function with defined type */
const Logo = (props: LogoProps) => (
<Link to="/" className={props.className}>
<img src={"logo"} alt="logo" />
</Link>
);
/* TS explicitly aware of prop set being passed to Logo via type */
export const StyledLogo = styled((props: LogoProps) => <Logo {...props} />)`
margin-left: 4.5rem;
`;
/* Usage */
<StyledLogo />
This method ensures that TS is aware of the actual prop set being passed to the Logo component.

Related

Problems with unique key prop with "useBreadcrumbs from use-react-router-breadcrumbs"

I'm having problems understanding how to apply key attribute for my breadcrumbs router component. I am getting this error in the console.
Warning: Each child in a list should have a unique "key" prop.
Check the render method of `BreadCrumbsRouterComponent`. See https://reactjs.org/link/warning-keys for more information.
at BreadCrumbsRouterComponent (http://localhost:3000/static/js/bundle.js:710:95)
at span
at div
at BreadCrumbsSection
at CategoriesView
at RenderedRoute (http://localhost:3000/static/js/bundle.js:42813:5)
at Routes (http://localhost:3000/static/js/bundle.js:43235:5)
at Router (http://localhost:3000/static/js/bundle.js:43173:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:41505:5)
at App (http://localhost:3000/static/js/bundle.js:463:83)
This is my breadcrumbs component. I used documentation from these pages:
https://www.npmjs.com/package/use-react-router-breadcrumbs
https://stackblitz.com/edit/github-fiw8uj?file=src%2FApp.tsx
import React from 'react'
import { NavLink } from 'react-router-dom';
import useBreadcrumbs from "use-react-router-breadcrumbs";
const BreadCrumbsRouterComponent = () => {
const breadcrumbs = useBreadcrumbs();
return (
<>
{breadcrumbs.map(({ breadcrumb, match }, index) => (
<>
<NavLink key={match.pathname} to={match.pathname}>
{breadcrumb}
</NavLink>
{index !== breadcrumbs.length - 1 && '\u00a0\u00a0>\u00a0\u00a0'}
</>
))}
</>
)
}
export default BreadCrumbsRouterComponent
This is my breadcrumbs section of which i use in my views.
import React from 'react'
import BreadCrumbsRouterComponent from '../components/BreadCrumbsRouterComponent'
const BreadCrumbsSection = () => {
return (
<div className="breadcrumb-container">
<span className="container">
<BreadCrumbsRouterComponent />
</span>
</div>
)
}
export default BreadCrumbsSection
This is my CSS
.breadcrumb-container {
.container{
display: flex;
margin-top: 1rem;
#include font-16-medium;
#include xxl-min{
max-width: 1110px;
}
.active{
color: $color-grey;
&:last-child{
color: $color-theme;
}
}
}
}
I've tried moving the "key" higher in the hierarchy by adding a new element. It solves the error but then i lose the control in CSS of which i want to use :last-child selector to highlight the current page.
I'm not sure, but you might need to put key into fragment component.
{breadcrumbs.map(({ breadcrumb, match }, index) => (
<React.Fragment key={match.pathname}> // here
<NavLink to={match.pathname}>
{breadcrumb}
</NavLink>
{index !== breadcrumbs.length - 1 && '\u00a0\u00a0>\u00a0\u00a0'}
<React.Fragment />
))}

Is it possible to dynamically render elements with props using TypeScript?

I've been digging around SO and the web at large for a solution, but I can't seem to nail it.
I have two components, Link and Button. Long story short: they are wrappers for <a> and <button> elements, but with the added options such as chevrons on the right-side, icons on the left-side, full-width mode, etc.
Here is what I have so far (and here's the code running on typescriptlang.org/play):
type Variant = "primary" | "secondary" | "tertiary";
interface CommonProps {
variant?: Variant;
showChevron?: boolean;
icon?: IconDefinition;
fullWidth?: boolean;
small?: boolean;
}
interface LinkOnlyProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
href: string;
}
interface ButtonOnlyProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick: React.MouseEventHandler<HTMLButtonElement>;
}
export type LinkProps = CommonProps & LinkOnlyProps;
export type ButtonProps = CommonProps & ButtonOnlyProps;
export const Link = (props: LinkProps) => {
const {
children,
showChevron,
icon,
fullWidth,
variant,
small,
className,
...rest
} = props;
const { classes } = initButton(props);
return (
<a className={classes} {...rest}>
<Content {...props} />
</a>
);
};
export const Button = (props: ButtonProps) => {
const {
children,
showChevron,
icon,
fullWidth,
variant,
small,
className,
...rest
} = props;
const { classes } = initButton(props);
return (
<button className={classes} {...rest}>
<Content {...props} />
</button>
);
};
I've tried extracting the common logic for the Link and Button components into a single Component, however when I spread the ...rest props I get TypeScript yelling at me. From the error, it seems because I haven't been able to account for the possibility of <a> props being spread on to a <button> element and vice-versa.
I wanted to keep Link and Button as separate components, rather than specifying the type as a prop, so that the intentionality of the developer is clear when the components are being implemented.
Is there any possibility of extracting that common logic into a central component that both Link and Button can simply act as wrappers for? For example:
export const Link = (props: LinkProps) => {
return <Component element="a" {...props} />;
}
export const Button = (props: ButtonProps) => {
return <Component element="button" {...props} />;
}
Was able to work around the type assertion using as any when spreading the rest of my props:
return (
<Element className={classes} {...(rest as any)}>
<Content {...props} />
</Element>
);

Type error on a React component created with forwardRef

I'm creating an element(Card component) using forwardRef but types mismatch when using it.
Here is the code of Card component
export const Card = React.forwardRef((
{ fluidHeight, className, ...props }:
{ fluidHeight?:boolean, className?:string, props?:any }, ref?:any) => (
<div
{...props}
ref={ref}
className={clsx(
"card card-custom gutter-b",
{ "card-height-fluid": fluidHeight },
className
)}
/>
));
Here is the code using Card component
<Card>
<CardHeader title="List">
<CardHeaderToolbar>
<button
type="button"
className="btn btn-primary"
onClick={UIProps.addButtonClick}
>
Add New
</button>
</CardHeaderToolbar>
</CardHeader>
<CardBody>
<DevicesFilter />
{UIProps.ids.length > 0 && <Grouping />}
<DevicesTable />
</CardBody>
</Card>
In the above snippet using Card component, compiler show type error on and hoving over it show following error.
Type '{ children: Element[]; }' has no properties in common with type 'IntrinsicAttributes & { fluidHeight?: boolean; className?: string; props?: any; } & RefAttributes'
I know it's a problem of forwardRef in typescript but don't know how to fix it.
Edit
I found a hack like this
Card component
export const Card = React.forwardRef((
{ children, fluidHeight, className, ...props }:
{children:JSX.Element[], fluidHeight?:boolean, className?:string, props?:any }, ref?:any) => (
<div
{...props}
ref={ref}
className={clsx(
"card card-custom gutter-b",
{ "card-height-fluid": fluidHeight },
className
)}
>
{/* {children} in case of forwardRef children elements are automatically passed from calling
component and should be used like this */}
</div>
));
But as per my assumption in this case when I don't need to pass any children elements for rendering inside Card component, I should use something else than forwardRef but don't know what :)
Please Help! Thanks
For children type, you shouldn't use JSX.Element or JSX.Element[]. Usually the convention is to use React.ReactNode. It includes the 2 above types and all other valid types for children like string or null.
So I'd change the props definition to:
type YourProps = { fluidHeight?:boolean, className?:string, props?:any };
type CardProps = React.PropsWithChildren<YourProps>;
export const Card = React.forwardRef((
{ fluidHeight, className, ...props }: CardProps, ref?:any) => (
<div
{...props}
ref={ref}
className={clsx(
"card card-custom gutter-b",
{ "card-height-fluid": fluidHeight },
className
)}
/>
));
where React.PropsWithChildren<T> is a utility type that includes children as React.ReactNode type. Here is the definition
type PropsWithChildren<P> = P & { children?: ReactNode };

How to Change a css property based on a state of another component

I'm building a web page with gatsby which is based in react, and I need my nav component changes his sticky position to relative or auto, every time that I open the modal of the gallery component..but I don't know how to approach and solve the problem. The nav component belongs to the layout component which is Gallery's parent component...Here are the components involved:
nav component:
import React, { Component } from 'react'
import { Location } from '#reach/router'
import { Link } from 'gatsby'
import { Menu, X } from 'react-feather'
import Logo from './Logo'
import './Nav.css'
export class Navigation extends Component {
state = {
active: false,
activeSubNav: false,
currentPath: false
}
componentDidMount = () =>
this.setState({ currentPath: this.props.location.pathname })
handleMenuToggle = () => this.setState({ active: !this.state.active })
// Only close nav if it is open
handleLinkClick = () => this.state.active && this.handleMenuToggle()
toggleSubNav = subNav =>
this.setState({
activeSubNav: this.state.activeSubNav === subNav ? false : subNav
})
render() {
const { active } = this.state,
{ subNav } = this.props,
NavLink = ({ to, className, children, ...props }) => (
<Link
to={to}
className={`NavLink ${
to === this.state.currentPath ? 'active' : ''
} ${className}`}
onClick={this.handleLinkClick}
{...props}
>
{children}
</Link>
)
return (
<nav className={`Nav ${active ? 'Nav-active' : ''}`}>
<div className="Nav--Container container">
<Link to="/" onClick={this.handleLinkClick}>
<div style={{ width: `40px`, margin:`0 20px`}}>
<Logo />
</div>
</Link>
<div className="Nav--Links">
<NavLink to="/">Home</NavLink>
<NavLink to="/contact/">Contacto</NavLink>
<div className={`Nav--Group ${this.state.activeSubNav === 'about' ? 'active' : '' }`} >
<span className={`NavLink Nav--GroupParent ${
this.props.location.pathname.includes('about') ||
this.props.location.pathname.includes('team') ||
this.props.location.pathname.includes('news')
? 'active'
: ''
}`}
onClick={() => this.toggleSubNav('about')}
>
Nosotros
</span>
<div className="Nav--GroupLinks">
{subNav.map( (link, index)=> (
<NavLink
to={link.link}
key={'posts-subnav-link-' + index}
className="Nav--GroupLink">{link.name}</NavLink>
))}
</div>
</div>
</div>
<button
className="Button-blank Nav--MenuButton"
onClick={this.handleMenuToggle}
>
{active ? <X /> : <Menu />}
</button>
</div>
</nav>
)
}
}
export default ({ subNav }) => (
<Location>{route => <Navigation subNav={subNav} {...route} />}</Location>
)
the default position property is set to sticky in the nav.css file I want remove that and change it
dynamically depending of the modal gallery state, open or close.
this is my gallery component:
import React, { useState, useCallback } from "react";
import Gallery from "react-photo-gallery";
import Carousel, { Modal, ModalGateway } from "react-images";
const PhotoGallery = ({photos}) => {
const [currentImage, setCurrentImage] = useState(0);
const [viewerIsOpen, setViewerIsOpen] = useState(false);
const openLightbox = useCallback((event, { photo, index }) => {
setCurrentImage(index);
setViewerIsOpen(true);
}, []);
const closeLightbox = () => {
setCurrentImage(0);
setViewerIsOpen(false);
};
return(
<div>
<Gallery photos={photos} onClick={openLightbox} />
<ModalGateway>
{viewerIsOpen ? (
<Modal onClose={closeLightbox}>
<Carousel
currentIndex={currentImage}
views={photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
)
}
export default PhotoGallery
the problem is that when the modal is open the nav still sticky and does not allow me access to the modal controls, like close and expand...and I need to change that.
There are a few approaches to this.
Old school classname toggling
Pass a prop down to the child component that reflects the state. On the child, use that prop to conditionally render one or more classes that represent the desired presentation.
Assign styles via style prop
This is similar to #1, but eliminates a layer of abstraction. Instead of assembling a class list you just assemble the CSS styles you'd like to apply as an object.
const Component = ({ someState }) =>
<div style={someState ? { border: "5px solid red" } : { color: "#999" }}>
Some Text
</div>
Use a CSS-in-JS library
The downside of the above approach is that you wind up duplicating styles for each instance of your element on the page. CSS-in-JS libraries solve this by extracting your styles into an automatically generated class and applying the class to your component instead. I prefer Emotion, but there are others.
Using Emotion you're able to accept a className prop from the parent that override the defaults set by the child. This inversion-of-control is really powerful and solves many of the shortcomings with early CSS-in-JS approaches.
const ParentComponent = () => {
const [someState] = useState(false)
return <ChildComponent css={{ color: someState ? "blue" : "red" }} />
}
const ChildComponent = ({ className }) =>
<div
css={{
color: "#000",
border: "4px solid currentColor"
}}
className={className}
>
Some Text
</div>
In the above example, className is assigned by Emotion using the generated class name assigned based on the css prop passed to ChildComponent inside of ParentComponent. The result of this would be a div with a blue border and blue text when someState is false (default). When someState is switched to true, the border and text will be red. This is because the styles passed in via className will override the styles assigned directly via css in Emotion.

Applying a theme/style to all html/react tags with a selector within a component

I'll try to give a minimal example for what I'm trying to achieve, I am using Material UI Styles to style my components, I wanted to style all the <Link> tags in my component, so for example:
const useStyles = makeStyles(theme => ({
menuLink: theme.styles.menuLinkStyle,
}));
function DrawerContents() {
const classes = useStyles();
// noinspection JSUnresolvedVariable
return (
<div>
<div className={classes.toolbar}>
<Link to='/' className={classes.menuLink}> {/*<==== This*/}
Hello
</Link>
</div>
<Divider/>
<List>
<Link to={'/users'} className={classes.menuLink}> {/*<==== This*/}
World
</Link>
</List>
</div>
);
}
export default DrawerContents;
As you can see, I need to manually give the className={classes.menuLink} to each and every <Link> tag, I was wondering if there is a possibility to assign the classes.menuLink to every <Link> tag by default, something like this:
const useStyles = makeStyles(theme => ({
Link: theme.styles.menuLinkStyle,
}));
Without the need to write className={classes.menuLink} for each <Link> tag in my component.
Is there such a thing?
The way I would handle this is to create a new component that takes care of the styling:
const useStyles = makeStyles(theme => ({
menuLink: theme.styles.menuLinkStyle,
}));
function MenuLink(props) {
const classes = useStyles(props);
return <Link {...props} className={classes.menuLink}/>;
}
Then import this component and use it instead of Link.

Categories