6

I need to validate dictionary-like objects in my reducers, but since I'm already using Babel I don't want to resort to tools like Typescript.

Take this object as an example:

posts : {
    byId : {
        "post1" : {
            id : "post1",
            author : "user1",
            body : "......",
            comments : ["comment1", "comment2"]    
        },
        "post2" : {
            id : "post2",
            author : "user2",
            body : "......",
            comments : ["comment3", "comment4", "comment5"]    
        }
    }
    allIds : ["post1", "post2"]
}

How could I express my expectations for the byId object using PropTypes? Is it possible? If so, how?

eddy
  • 4,373
  • 16
  • 60
  • 94

2 Answers2

10

You can write custom proptype checkers if you can't achieve what you wan't with PropTypes' builtin proptypes.

If you want an all the values of the byId to be objects with the properties id, author, body and comments, you can use shape, objectOf, and arrayOf. If you want allIds to include all the keys of byId you can write a custom validator:

posts: PropTypes.shape({
  byId: PropTypes.objectOf(PropTypes.shape({
    id: PropTypes.string,
    author: PropTypes.string,
    body: PropTypes.string,
    comments: PropTypes.arrayOf(PropTypes.string)
  })),
  allIds(props, propName, componentName) {
    if(!Object.keys(props.byId).every(postID => props[propName].includes(postID))) {
      return new Error('allIds must include all the ids provided to byId!');
    }
  }
})

The above uses shape so it expects a posts object with the keys byId and allIds. It expects byId to be an object with the property values to also be object of a shape, with id, author and body being strings, with comments being an array of strings. Finally, it uses a custom proptype validator that checks if every key in byId (the post IDs) exists in allIds. If not, then throw an error. Beware though, this won't cover the case that allIds has post IDs that don't exist in byIds. See How to know if two arrays have the same values for more solutions. You can add isRequired where necessary.

Andrew Li
  • 55,805
  • 14
  • 125
  • 143
  • Sorry if I'm being some kind of ignorant for asking this but, by putting `shape` directly inside of `objectOf`, wouldn't I be saying that one ,and only one,object of shape "post" is a direct descendant of the object "byId"? – eddy Jun 30 '17 at 05:08
  • @eddy I haven't tested it, but `objectOf` just checks for an object with *property values* of the type, in this case, a shaped object. It means *all property values must conform to the shape*. – Andrew Li Jun 30 '17 at 05:11
  • What I can't wrap my head around is that by placing an object of an "X" shape inside the "byId" object , I will be implying that "byId" can contain **one or more** objects of that shape and that each of those objects will be a key/value pair, where `value` happens to be of shape "X" – eddy Jun 30 '17 at 05:25
  • @eddy So `byId` is going to be an object with keys and values. `objectOf` defines that all values of that object have to be a certain proptype. The proptype for all the values of the object will be a shaped object with some properties. I'm not sure what your question is exactly. – Andrew Li Jun 30 '17 at 05:32
  • I'm so sorry :'( . I think my confusion came from my background in other languages (mainly strongly-typed ones) where to define a dictionary, you need to specify the data type for both, the key and the value. – eddy Jun 30 '17 at 14:37
  • @eddy `objectOf` assumes there will be keys of any type and the values the proptype you specify. – Andrew Li Jun 30 '17 at 15:11
  • Well I'm not sure about the part of _keys of any type_ . Keys(property names) in javascript objects are mostly strings, right? . [The docs](https://facebook.github.io/react/docs/typechecking-with-proptypes.html) only says this : _"An object with property values of a certain type"_, and that was the part I didn't notice :S . There lies the key to understand that `objectOf` can hold one of more values of the provided type. – eddy Jun 30 '17 at 15:22
  • @eddy Yes, keys are mostly strings, and what I meant by 'keys of any type' I meant any type that was valid JavaScript, such as numbers or strings which post IDS can be. BTW have you tried the answer out? – Andrew Li Jun 30 '17 at 15:25
  • Yep! Thank your for you patience and sorry for all the inconveniences. I'm accepting your answer. – eddy Jun 30 '17 at 15:26
  • @eddy No problem, happy to help! – Andrew Li Jun 30 '17 at 15:27
-2

Using PropTypes.shape()

posts: PropTypes.shape({
    byId: PropTypes.sth() // could be shape() or sth else
}),
cfraser
  • 941
  • 6
  • 16