An alternative way to provide default properties to React components. See also this and this blog post.
The Synopsis below shows examples of providing and consuming default properties. Below the Synopsis we present several snippets (short examples) and facts about those snippets.
A PropsContext
is a React context that offers a dictionary of getter functions.
Each getter function corresponds to a default property that is available through
the withDefaultProps
higher order component.
Instead of using DefaultPropsContext.Provider
, you should use the DefaultPropsProvider
component, which offers additional functionality (i.e. the extend
attribute).
import { DefaultPropsProvider } from 'react-default-props-context';
const MyFrame = () => {
const defaultPropsContext = {
defaultProps: {
color: () => 'red',
},
};
return (
// Note that DefaultPropsProvider is a substitute for DefaultPropsContext.Provider
<DefaultPropsProvider value={defaultPropsContext}>
<div>
<MyComponent name="example using red (the default color)" />
<MyComponent name="example using green" color="green" />
</div>
</DefaultPropsProvider>
);
};
import { withDefaultProps, stub } from 'react-default-props-context';
type PropsT = { name: string };
const DefaultProps = { color: stub as string };
const MyComponent = withDefaultProps(
//
(props: PropsT & typeof DefaultProps) => {
// The props.color value either comes directly from the parent element
// (as a property) or from a PropsContext.
return <text color={props.color}>{`Hello ${props.name}`}</text>;
},
DefaultProps
);
The remainder of this documentation presents a series of snippets and facts about those snippets.
import { DefaultPropsProvider } from 'react-default-props-context';
const MyFrame = () => {
const defaultPropsContext = {
defaultProps: {
color: () => 'red',
},
};
return (
<DefaultPropsProvider value={defaultPropsContext}>
<MyComponent name="example using red (the default color)" />
</DefaultPropsProvider>
);
};
The DefaultPropsProvider
provides every default property stored in defaultProps
to its nested components (MyComponent
).
Rather than storing values directly in defaultProps
, each default property is stored as a function that returns the default value. This function is called when the consuming component (MyComponent
) accesses the default property. There are two reasons for this design. First, it provides lazy evaluation, which means that the default property value can be unknown when the PropsContext is created. Second, it prevents the providing component (MyFrame
) from referencing the property value. This is important when using MobX (or any other framework that tracks variable access): if the default value changes then this will not trigger a re-render of MyFrame
.
import { withDefaultProps } from 'react-default-props-context';
type PropsT = { name: string };
const DefaultProps = { color: stub as string };
const MyComponent = withDefaultProps(
//
(props: PropsT & typeof DefaultProps) => {
return <text color={props.color}>{`Hello ${props.name}`}</text>;
},
DefaultProps
);
The withDefaultProps
function is a higher order component that receives a properties object and "enriches" it with the default properties provided by the enclosing DefaultPropsProvider
. It passes the enriched properties to it's enclosed component function. Informally, you can think of it as receiving the props.color
value either from the parent component (that can set this property) or from the enclosing PropsContext
.
To tell withDefaultProps
which default properties are used in a component, you need to pass in a plain DefaultProps
object where:
- the object keys contain the names of the default properties
- the object value types contain the types of the default properties.
Note that the object values themselves are unimportant, because the default property value will be provided by the enclosing PropsContext
. The withDefaultProps
function uses the default property names to assert (at run-time) that requested default property are provided. The default property types are used to produce a type error when overriding a default property using the wrong type.
import { DefaultPropsProvider } from 'react-default-props-context';
const MyFrame = () => {
const defaultPropsContext = {
defaultProps: {
color: () => 'red',
shape: () => 'circle',
},
fixed: { shape: true },
};
const moreDefaultPropsContext = {
defaultProps: {
size: () => 123,
shape: undefined
}
};
return (
<DefaultPropsProvider value={defaultPropsContext}>
<DefaultPropsProvider extend value={moreDefaultPropsContext}>
<MyComponent name="example using green" color="green">
<MyComponent name="this nested component also uses green"/>
<MyComponent name="this nested component uses blue" color="blue"/>
<MyComponent/>
</DefaultPropsProvider>
</DefaultDefaultPropsProvider>
);
};
Because we use a DefaultPropsProvider
with extend=true
here, the MyComponent
instance can access both the color
and the size
default property. If we dont set extend
to true then MyComponent
only has access to size
(because it only takes default properties from the nearest enclosing DefaultDefaultPropsProvider
).
Here, we override the default color
value by setting it to green
. This works by wrapping the MyComponent
instance in a extended DefaultPropsProvider
that adds a new color
default property with green
as its value. So when MyFrame
sets the color
property of MyComponent
, it has the side effect of overriding the default color
property. This means that the new default value (green
) is also provided to any child components of MyComponent
.
In some case, it's good to allow components to use a default property, but not to override it. This can be achieved this by setting the fixed
options object. In the example, we see how this is used to declare shape
to be fixed.
Sometimes you want to remove a default property in a particular branch of the rendering tree. You can do this by setting the property to undefined in the defaultProps
object. In the above example, the shape
default property was removed.
// file: dps.ts
import { stub } from 'react-default-props-context';
const dps = {
color: { color: stub as string };
size: { size: stub as int };
}
// file: MyComponent.tsx
import { withDefaultProps } from 'react-default-props-context';
import { dps } from 'dps';
type PropsT = { name: string };
const DefaultProps = {
...dps.color,
...dps.size,
};
const MyComponent = withDefaultProps((props: PropsT & typeof DefaultProps) => {
const propsReceivedFromParent = getOriginalProps(props);
const defaultPropsWithoutOverrides = getOriginalDefaultProps(props);
return <text color={props.color}>{`Size is ${props.size}`}</text>;
}, DefaultProps);
It makes sense to use a central location to define the names and types of the default properties that are used in the application.
This way, when using the default properties in your component, you are protected from misspelling the names, or using the wrong types.
In the above example, we see how color
and size
can be defined in a global defaultProps.ts
file.
The props
argument of MyComponent
contains both normal properties and default properties. If you want to access only the properties received from the parent component then you can use the getOriginalProps
function.
If the parent component of MyComponent
sets the color
property (e.g. color="green"
) then this has the effect of overriding the color
default property value . If you want to access the original default properties (without overrides from the parent component) then you can use the getOriginalDefaultProps
function.