reactjs - Typescript: Error de tipo en el componente de orden superior de React Context
Estoy tratando de escribir una función HOC de React Context para poder envolver otros componentes con el Contexto, lo que me permite enviar todas las notificaciones a un solo componente. Tuve que armar una solución funcional a partir de varias guías diferentes, principalmente esta, ya que ninguna de ellas funcionaba exactamente en su forma original.
Lo que terminé teniendo es algo así:
HOC :
import * as React from "react";
import { INotificationContextState, NotificationConsumer } from "./NotificationContext";
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export default function withNotificationContext<
P extends { notificationContext?: INotificationContextState },
R = Omit<P, 'notificationContext'>
>(
Component: React.ComponentClass<P> | React.StatelessComponent<P>
): React.SFC<R> {
return function BoundComponent(props: R) {
return (
<NotificationConsumer>
{value => <Component {...props} notificationContext={value} />}
</NotificationConsumer>
);
};
}
Contexto :
interface IMessage {
message: string;
key: number;
}
interface INotificationState {
open: boolean;
messageInfo: IMessage;
timeOut: number;
}
interface INotificationContextState extends INotificationState {
handleClick(message: string): void;
handleClose(event: React.MouseEvent<HTMLElement>, reason: any): void;
handleExited(): void;
}
const NotificationContext = React.createContext<INotificationContextState | null>(
null
);
class NotificationProvider extends React.Component {
public state: Readonly<INotificationState> = DEFAULT_STATE;
private queue: IMessage[] = [];
constructor(props: INotificationContextState) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.handleClose = this.handleClose.bind(this);
}
// rest of the class methods...
}
export const NotificationConsumer = NotificationContext.Consumer;
El archivo HOC me da un subrayado rojo bajo la palabra Component en la línea {value => <Component {...props} notificationContext={value} />}
El error dice lo siguiente:
El tipo 'R & { notificationContext: INotificationContextState | null; }' no se puede asignar al tipo 'IntrinsicAttributes & P & { children?: ReactNode; }'. El tipo 'R & { notificationContext: INotificationContextState | null; }' no se puede asignar al tipo 'P'.ts(2322)
Lo extraño es que este error no me impide ejecutar la aplicación y, además, la función envoltorio funciona exactamente como debería, y el componente envuelto puede pasar notificaciones al objeto Notification.
Parte de mi problema es que no estoy familiarizado con la sintaxis Omit/Pick y trato de determinar mentalmente cuál es exactamente la definición de tipo de "P" en este contexto. Simplemente no tengo idea de por qué genera un error, pero aún así funciona.
Respuesta #1
Tampoco tengo una solución funcional para esto en este momento. Pero por ahora estoy haciendo un cast tardío de las props pasadas a Component de esta manera:
<Component {...props as any} notificationContext={value} />
Esto aún te daría tipos en el método render y eliminará el error.
Respuesta #2
El problema es el genérico R. Por defecto es Omit<P, 'notificationContext'>, pero podría establecerse explícitamente a cualquier cosa. No tienes problemas al ejecutar el código porque este valor por defecto es el valor que quieres, pero no quieres que sea genérico.
Tu HOC debería tener solo un valor genérico. Aquí estoy usando el genérico P para referirme a las props del componente externo. Tomamos un componente que necesita notificationContext y algunas props P y lo convertimos en un componente que solo requiere esas props P.
Tuve que incluir null como uno de los valores posibles de la propiedad notificationContext porque el valor de tu contexto podría ser null.
export default function withNotificationContext<P extends {}>(
Component: React.ComponentType<P & { notificationContext: INotificationContextState | null }>
): React.ComponentType<P> {
return function BoundComponent(props: P) {
return (
<NotificationConsumer>
{value => <Component {...props} notificationContext={value} />}
</NotificationConsumer>
);
};
}
