Shane Shane - 7 days ago 7
React JSX Question

React.PropType with two possible shapes

Background: I'm trying to specify a prop type for a dimension spec where I can either specify an explicit value, or a min and max value to evaluate. This will be used by an image component that will evaluate if the image is an acceptable height and width for user generated input.


The abstract goal is to have a React props value resolve to one of two possible shapes.





What works so far



an explicit value for width would have this shape:

{
width: PropTypes.shape({
value: PropTypes.number.isRequired,
error: PropTypes.string
})
}


a min and max value would have this shape:

{
width: PropTypes.shape({
min: {
value: PropTypes.number.isRequired,
error: PropTypes.string
}, max: {
value: PropTypes.number.isRequired,
error: PropTypes.string
}
})
}


If I try either of these methods and the properties match, everything works as expected so I can safely assume errors are coming from the code below.




What does not work



I tried using an optionalUnion as seen in the React typechecking documentation via PropTypes.oneOfType with the syntax below.

{
width: PropTypes.oneOfType([
PropTypes.shape({
value: PropTypes.number.isRequired,
error: PropTypes.string
}), PropTypes.shape({
min: {
value: PropTypes.number.isRequired,
error: PropTypes.string
}, max: {
value: PropTypes.number.isRequired,
error: PropTypes.string
}
})
])
}


When I try this, I get the response

Warning: Failed propType: checker is not a function Check the render method of 'MyParentContainer'.


If I use either shape without the
PropTypes.oneOfType
method, no complaints in the debugger, everything runs as expected.
PropTypes.oneOf
throws a seemingly worse error:

warning.js:45Warning: Failed propType: Invalid prop 'dimensions.width' of value '[object Object]' supplied to 'MyComponent', expected one of [null,null]. Check the render method of 'MyParentContainer'.


Does anyone know if this syntax is off or is this a limitation of PropTypes.oneOfType? Is there a standard solution to this kind of problem?

Answer

PropTypes.shape is not recursive, you have to repeat it for substructures, e.g.

width: PropTypes.shape({
    min: PropTypes.shape({
        value: PropTypes.number.isRequired,
        error: PropTypes.string
    }).isRequired,
    max: PropTypes.shape({
        value: PropTypes.number.isRequired,
        error: PropTypes.string
    }).isRequired
})

Therefore, the entire validator should be:

const propTypes = {
    width: PropTypes.oneOfType([
        PropTypes.shape({
            value: PropTypes.number.isRequired,
            error: PropTypes.string
        }),
        PropTypes.shape({
            min: PropTypes.shape({
                value: PropTypes.number.isRequired,
                error: PropTypes.string
            }).isRequired,
            max: PropTypes.shape({
                value: PropTypes.number.isRequired,
                error: PropTypes.string
            }).isRequired
        })
    ])
};

Also, I really recommend to use variables to increase readability and prevent code duplication:

const valueType = PropTypes.shape({
    value: PropTypes.number.isRequired,
    error: PropTypes.string
});

const propTypes = {
     width: PropTypes.oneOfType([
         valueType,
         PropTypes.shape({
             min: valueType.isRequired,
             max: valueType.isRequired
         })
     ])
 };