Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrict usage of global React object #3376

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const createProductMutation = gql`
`;

interface ProductsGridToolbarToolbarProps extends GridToolbarProps {
toolbarAction: React.ReactNode;
toolbarAction: ReactNode;
}
function ProductsGridToolbar({ toolbarAction }: ProductsGridToolbarToolbarProps) {
return (
Expand Down Expand Up @@ -119,7 +119,7 @@ export function ProductsGrid({ toolbarAction, rowAction, actionsColumnWidth = 52
filterable: false,
sortable: false,
renderCell: ({ row }) => {
const typeLabels: Record<string, React.ReactNode> = {
const typeLabels: Record<string, ReactNode> = {
Cap: <FormattedMessage id="product.staticSelectType.primaryText.Cap" defaultMessage="great Cap" />,
Shirt: <FormattedMessage id="product.staticSelectType.primaryText.Shirt" defaultMessage="Shirt" />,
Tie: <FormattedMessage id="product.staticSelectType.primaryText.Tie" defaultMessage="Tie" />,
Expand All @@ -137,7 +137,7 @@ export function ProductsGrid({ toolbarAction, rowAction, actionsColumnWidth = 52
filterable: false,
sortable: false,
renderCell: ({ row }) => {
const inStockLabels: Record<string, React.ReactNode> = {
const inStockLabels: Record<string, ReactNode> = {
true: <FormattedMessage id="product.staticSelectInStock.primaryText.true" defaultMessage={`It's in stock :D`} />,
false: <FormattedMessage id="product.staticSelectInStock.primaryText.false" defaultMessage="No longer available :(" />,
};
Expand Down
6 changes: 3 additions & 3 deletions demo/admin/src/products/future/generated/ProductsGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const createProductMutation = gql`
`;

interface ProductsGridToolbarToolbarProps extends GridToolbarProps {
toolbarAction: React.ReactNode;
toolbarAction: ReactNode;
exportApi: ExportApi;
}
function ProductsGridToolbar({ toolbarAction, exportApi }: ProductsGridToolbarToolbarProps) {
Expand Down Expand Up @@ -167,12 +167,12 @@ export function ProductsGrid({ filter, toolbarAction, rowAction, actionsColumnWi
filterable: false,
sortable: false,
renderCell: ({ row }) => {
const typeLabels: Record<string, React.ReactNode> = {
const typeLabels: Record<string, ReactNode> = {
Cap: <FormattedMessage id="product.overview.secondaryText.type.Cap" defaultMessage="great Cap" />,
Shirt: <FormattedMessage id="product.overview.secondaryText.type.Shirt" defaultMessage="Shirt" />,
Tie: <FormattedMessage id="product.overview.secondaryText.type.Tie" defaultMessage="Tie" />,
};
const inStockLabels: Record<string, React.ReactNode> = {
const inStockLabels: Record<string, ReactNode> = {
true: <FormattedMessage id="product.overview.secondaryText.inStock.true" defaultMessage="In stock" />,
false: <FormattedMessage id="product.overview.secondaryText.inStock.false" defaultMessage="Out of stock" />,
};
Expand Down
8 changes: 4 additions & 4 deletions demo/site-pages/src/redraft.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
declare module "redraft" {
interface InlineStylesMap {
[key: string]: (children: React.ReactNode, options: { key: string }) => React.ReactNode;
[key: string]: (children: ReactNode, options: { key: string }) => ReactNode;
}

export type TextBlockRenderFn = (children: React.ReactNode[], options: { key: string; depth: number; keys: string[] }) => React.ReactNode;
export type TextBlockRenderFn = (children: ReactNode[], options: { key: string; depth: number; keys: string[] }) => ReactNode;
interface BlockMap {
[key: string]: TextBlockRenderFn;
}

interface EntityMap {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: (children: React.ReactNode, data: any, options: { key: string }) => React.ReactNode;
[key: string]: (children: ReactNode, data: any, options: { key: string }) => ReactNode;
}

export interface Renderers {
Expand All @@ -20,7 +20,7 @@ declare module "redraft" {
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const redraft = (raw: any, renderers: Renderers): React.ReactElement<any, any> | null => {};
const redraft = (raw: any, renderers: Renderers): ReactElement<any, any> | null => {};

export default redraft;
}
4 changes: 2 additions & 2 deletions demo/site/src/common/blocks/CookieSafeYouTubeVideoBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import { CookieSafe, useCookieApi, YouTubeVideoBlock } from "@comet/cms-site";
import { cookieIds } from "@src/util/cookieIds";
import { type ComponentProps } from "react";
import { type ComponentProps, type CSSProperties } from "react";
import styled from "styled-components";

import { FallbackCookiePlaceholder, LoadingCookiePlaceholder } from "../helpers/CookiePlaceholders";
Expand All @@ -24,7 +24,7 @@ export const CookieSafeYouTubeVideoBlock = (props: ComponentProps<typeof YouTube
);
};

const Root = styled.div<{ $aspectRatio: React.CSSProperties["aspectRatio"] }>`
const Root = styled.div<{ $aspectRatio: CSSProperties["aspectRatio"] }>`
position: relative;
aspect-ratio: ${({ $aspectRatio }) => $aspectRatio};
`;
8 changes: 4 additions & 4 deletions demo/site/src/redraft.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
declare module "redraft" {
interface InlineStylesMap {
[key: string]: (children: React.ReactNode, options: { key: string }) => React.ReactNode;
[key: string]: (children: ReactNode, options: { key: string }) => ReactNode;
}

export type TextBlockRenderFn = (children: React.ReactNode[], options: { key: string; depth: number; keys: string[] }) => React.ReactNode;
export type TextBlockRenderFn = (children: ReactNode[], options: { key: string; depth: number; keys: string[] }) => ReactNode;
interface BlockMap {
[key: string]: TextBlockRenderFn;
}

interface EntityMap {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: (children: React.ReactNode, data: any, options: { key: string }) => React.ReactNode;
[key: string]: (children: ReactNode, data: any, options: { key: string }) => ReactNode;
}

export interface Renderers {
Expand All @@ -20,7 +20,7 @@ declare module "redraft" {
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const redraft = (raw: any, renderers: Renderers): React.ReactElement<any, any> | null => {};
const redraft = (raw: any, renderers: Renderers): ReactElement<any, any> | null => {};

export default redraft;
}
4 changes: 2 additions & 2 deletions demo/site/src/util/ErrorHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use client";

import { ErrorHandlerProvider } from "@comet/cms-site";
import { type PropsWithChildren } from "react";
import { type ErrorInfo, type PropsWithChildren } from "react";

export function ErrorHandler({ children }: PropsWithChildren) {
function onError(error: Error, errorInfo: React.ErrorInfo) {
function onError(error: Error, errorInfo: ErrorInfo) {
if (process.env.NODE_ENV === "development") {
console.error("Error caught by error handler", error, errorInfo.componentStack);
throw error;
Expand Down
4 changes: 3 additions & 1 deletion docs/src/theme/Playground/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { type ButtonHTMLAttributes, type DetailedHTMLProps } from "react";

import styles from "./button.module.css";

interface ButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
isOpen: boolean;
}

Expand Down
9 changes: 5 additions & 4 deletions packages/admin/admin-theme/src/typographyOptions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type Breakpoints } from "@mui/material";
import { type TypographyOptions } from "@mui/material/styles/createTypography";
import { type CSSProperties } from "react";

const fontFamily = "Roboto Flex Variable, Helvetica, Arial, sans-serif";

Expand Down Expand Up @@ -130,13 +131,13 @@ export const createTypographyOptions = (breakpoints: Breakpoints): TypographyOpt

declare module "@mui/material/styles" {
interface TypographyVariants {
list: React.CSSProperties;
listItem: React.CSSProperties;
list: CSSProperties;
listItem: CSSProperties;
}

interface TypographyVariantsOptions {
list?: React.CSSProperties;
listItem?: React.CSSProperties;
list?: CSSProperties;
listItem?: CSSProperties;
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/admin/admin/src/dataGrid/GridColDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {
} from "@mui/x-data-grid";
import { type GridBaseColDef as MuiGridBaseColDef } from "@mui/x-data-grid/models/colDef/gridColDef";
import { type GridPinnedColumns } from "@mui/x-data-grid-pro";
import { type ReactNode } from "react";

type ValueOption =
| string
| number
| {
value: any;
label: string;
cellContent?: React.ReactNode;
cellContent?: ReactNode;
};

type GridColDefExtension<R extends GridValidRowModel = any> = {
Expand Down
2 changes: 1 addition & 1 deletion packages/admin/admin/src/renderFinalFormChildren.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function renderFinalFormChildren<FormValues = Record<string, any>, Initia
// Explicit cast to remove InitialFormValues because FormRenderProps doesn't pass InitialFormValues to RenderableProps here:
// https://github.com/final-form/react-final-form/blob/main/typescript/index.d.ts#L56-L67.
// See https://github.com/final-form/react-final-form/pull/998.
{ ...formRenderProps, render: render as ((props: FormRenderProps<FormValues>) => React.ReactNode) | undefined },
{ ...formRenderProps, render: render as ((props: FormRenderProps<FormValues>) => ReactNode) | undefined },
children as ReactNode,
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useApolloClient } from "@apollo/client";
import { createContext, type ReactNode, useCallback, useContext, useState } from "react";
import { createContext, type PropsWithChildren, useCallback, useContext, useState } from "react";

import { type GQLFilenameResponse } from "../../../graphql.generated";
import { useDamScope } from "../../config/useDamScope";
Expand Down Expand Up @@ -28,7 +28,7 @@ export const useManualDuplicatedFilenamesHandler = (): ManualDuplicatedFilenames

export type DuplicateAction = "replace" | "copy" | "skip";

export const ManualDuplicatedFilenamesHandlerContextProvider: React.FunctionComponent<{ children: ReactNode }> = ({ children }) => {
export const ManualDuplicatedFilenamesHandlerContextProvider = ({ children }: PropsWithChildren) => {
const client = useApolloClient();
const scope = useDamScope();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Typography,
} from "@mui/material";
import { styled } from "@mui/material/styles";
import { Fragment } from "react";
import { Fragment, type VoidFunctionComponent } from "react";
import { FormattedMessage } from "react-intl";

import { type FilenameData } from "./ManualDuplicatedFilenamesHandler";
Expand All @@ -38,7 +38,7 @@ interface DuplicateFilenameDialogProps {
onReplace: () => void;
}

export const ManuallyHandleDuplicatedFilenamesDialog: React.VoidFunctionComponent<DuplicateFilenameDialogProps> = ({
export const ManuallyHandleDuplicatedFilenamesDialog: VoidFunctionComponent<DuplicateFilenameDialogProps> = ({
open,
filenameData,
onSkip,
Expand Down
7 changes: 4 additions & 3 deletions packages/admin/cms-admin/src/documents/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type TypedDocumentNode } from "@apollo/client";
import { type BlockDependency, type ReplaceDependencyObject } from "@comet/blocks-admin";
import { type SvgIconProps } from "@mui/material";
import { type ComponentType, type ReactNode } from "react";

import { type GQLDocumentInterface, type Maybe } from "../graphql.generated";
import { type PageTreePage } from "../pages/pageTree/usePageTree";
Expand Down Expand Up @@ -36,14 +37,14 @@ export interface DocumentInterface<
DocumentInput extends Record<string, unknown> = Record<string, unknown>,
DocumentOutput extends Record<string, unknown> = Record<string, unknown>,
> {
displayName: React.ReactNode;
displayName: ReactNode;
getQuery?: TypedDocumentNode<GQLPageQuery, GQLPageQueryVariables>; // TODO better typing (see createUsePage.tsx)
editComponent?: React.ComponentType<{ id: string; category: string }>;
editComponent?: ComponentType<{ id: string; category: string }>;
updateMutation?: TypedDocumentNode<GQLUpdatePageMutation, GQLUpdatePageMutationVariables<DocumentOutput>>;
inputToOutput?: (input: DocumentInput) => DocumentOutput;
menuIcon: (props: SvgIconProps<"svg">) => JSX.Element | null;
hideInMenuIcon?: (props: SvgIconProps<"svg">) => JSX.Element | null;
InfoTag?: React.ComponentType<{ page: PageTreePage }>;
InfoTag?: ComponentType<{ page: PageTreePage }>;
anchors: (input: DocumentInput) => string[];
dependencies: (input: DocumentInput) => BlockDependency[];
replaceDependenciesInOutput: (output: DocumentOutput, replacements: ReplaceDependencyObject[]) => DocumentOutput;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type GridColDef } from "@comet/admin";
import { type ComponentProps } from "react";
import { type FormattedNumber } from "react-intl";

import { type BaseColumnConfig, type ImportReference } from "../generator";
Expand All @@ -18,7 +19,7 @@ type StaticText = {
text: string;
};

type FormattedNumberPropsForNumberField = Omit<React.ComponentProps<typeof FormattedNumber>, "value" | "children">;
type FormattedNumberPropsForNumberField = Omit<ComponentProps<typeof FormattedNumber>, "value" | "children">;

type NumberField<FieldName extends string> = AbstractField<FieldName> &
FormattedNumberPropsForNumberField & {
Expand Down Expand Up @@ -163,7 +164,7 @@ const getTextForCellContent = (textConfig: Field<string>, messageIdPrefix: strin
})
.join(", ");

const labelMappingVar = `const ${labelsVariableName}: Record<string, React.ReactNode> = { ${labelMapping} };`;
const labelMappingVar = `const ${labelsVariableName}: Record<string, ReactNode> = { ${labelMapping} };`;
const textContent = `(${rowValue} == null ? ${emptyText} : ${labelsVariableName}[` + `\`\${${rowValue}}\`` + `] ?? ${rowValue})`;

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const getGridToolbarProps = (componentName: string, toolbarAction: boolean, expo
if (toolbarAction) {
props.push({
destructured: "toolbarAction",
typeDefinition: "toolbarAction?: React.ReactNode",
typeDefinition: "toolbarAction?: ReactNode",
});
}

Expand Down Expand Up @@ -135,7 +135,7 @@ const renderToolbarActions = ({ forwardToolbarAction, addItemText, excelExport,
const renderToolbarProps = (componentName: string, forwardToolbarAction: boolean | undefined, exportApi: boolean) => {
if (forwardToolbarAction || exportApi) {
return `interface ${componentName}ToolbarProps extends GridToolbarProps {
${forwardToolbarAction && "toolbarAction: React.ReactNode;"}
${forwardToolbarAction && "toolbarAction: ReactNode;"}
${exportApi ? "exportApi: ExportApi;" : ""}
}`;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/admin/cms-admin/src/userPermissions/UserGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { styled } from "@mui/material/styles";
import { DataGrid, type GridRenderCellParams, GridToolbarQuickFilter } from "@mui/x-data-grid";
import type { GridToolbarProps } from "@mui/x-data-grid/components/toolbar/GridToolbar";
import { type GridSlotsComponent } from "@mui/x-data-grid/models/gridSlotsComponent";
import { useContext } from "react";
import { type ReactNode, useContext } from "react";
import { FormattedMessage, useIntl } from "react-intl";

import { commonImpersonationMessages } from "../common/impersonation/commonImpersonationMessages";
Expand All @@ -28,7 +28,7 @@ import { type GQLUserForGridFragment, type GQLUserGridQuery, type GQLUserGridQue
import { startImpersonation, stopImpersonation } from "./utils/handleImpersonation";

interface UserPermissionsUserGridToolbarProps extends GridToolbarProps {
toolbarAction: React.ReactNode;
toolbarAction: ReactNode;
}
function UserPermissionsUserGridToolbar({ toolbarAction }: UserPermissionsUserGridToolbarProps) {
return (
Expand All @@ -45,9 +45,9 @@ function UserPermissionsUserGridToolbar({ toolbarAction }: UserPermissionsUserGr
);
}
type Props = {
toolbarAction?: React.ReactNode;
toolbarAction?: ReactNode;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rowAction?: (params: GridRenderCellParams<any, GQLUserForGridFragment, any>) => React.ReactNode;
rowAction?: (params: GridRenderCellParams<any, GQLUserForGridFragment, any>) => ReactNode;
actionsColumnWidth?: number;
};

Expand Down
1 change: 1 addition & 0 deletions packages/admin/cms-admin/src/vendors.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="@comet/admin-theme" />

// eslint-disable-next-line no-restricted-globals
Copy link
Contributor Author

@SebiVPS SebiVPS Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not get it to work, when removing the React. usage here.

When its removed, there is an error, that the ref prop is missing in CustomInputProps.
And when the two types are imported import { type DetailedHTMLProps, type InputHTMLAttributes } from "react"; there is an error, that the added prop webkitdirectory is not found.

Do you have an idea what's going on here? Can we leave the React. usage here in the vendor.d.ts file (in the typedefinitions React. usage seems common?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should work:

/// <reference types="@comet/admin-theme" />

import { type HTMLAttributes } from "react";

declare module "react" {
    interface InputHTMLAttributes<T> extends HTMLAttributes<T> {
        // extends React's HTMLAttributes
        directory?: string;
        webkitdirectory?: string;
    }
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we leave the React. usage here in the vendor.d.ts file (in the typedefinitions React. usage seems common?)

We don't need it in other .d.ts files 🤔 Maybe we should add explicit imports to all .d.ts files, otherwise probably the namespace is still used?

interface CustomInputProps extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
webkitdirectory?: string;
directory?: string;
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-config/nextjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const config = [
{
rules: {
"@comet/no-private-sibling-import": ["error", ["gql", "sc", "gql.generated"]],
"no-restricted-globals": ["error", "React"],
"no-restricted-imports": [
"error",
{
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-config/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const config = [
"react/prop-types": "off",
"react/jsx-curly-brace-presence": "error",
"react/jsx-no-useless-fragment": ["error", { allowExpressions: true }],
"no-restricted-globals": ["error", "React"],
"no-restricted-imports": [
"error",
{
Expand Down
4 changes: 2 additions & 2 deletions packages/site/cms-site/src/blocks/VimeoVideoBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { type ReactNode, useState } from "react";
import { type ReactElement, type ReactNode, useState } from "react";
import styled, { css } from "styled-components";

import { type VimeoVideoBlockData } from "../blocks.generated";
Expand Down Expand Up @@ -27,7 +27,7 @@ function parseVimeoIdentifier(vimeoIdentifier: string): string | undefined {
interface VimeoVideoBlockProps extends PropsWithData<VimeoVideoBlockData> {
aspectRatio?: string;
previewImageSizes?: string;
renderPreviewImage?: (props: VideoPreviewImageProps) => React.ReactElement;
renderPreviewImage?: (props: VideoPreviewImageProps) => ReactElement;
fill?: boolean;
previewImageIcon?: ReactNode;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/site/cms-site/src/cookies/CookieApiContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { createContext, useContext } from "react";
import { createContext, type PropsWithChildren, useContext } from "react";

export type CookieApi = {
initialized: boolean;
Expand All @@ -12,7 +12,7 @@ export type CookieApiHook = () => CookieApi;

const CookieApiContext = createContext<CookieApi | undefined>(undefined);

type CookieApiProviderProps = React.PropsWithChildren<{
type CookieApiProviderProps = PropsWithChildren<{
api: CookieApiHook;
}>;

Expand Down
Loading