npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

typed-component

v1.0.26

Published

Another syntax to type props

Downloads

3

Readme

typed-component

Another syntax to type props

Get started

npm install typed-component

# or

yarn add typed-component

Use

import typed from 'typed-component';
import Component from './component';

// check constructor
const MyTypedComponent = typed({
     onClick: Function
})(Component)

// check enums
const MyTypedComponent = typed({
     id: [Number, String]   // id could be a number or string
})(Component)
// check enums
const MyTypedComponent = typed({
     id: [undefined, String]   // optional prop
})(Component)
// check primitives
const MyTypedComponent = typed({
     genre: ['male', 'female']
})(Component)
// check with custom logic
const MyTypedComponent = typed({
     age: age => age > 18
})(Component)
// check string with regex
const MyTypedComponent = typed({
     email:  /^((https?):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/
})(Component)

Roadmap

  • [x] check type by constructor
  • [x] enum type (oneOf & oneOfType)
  • [x] shape type
  • [x] default props
  • [x] optional prop ([undefined, String])
  • [x] custom prop validation with a function (value, propName, allProps)
  • [x] Check RegEx
  • [x] Match prop name by RegEx
  • [x] arrayOf & objectOf examples
  • [ ] optional prop width ? { a?: Number}
  • [ ] instanceof
  • [ ] global and local settings to change how to warn invalid prop (throw error , log error or custom log)
  • [ ] support to handle static propTypes and static defaultProps

All it can do

import React from 'react';
import ReactDom from 'react-dom';
import typedComponent from './index';

const RenderProps = props => (
  <pre>{JSON.stringify(props, null, 4)}</pre>
);

const div = document.createElement('div');
const render = c => ReactDom.render(c, div);
beforeAll(() => {
  global.console = {
    error: jest.fn(),
    log: jest.fn(),
  };
});
describe('basic usage by Constructor', () => {
  const ValidTypes = typedComponent({
    String: String,
    Boolean: Boolean,
    Array: Array,
    Object: Object,
    String: String,
    Number: Number,
    Function: Function,
    RegExp: RegExp,
    Map: Map,
    Undefined: undefined,
    Null: null,
  })(RenderProps);

  test('should render and test valid props', () => {
    render(
      <ValidTypes
        String='1'
        String={'text'}
        Number={5}
        Boolean
        Boolean={false}
        Array={[2, 3]}
        Object={{ c: 4 }}
        Function={() => {}}
        RegExp={/hola/}
        Map={new Map()}
        Undefined={undefined}
        Null={null}
      />,
    );
    expect(global.console.error).not.toHaveBeenCalled();
  });
  test('should render and log error on invalid props', () => {
    render(
      <ValidTypes
        String
        Number
        Boolean={0}
        Array
        Object
        Function
        RegExp
        Map
        Undefined
        Null
      />,
    );
    expect(global.console.error).toHaveBeenCalledTimes(10);
  });
  test('should detect all types props are required', () => {
    render(<ValidTypes Undefined />);
    expect(global.console.error).toHaveBeenCalledTimes(10);
  });
});

describe('Primitives', () => {
  const Primitives = typedComponent({
    color: 'blue',
  })(RenderProps);

  test('should work primitives', () => {
    render(<Primitives color='blue' />);
    expect(global.console.error).not.toHaveBeenCalled();
  });
  test('should warn primitives', () => {
    render(<Primitives color='red' />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('Shapes', () => {
  const Shape = typedComponent({
    shape: {
      a: String,
    },
  })(RenderProps);
  test('should work shapes', () => {
    render(<Shape shape={{ a: 'a' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn shapes', () => {
    render(<Shape shape={{ a: 1 }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn shapes', () => {
    render(<Shape shape={{ b: 2, c: 'c' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('Shapes multiple required keys', () => {
  const Shape = typedComponent({
    shape: {
      a: String,
      b: Number,
    },
  })(RenderProps);
  test('should work', () => {
    render(<Shape shape={{ a: 'a', b: 2 }} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<Shape shape={{ a: 'a' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
   test('should warn one time per prop (not 2 even if 2 keys will fail)', () => {
     render(<Shape shape={{  }} />);
     expect(global.console.error).toHaveBeenCalledTimes(1);
   });
});

describe('Shapes recursively', () => {
  const Shape = typedComponent({
    shape: {
      person: {
        name: String,
        age: Number,
      },
      id: String,
    },
  })(RenderProps);
  test('should work', () => {
    render(
      <Shape
        shape={{
          person: {
            name: 'juan',
            age: 2,
          },
          id: 'asdasd',
        }}
      />,
    );
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(
      <Shape
        shape={{
          person: {
            name: 'juan',
            age: '2',
          },
          id: 'asdasd',
        }}
      />,
    );
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('Should check a function', () => {
  const Comp = typedComponent({
    a: value => value > 1,
  })(RenderProps);
  test('should work', () => {
    render(<Comp a={2} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<Comp a={0} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  const Comp2 = typedComponent({
    start: (value, allProps, key) => value < allProps.end,
  })(RenderProps);

  test('multiple props works', () => {
    render(<Comp2 start={1} end={4} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('multiple props warn', () => {
    render(<Comp2 start={1} end={0} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});
describe('Should check string by regex', () => {
  const Comp = typedComponent({
    a: /^a/i,
  })(RenderProps);
  test('should work', () => {
    render(<Comp a='a' />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should work', () => {
    render(<Comp a='A' />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<Comp a='ba' />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('match key by regex', () => {
  const Regex = typedComponent({
    '/a/': Number,
    [/c/]: Function,
    '/d/': ['hola', 'adios'],
  })(RenderProps);
  test('should work', () => {
    render(<Regex a={2} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should work', () => {
    render(<Regex a={2} c={() => {}} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should work', () => {
    render(<Regex a={2} c={() => {}} d='hola' />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should work', () => {
    render(<Regex d='hola' />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should work', () => {
    render(<Regex d='adios' />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<Regex d='nada' />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<Regex a='a' b='b' />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<Regex a='a' c='b' />);
    expect(global.console.error).toHaveBeenCalledTimes(2);
  });
});

describe('Handle Events', () => {
  const HandleEvents = typedComponent({
    [/^on/]: Function,
  })(RenderProps);
  test('should work', () => {
    render(
      <HandleEvents onClick={() => {}} onHover={() => {}} />,
    );
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<HandleEvents onClick={() => {}} onHover={1} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<HandleEvents onClick={1} onHover={1} />);
    expect(global.console.error).toHaveBeenCalledTimes(2);
  });
});

describe('onOnly valid props', () => {
  const HandleEvents = typedComponent({
    onClick: Function,
    [/.+/]: () => false,
  })(RenderProps);

  test('should warn', () => {
    render(<HandleEvents onClick={() => {}} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<HandleEvents onClick={() => {}} id='id' />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('regex advanced', () => {
  const Regex = typedComponent({
    a: String, // required prop
    '/a|b/': Number, // optional prop (only check props that do not have been check by required props)
  })(RenderProps);
  test('should work', () => {
    render(<Regex a='a' />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should work', () => {
    render(<Regex a='a' b={2} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<Regex />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<Regex b='b' />);
    expect(global.console.error).toHaveBeenCalledTimes(2);
  });
  test('should warn', () => {
    render(<Regex a={2} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<Regex a='a' b='b' />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<Regex a={2} b='b' />);
    expect(global.console.error).toHaveBeenCalledTimes(2);
  });
});

describe('regex check in shapes', () => {
  const Regex = typedComponent({
    shape: {
      '/.+/': Number,
      '/regex/': /regex/,
    },
  })(RenderProps);
  test('should work', () => {
    render(<Regex shape={{ a: 2 }} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<Regex shape={{ a: 2, regex: 'regex' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<Regex shape={{ a: '12', regex: '2' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<Regex shape={{}} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn', () => {
    render(<Regex shape={{ a: [] }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });

  test('should work', () => {
    render(<Regex shape={{ regex: 'regex' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn', () => {
    render(<Regex shape={{ regex: 2 }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should work recursively', () => {
    render(<Regex shape={{ regex: 'regex' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('regex check in shapes mixed', () => {
  const Regex = typedComponent({
    shape: {
      a: Number,
      '/ex/': 'regex',
    },
  })(RenderProps);
  test('should work ', () => {
    render(<Regex shape={{ a: 1, regex: 'regex' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn ', () => {
    render(<Regex shape={{ a: '1' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
  test('should warn ', () => {
    render(<Regex shape={{ a: 1, regex: 1 }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('regex check in shapes recursively', () => {
  const Regex = typedComponent({
    shape: {
      [/.+/]: {
        a: Number,
      },
    },
  })(RenderProps);
  test('should work ', () => {
    render(<Regex shape={{ any: { a: 2 } }} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn ', () => {
    render(<Regex shape={{ whatever: { a: 'a' } }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('regex check in shapes recursively even if complex', () => {
  const Regex = typedComponent({
    shape: {
      [/^price/]: v => v > 0,
      [/^zip/]: String,
    },
  })(RenderProps);
  test('should work ', () => {
    render(<Regex shape={{ priceTotal: 1 }} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn ', () => {
    render(<Regex shape={{ zipCode: 'hw30' }} />);
    expect(global.console.error).toHaveBeenCalledTimes(0);
  });
  test('should warn even if complex', () => {
    render(<Regex shape={{ zip: 123 }} />);
    expect(global.console.error).toHaveBeenCalledTimes(1);
  });
});

describe('Common cases', () => {
  describe('arrayOf', () => {
    const Comp = typedComponent({
      arrayOfStrings: {
        '/[0-9]+/': String,
      },
    })(RenderProps);
    test('should work ', () => {
      render(<Comp arrayOfStrings={['a', 'b']} />);
      expect(global.console.error).toHaveBeenCalledTimes(0);
    });
    test('should warn ', () => {
      render(<Comp arrayOfStrings={['a', 1]} />);
      expect(global.console.error).toHaveBeenCalledTimes(1);
    });
  });

  describe('ObjectOf', () => {
    const Comp = typedComponent({
      obj: {
        '/.+/': Number,
      },
    })(RenderProps);
    test('should work ', () => {
      render(<Comp obj={{ a: 1, b: 2 }} />);
      expect(global.console.error).toHaveBeenCalledTimes(0);
    });
    test('should warn ', () => {
      render(<Comp obj={{ a: 1, b: '2' }} />);
      expect(global.console.error).toHaveBeenCalledTimes(1);
    });
    test('should warn ', () => {
      render(<Comp obj={{ a: 1, b: '2', c:
      '3' }} />);
      expect(global.console.error).toHaveBeenCalledTimes(1);
    });
  });
});