facebook / prop-types

Runtime type checking for React props and similar objects
MIT License
4.48k stars 356 forks source link

There should be a way to validate the "shape" of an array. #230

Open GreenGremlin opened 6 years ago

GreenGremlin commented 6 years ago

It would be useful to have the ability to define the "shape" of an array prop. This could fairly easily be done by refactoring PropTypes.shape to take a shapeTypes argument of either an object or an array.

Example use case

const answers = loadAnswers();
/*
loadAnswers returns an array of key / value pairs.
[
  ['name', 'Foo Bar'],
  ['email', 'foo@email.com'],
  ['website', 'www.foo.com'],
];
*/
<UserForm answers={answers} />

UserForm.propTypes = {
  answers: PropTypes.arrayOf(PropTypes.shape([
    PropTypes.oneOf(['name', 'email', 'website']),
    PropTypes.string,
  ])),
}

Implementation wise, this should be as simple as updating the for...in loop in createShapeTypeChecker to be a for...of loop over the shapeTypes entries.

-       if (propType !== 'object') {
+       if (propType !== 'object' && propType !== 'array') {
          return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
        }
-       for (var key in shapeTypes) {
+       for (var [key, checker] of Object.entries(shapeTypes)) {
-         var checker = shapeTypes[key];

Of course this assumes support for Object.entries and destructuring, which I'm not sure this package is free to use. If not, then the implementation will be a little more complex, but certainly doable.

If this seems like a reasonable proposal, I can put up a PR.

ljharb commented 6 years ago

I'm confused; you want to validate that all items in an array match a validator? "shape" is for object properties, which arrays tend not to have.

GreenGremlin commented 6 years ago

I want to be able to define validation for the elements of an array in a way that position matters. In my example an answer is a key / value array. An answer has the "shape" [key, value] which is equivalent to {0: key, 1: value}. This makes sense, because in javascript arrays are just a special type of object.

ljharb commented 6 years ago

Ah, i see, you want to make a propType for a "tuple".

I don't think it belongs in this project, but here's how you could do it using and and shape from airbnb-prop-types:

answers: and([PropTypes.array, shape({
  0: keyPropType.isRequired,
  1: valuePropType.isRequired,
  length: PropTypes.oneOf([2]).isRequired,
})])

(Arrays may be a special type of object, but conceptually they are quite different - arrays are lists, objects are bags of properties)

GreenGremlin commented 6 years ago

Yes, a tuple is a good way to put it.

This is the solution I came up with, before filing this ticket, which doesn't require adding the airbnb-prop-types library.

function arrayOfShape(typeCheckers) {
  return PropTypes.arrayOf(
    (value, index, ...rest) => typeCheckers[index](value, index, ...rest)
  )
}
ljharb commented 6 years ago

sure, that works as well (altho it won't check the length).

ljharb commented 5 years ago

Related to #143.

feonit commented 5 years ago

Best version of preview example:

function tuple(typeCheckers: Function[]) {
    return PropTypes.arrayOf(
        function (value, index, ...rest) {
            return typeCheckers[index].call(PropTypes, value, index, ...rest);
        }
    )
}