I have a "higher order component" that is implemented in the following way (without types).
const Themeable = (mapThemeToProps) => {
return (WrappedComponent) => {
const themedComponent = (props, { theme: appTheme }) => {
return <WrappedComponent
{...props}
theme={merge(
{},
defaultTheme,
appTheme,
mapThemeToProps(merge(defaultTheme, appTheme))
)}
>
}
themedComponent.contextTypes = { theme: PropTypes.object };
return themedComponent;
}
}
To summarize what it does, it takes a mapThemeToProps
function. This will receive a theme parameter created by merging defaultTheme
(provided by my library) and appTheme
(provided by a ThemeProvider
component via context). It will then create an extended Theme
and pass it to the component as a prop called theme
. In practice, it would be used as follows (being called Themeable
in this scope):
const mapThemeToProps = (theme) => ({
background: theme.palette.dark,
color: theme.text.light,
});
function Greeting({ theme: { background, color } }) {
return (
<div style={{ background, color }}>
Hello World!
<div>
)
}
export default Themeable(mapThemeToProps)(Greeting);
I am having a very hard time developing a proper typing for this function. I found this pattern is very similar to something along the lines of connect
from react-redux
so have been working from their typings as my starting point. Anyway, I am a bit lost, this is basically where I am at:
import { Theme } from 'types/Theme';
interface ThemeableComponentEnhancer {
<P>(component: React.ComponentType<P>): React.ComponentClass<Pick<P, "theme"> & { theme: Theme }> & { WrappedComponent: React.Component<P>}
}
export interface Themeable {
// calling it with no parameters returns a basic theme.
(): Theme;
// calling it with a function
<TTheme = {}>(mapThemeToProps: MapThemeToPropsParam<TTheme>): ComponentEnhancer<{ theme: TTheme }>;
}
interface MapThemeToProps<TTheme> {
(theme: TTheme): TTheme;
}
interface MapThemeToPropsFactory<TTheme> {
(theme: Theme): MapThemeToProps<TTheme>;
}
type MapThemeToPropsParam<TTheme> = MapStateToPropsFactory<TTheme>
I'm having trouble getting my head around this. How would this be done in TypeScript?
An update can be caused by changes to props or state. These methods are called in the following order when a component is being re-rendered: static getDerivedStateFromProps() shouldComponentUpdate() render()
The simplest way to define a component is to write a JavaScript function: function Welcome(props) { return <h1>Hello, {props. name}</h1>; } This function is a valid React component because it accepts a single “props” (which stands for properties) object argument with data and returns a React element.
Theme.ts
export interface Theme {
palette: {
primary: string;
secondary: string;
};
}
export const theme: Theme = {
palette: {
primary: 'red',
secondary: 'yellow'
}
};
Rect
component with Theme
import * as React from 'react';
import { Theme } from '../theme/Theme';
import withTheme, { MapThemeToProps } from '../theme/withTheme';
export interface RectThemeProps {
titleColor?: string;
bodyColor?: string;
}
export interface RectProps extends RectThemeProps {
content?: string;
}
export class Rect extends React.Component<RectProps> {
render() {
const {titleColor, bodyColor, content} = this.props;
return <div>{content && content}</div>;
}
}
const mapThemeToProps: MapThemeToProps<
RectThemeProps,
Theme
> = (theme) => {
return {
titleColor: theme.palette.primary,
bodyColor: theme.palette.secondary
};
};
export default withTheme(mapThemeToProps)(Rect);
withTheme.ts
import * as React from 'react';
const ID = '__context_id__';
export interface MapThemeToProps<M, T> {
(theme: T): M;
}
export interface withTheme {
<M, T>(mapThemeToProps: MapThemeToProps<M, T>):
(c: React.ComponentType<M>) => React.ComponentType<M>;
}
export default <M, T>(
mapThemeToProps: MapThemeToProps<M, T>
) => (Component: React.ComponentType<M>) => {
return class extends React.Component<M> {
render() {
const theme = this.context[ID];
return (
<Component
{...this.props}
{...mapThemeToProps(theme.getTheme())}
/>
);
}
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With