Passing both props.children and a component as prop in React/Nextjs - reactjs

User.tsx is a kind of navigation bar that passes all children components through via {props.children}. I also want to pass a breadcrumb component through to it as well but I'm getting an error that says:
JSX element type 'props.component' does not have any construct or call
signatures
Layout.tsx:
import UserShell from "./account/User
import Breadcrumb from "./navigation/Breadcrumb";
<User component={Breadcrumb}>{props.children}</User>;
User.tsx:
type ShellProps = {
children: React.ReactNode | React.ReactNode[];
component?: React.ComponentType;
export default function UserShell(props: ShellProps) {
return (
<>
<props.component />
^^^^^^^^^^^^^^^^^^//error is here
<div>{props.children}</div>
</>
}
Any clue on how to fix this?

import React from 'react'
type ShellProps = {
children: React.ReactNode | React.ReactNode[];
component?: React.ComponentType;
}
function UserShell(props: ShellProps) {
return (
<>
{props.component ? <props.component /> : null}
<div>{props.children}</div>
</>
}
playground
you probably need to capitalize the component (Component)

try updating to this
Layout.tsx:
import UserShell from "./account/User
import Breadcrumb from "./navigation/Breadcrumb";
<User Breadcrumb={Breadcrumb}>{props.children}</User>; //use Breadcrumb or Component (capital C)
User.tsx
type ShellProps = {
children: React.ReactNode | React.ReactNode[];
Breadcrumb?: React.ElementType; //covers all elements
export default function UserShell({Breadcrumb, children}) {
return (
<>
<Breadcrumb/>
<div>{children}</div>
</>
}

Related

React Context Provider override not working

The Goal
Hello ๐Ÿ‘‹๐Ÿป
I have a React project with NextJS where I want to chose whether using light or dark theme depending on the background / section.
My solution
I have found that the best solution to my problem is using React Context API. To change the theme I just need to override the provider with a new one (as indicated in the documentation).
The problem
I have the start of a solution but when overriding the Provider, I noticed that the change isn't effective for every components even if in the devTools the context in theses components is the correct one. ๐Ÿ˜•
I have tried switching from useContext(context) to <context.Consumer> but there was no improvement.
The code:
/context/Theme
import { createContext, JSXElementConstructor, ReactElement, ReactFragment, ReactNode, ReactPortal } from "react";
import { getTheme, Theme } from "../constants/theme";
// type of getTheme: (theme: 'light' | 'dark') => Theme
const ThemeContext = createContext<Theme>(getTheme('light'))
export default ThemeContext
interface ThemeProviderProps {
theme: 'light' | 'dark'
children: string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | ReactPortal | null | undefined
}
interface ThemeConsumerProps {
children: (value: Theme) => ReactNode
}
// The goal here is to make the theme switching more natural
export const ThemeProvider = (props: ThemeProviderProps): JSX.Element => {
return (
<ThemeContext.Provider value={getTheme(props.theme)}>
{props.children}
</ThemeContext.Provider>
)
}
// Just for convenience
export const ThemeConsumer = (props: ThemeConsumerProps): JSX.Element => {
return <ThemeContext.Consumer>{props.children}</ThemeContext.Consumer>
}
/pages/_app (Starting point of NextJS App, equivalent to /src/App
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { ThemeProvider } from "../context/Theme";
export default function App({ Component, pageProps }: AppProps) {
return (
<ThemeProvider theme="light">
<Component {...pageProps} />
</ThemeProvider>
)
}
/common/Popup/Popup
import {ReactNode, useContext, useState} from "react";
import styles from "./Popup.module.css"
import Typography, {Typos} from "../Typography";
import Icon from "../Icon";
import {CloseIcon} from "../../icons";
import ThemeContext, {ThemeProvider, ThemeConsumer} from "../../context/Theme";
interface Props {
children: ReactNode
title: string
open: boolean
close: () => void
}
export default function (props: Props): JSX.Element {
return (
<>
// ...
<ThemeProvider theme="dark">
<div className={styles.header}>
<Typography level={Typos.TITLE_2} bold>{ props.title }</Typography> // The useContext works inside of this components
<ThemeConsumer>
{(theme) =>
<Icon icon={CloseIcon} color={theme.texts.secondary} size={1.5}
onClick={props.close} clickable />
}
</ThemeConsumer>
</div>
<div className={styles.body}>
{ props.children } // The useContext or the <context.Consumer> doesn't work for the children
</div>
</ThemeProvider>
// ...
</>
)
}
I've just misplaced my <context.Consumer> outside of the Popup component and I learned that useContext doesn't work when overriding the Provider.

How to restrict props.children in react typescript?

import React from 'react';
import './App.css';
import Message from "./components/Message";
function App() {
return (
<div className="App">
<Message>
<p>Hello World</p>
</Message>
<Message>
<a>Hello World</a>
</Message>
<Message>
<button>Hello World</button>
</Message>
</div>
);
}
export default App;
import React, {FunctionComponent} from 'react';
interface OwnProps {
children: React.ReactElement<HTMLParagraphElement>;
}
type Props = OwnProps;
const Message: FunctionComponent<Props> = ({children}) => {
return (
<h1>
{children}
</h1>
);
};
export default Message;
The above code I can pass any HTMLElementTag as props.
Is there any way to restrict ReactElement to only specific element ?
For example only with p tag or button tag etc.
You can render specific children with React.Children by checking its type.
Below example only renders p tag elements.
import React, { Children } from "react";
interface Props {
children: React.ReactElement<HTMLParagraphElement | HTMLHeadElement>[] | React.ReactElement<HTMLParagraphElement | HTMLHeadElement>;
}
const Component: React.FC<Props> = ({ children }) => {
const elements = Children.map(children, (child) => {
if ((child as React.ReactElement).type === "p") {
return child;
}
return null;
})?.filter(Boolean);
return <div>{elements}</div>;
};
export default Component;
Note that type of custom component is a function, not string.
And if you want to check types of nested html elements, you need to recursively check types of react elements children.
I do not know what you are trying to do here, but there may be a better approach than restricting render of children elements.
I think this is the wrong way to be going about implementing HTMl here: if you want a <h1> element to render then you shouldn't be passing <p> or <button> elements into it.
Why not just leave the functional component input here as just a string? Like so:
import React from 'react';
interface MessageProps {
messageText: string;
}
const Message: React.FC<MessageProps> = ({ messageText }) => {
return (
<h1>
{messageText}
</h1>
);
};
export default Message;
//////
import React from 'react';
import Message from "./components/Message";
function App() {
return (
<div className="App">
<Message messageText="Hello world" />
</div>
);
}
export default App;
you can use following for static checking:
type Props = { children: React.ReactNode } //-- allow any React valid Node
// type Props = { children: never } //-- will not allow children to be passed

Typescript error with NextJS and Context API

I have the following code in my _app.tsx file:
import React from 'react'
import type { AppProps } from 'next/app'
/* Import Styles */
import '#themes/index.scss'
/* Import Template */
import WithAuth from '#templates/with-auth'
// This default export is required in a new `pages/_app.js` file.
export const App = ({ Component, pageProps }: {Component: unknown, pageProps: unknown}): AppProps => {
return (
<WithAuth>
<Component {...pageProps} />
</WithAuth>
)
}
export default App
I got the AppProps usage from the nextjs documentation, however I have wrapped the component with a WithAuth component which is basically a template with a MaterialUI ThemeProvider and a react context provider:
return <div className={styles['grid-page']}>
<ThemeProvider theme={theme}>
<GlobalContext.Provider value={globalProps}>
<HeaderBar />
<main>
{ children }
</main>
</GlobalContext.Provider>
</ThemeProvider>
</div>
}
When I do this, I get the typescript error:
pages/_app.tsx:12:3 - error TS2322: Type 'Element' is not assignable to type 'AppPropsType<Router, {}>'.
Property 'pageProps' is missing in type 'Element' but required in type 'AppInitialProps'.
12 return (
~~~~~~~~
13 <WithAuth>
~~~~~~~~~~~~~~
...
15 </WithAuth>
~~~~~~~~~~~~~~~
16 )
~~~
node_modules/next/dist/next-server/lib/utils.d.ts:124:5
124 pageProps: any;
~~~~~~~~~
'pageProps' is declared here.
pages/_app.tsx:14:8 - error TS2604: JSX element type 'Component' does not have any construct or call signatures.
14 <Component {...pageProps} />
I'm struggling to understand the correct types to add here
AppProps should be de-structured.
import { AppProps } from 'next/app'
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<>
<Header />
<Component {...pageProps} />
</>
)
}

Error when calling theme Props in component using ReactJs and Typescript

I need to pass the color of a theme to a component, I'm using type script (I'm a beginner).
I came across an error that I can't solve.
The error is: Parameter 'props' implicitly has an 'any' type.ts(7006)
and it appears in the color line of my code
<Linkxx
color={(props) => props.theme.colors.red}
size={11}
weight={500}
decoration={false}
/>
My component
import React from 'react';
import { Container } from './styles';
interface LinkProps {
color: string;
size?: number;
weight?: number;
decoration?: boolean;
}
const Link: React.FC<LinkProps> = ({ color, size, weight, decoration }) => {
return (
<Container
decoration={decoration}
weight={weight}
size={size}
color={color}
to="/teste"
>
teste
</Container>
);
};
export default Link;
The code where the problem occurs
import React from 'react';
import Header from '../../components/Header';
import Linkxx from '../../components/Link';
import {
Container,
Content,
UserCard,
Avatar,
UserData,
Canais,
BoxAction,
} from './styles';
const User: React.FC = () => {
return (
<>
<Header />
<Container>
<Content>
<h1>User</h1>
<UserCard>
<Avatar />
<UserData>
<span>Sample Name</span>
<small>sample#gmail.com</small>
</UserData>
<Canais>
<span>Adm, Manager</span>
<div>
<small>test</small>
<small>test</small>
<small>test</small>
</div>
</Canais>
<BoxAction>
<div>
<Linkxx
color={(props) => props.theme.colors.red}
size={11}
weight={500}
decoration={false}
/>
</div>
</BoxAction>
</UserCard>
</Content>
</Container>
</>
);
};
export default User;
How can I use the theme properties when calling my component?
Try this:
<Linkxx
// color={(props) => props.theme.colors.red} // You passed a function
color={props.theme.colors.red} // Try passing a value or string i.e. props.theme.colors.red
size={11}
weight={500}
decoration={false}
/>
As your interface says color is string:
interface LinkProps {
color: string; // color is string
size?: number;
weight?: number;
decoration?: boolean;
}
Try adding the type 'any' to your 'props' parameter as such:
color={(props: any) => props.theme.colors.red}
size={11}
weight={500}
decoration={false}
Because in typescript you need to specify the type of props you are going to send or it takes the default type defined. if you don't want to specify any type then explicitly ask the component to expect state and props of โ€˜anyโ€™ type. Refer to this article for detailed solution: React/typescript: Parameter โ€˜propsโ€™ implicitly has an โ€˜anyโ€™ type error

Infer type of props based on component passed also as prop

Is it possible to infer correct types for props from an unknown component passed also as a prop?
In case of known component (that exists in current file) I can get props:
type ButtonProps = React.ComponentProps<typeof Button>;
But if I want to create a generic component Box that accepts a component in as prop and the component's props in props prop. The component can add some default props, have some behavior, it doesn't matter. Basically its similar to higher-order components, but its dynamic.
import React from "react";
export interface BoxProps<TComponent> {
as?: TComponent;
props?: SomehowInfer<TComponent>; // is it possible?
}
export function Box({ as: Component, props }: BoxProps) {
// Note: it doesn't have to be typed within the Box (I can pass anything, I can control it)
return <Component className="box" title="This is Box!" {...props} />;
}
function MyButton(props: {onClick: () => void}) {
return <button className="my-button" {...props} />;
}
// usage:
function Example() {
// I want here the props to be typed based on what I pass to as. Without using typeof or explicitly passing the generic type.
return (
<div>
<Box
as={MyButton}
props={{
onClick: () => {
console.log("clicked");
}
}}
>
Click me.
</Box>
</div>
);
}
requirements:
must work without passing the generic type (is it possible?), because it would be used almost everywhere
must work with user-defined components (React.ComponentType<Props>)
would be great if it worked also with react html elements (a, button, link, ...they have different props), but not necessary
You can use the predefined react type ComponentProps to extract the prop types from a component type.
import React from "react";
export type BoxProps<TComponent extends React.ComponentType<any>> = {
as: TComponent;
props: React.ComponentProps<TComponent>;
}
export function Box<TComponent extends React.ComponentType<any>>({ as: Component, props }: BoxProps<TComponent>) {
return <div className="box" title="This is Box!">
<Component {...props} />;
</div>
}
function MyButton(props: {onClick: () => void}) {
return <button className="my-button" {...props} />;
}
// usage:
function Example() {
// I want here the props to be typed based on what I pass to as. Without using typeof or explicitly passing the generic type.
return (
<div>
<Box
as={MyButton}
props={{ onClick: () => { } }}
></Box>
</div>
);
}
Playground Link
Depending on you exact use case the solution might vary, but the basic idea is similar. You could for example turn the type around a little bit and take in the props as the type parameter for the BoxProps. That way you can constrain the component props to have some specific properties you can supply inside the Box component:
export type BoxProps<TProps extends {title: string}> = {
as: React.ComponentType<TProps>;
} & {
props: Omit<TProps, 'title'>;
}
export function Box<TProps extends {title: string}>({ as: Component, props }: BoxProps<TProps>) {
return <div className="box" title="This is Box!">
<Component title="Title from box" {...props as TProps} />;
</div>
}
Playground Link
If you want to take in intrinsic tags, you can also add keyof JSX.IntrinsicElements to the TComponent constraint:
export type BoxProps<TComponent extends React.ComponentType<any> | keyof JSX.IntrinsicElements> = {
as: TComponent;
props: React.ComponentProps<TComponent>;
}
export function Box<TComponent extends React.ComponentType<any>| keyof JSX.IntrinsicElements>({ as: Component, props }: BoxProps<TComponent>) {
return <div className="box" title="This is Box!">
<Component {...props} />;
</div>
}
Playground Link

Resources