How can I validate a propType shape with dynamic keys and values already defined by another propType interface?

Viewed 118

Let's say I have the following prop-type interface for an user object:

const userPropType = shape({
  name: string,
  address: string,
  age: number    
});

Then I wanted to validate a global users' object received by props, that is comprised of several individual user objects, which keys are random numeric IDs. For example:

{
  3142: {
    name: 'Jonh Doe',
    address: 'Baker Street',
    age: 23 
  },
  4345: {
    name: 'Jane Doe',
    address: 'Fourth Avenue',
    age: 64 
  }
}

I know I can validate the numeric keys without trouble using a Prop Type custom validator. But the issue would be: how can I validate my global users' object using a custom handler that could validate the keys dynamically, but also the values based on my already defined userPropType interface.

In this short example I could easily type check each of the user attributes on the custom validator, but if we consider a real case scenario where my user object could have more than 50 attributes, it would be pretty annoying to be custom validating each one of them, specially when I already have a defined propType for my single user object.

So, that's the question: is it possible to use an already defined prop-type interface inside a custom validator?

Thanks!

1 Answers

I've seen that this has not being answered yet. As I could already find a solution, I share it here in the hope that it may be of any help of other fellow developers out there.

I've implemented a custom validator that could check for me if the prop is indeed in the exact format I expect.

To clarify, my 'users' prop should be an object with numeric keys (even if represented as strings) and have three attributes: name (string), address (string) and age (number).

This is my custom implementation:

const usersObjectPropType = (
  propValue,
  key,
  componentName
) => {
  let isNotValid;
  const userKeys = Object.keys(propValue.users);
  const userValues = Object.values(propValue.users);
  const allKeysAreNumeric = userKeys.every(key => !Number.isNaN(Number(key)));
  userValues.forEach(user => {
    if (typeof user.name !== 'string') {
      isNotValid = ['name', 'string', user.name];
    } else if (typeof user.address !== 'string') {
      isNotValid = ['address', 'string', user.address];
    } else if (typeof user.age !== 'number') {
      isNotValid = ['age', 'number', user.age];
    }
  });

  if (!allKeysAreNumeric) {
    return new Error(
      `Invalid prop '${key}' supplied to ${componentName} component. The keys for the '${key}' object should have the type 'number'.`
    );
  }
  if (isNotValid) {
    return new Error(
      `Invalid prop in '${key}' object supplied to ${componentName} component. The type of '${
        isNotValid[0]
      }' attribute provided was '${typeof isNotValid[2]}' and a '${
        isNotValid[1]
      }' was expected.`
    );
  }
};

Child.propTypes = {
  users: usersObjectPropType
};

And here's a working demo, where you can change the type of the props sent by App to a Child component and see the PropType errors on the console:

https://stackblitz.com/edit/react-4sunky?file=src/App.js

Related