Skip to content

PropTypes for Preact #902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
rumkin opened this issue Oct 5, 2017 · 14 comments
Closed

PropTypes for Preact #902

rumkin opened this issue Oct 5, 2017 · 14 comments

Comments

@rumkin
Copy link

rumkin commented Oct 5, 2017

Hi. I've realised PropTypes's alike extensible type checker which produce nice-looking report object. Instead of PropTypes which print's some output into console. And I want to add integration for preact. Is there any way to make it play together without patching preact itself?

It could be used like so:

const TypedProps = require('typed-props');

const userShape = TypedProps.shape({
    username: TypedProps.string.isRequired,
    age: TypedProps.number,
});

TypedProps.check({}, userShape); // -> returns array of issues: {path:string[], rule:string, params:{}}

Thanks for any advise.

P.S. Here it is.

@pl12133
Copy link
Contributor

pl12133 commented Oct 5, 2017

Hey @rumkin thanks for the issue. Preact offers an (undocumented) hook at the end of the h function which is called with every created vNode. You can use this hook to monkey-patch class methods. In this way, I think you could achieve checking PropTypes on arbitrary class methods. Here is an example of patching render with the prop-types package from npm:

import { options } from 'preact';
import PropTypes from 'prop-types';


function patchWithPropTypes(PatchingComponent) {
	let name = PatchingComponent.displayName || PatchingComponent.name || 'component';
	let render = PatchingComponent.prototype.render;

	PatchingComponent.__patchedWithPropTypes = true;

	if (render) {
		PatchingComponent.prototype.render = function renderWithPropTypes(props, state, ctx) {
			if (PatchingComponent.propTypes) {
				for (let prop in props) {
					PropTypes.checkPropTypes(PatchingComponent.propTypes, props, prop, name);
				}
			}
			return render.call(this, props, state, ctx);
		}
	}
}

let nextVnode = options.vnode;
options.vnode = vnode => {
	if (typeof vnode.nodeName==='function' && !vnode.nameNode.__patchedWithPropTypes) {
		patchWithPropTypes(vnode.nodeName)
	}
	if (nextVnode) {
		nextVode(vnode);
	}
}

I hope this helps you extend preact with your own project. If you get this working, it would be great to hear if this has a performance impact on your code.

@developit
Copy link
Member

Also worth noting: preact-compat does PropType checking right now in a slightly odd way, but @tkh44 has opened a PR that works very similarly to @pl12133's suggestion above. You can see it here:
preactjs/preact-compat#370

@marvinhagemeister
Copy link
Member

Looks like we use prop-types + preact today without any necessary changes to core 🎉

@rumkin If you feel like the posted solutions don't solve your problem feel free to ping me to reopen this issue.

@pirelenito
Copy link

Hi @marvinhagemeister, what do you mean when you say:

Looks like we use prop-types + preact today without any necessary changes to core 🎉

I've tried just importing prop-types and juts anotating a component, such as:

Button.propTypes = {
	children: PropTypes.any.isRequired,
	variant: PropTypes.oneOf(['primary', 'secondary', 'cta']),
}

But there was no warning in the console.

I only managed to make it work by using the solution proposed by @pl12133 with a custom hack (based on preact-compact) that you can check on this gist. However that doesn't seem to work very well and I'm not really understanding what I'm doing 😅.

What is the recommended way going forward if I want to use prop-types with preact?

Cheers ❤️

@marvinhagemeister
Copy link
Member

I stand corrected, we indeed don't support it out of the box in core :/ I'll try to get this into Preact X before it's out.

@marvinhagemeister
Copy link
Member

Just an FYI: PropType checking will be possible in Preact X itself. That means that compat won't be needed just for PropTypes anymore. Merged the PR for it yesterday 🎉

@DonGissel
Copy link

Hot diggety! Thanks!

@marvinhagemeister
Copy link
Member

We just released the alpha for Preact X which will check for prop-types via preact/debug. It just needs to be included once (done automatically in preact-cli) to get prop-type checking to work 🎉

@shawnwall
Copy link

shawnwall commented May 29, 2019

@marvinhagemeister thanks for the info. If I'm not using preact-cli, what is the preferred method of requiring/including preact/debug for development builds to get prop-type checking working with webpack?

@ForsakenHarmony
Copy link
Member

it should be included by default

@shawnwall
Copy link

@ForsakenHarmony I'm not seeing that in my current setup. Only if I manually require preact/debug in one of my files do proptypes begin to actually validate.

@ForsakenHarmony
Copy link
Member

Oh sorry I misread, I thought you were using preact-cli

@ForsakenHarmony
Copy link
Member

https://github.com/preactjs/preact-cli/blob/d1718255d9a81c52ad022f3308e53bdda9917bc7/packages/cli/lib/lib/entry.js#L7

You can copy what we do there though, only enable it in dev, the check gets removed in a production build because of dead code elimination

@Alexpol19
Copy link

Hi @pirelenito.
Your gist looks good. Just a little outdated.
Here is an update with the newest version of preact: 10.3.4 and prop-types: 15.8.1 :


/**
 * Custom implementation that enables PropTypes checks in preact
 *
 * Based on:
 * - https://github.com/developit/preact-compat
 * - https://github.com/developit/preact/issues/902#issuecomment-334507942
 */

import { options, Component } from 'preact'
import PropTypes from 'prop-types'

const COMPONENT_WRAPPER_KEY = '__preactCompatWrapper'

function patchWithPropTypes(vnode) {
	if (!vnode.preactCompatNormalized) {
		normalizeVNode(vnode)
	}
	
	const PatchingComponent = vnode.type
	let name = PatchingComponent.displayName || PatchingComponent.name || 'component'
	let render = PatchingComponent.prototype.render
	PatchingComponent.__patchedWithPropTypes = true

	if (render) {
		PatchingComponent.prototype.render = function renderWithPropTypes(props, state, ctx) {
			if (PatchingComponent.propTypes) {
				PropTypes.checkPropTypes(PatchingComponent.propTypes, props, 'prop', name)
			}
			return render.call(this, props, state, ctx)
		}
	}
}

function normalizeVNode(vnode) {
	vnode.preactCompatNormalized = true

	if (isStatelessComponent(vnode.type)) {
		vnode.type = statelessComponentHook(vnode.type)
	}

	return vnode
}

function statelessComponentHook(Ctor) {
	let Wrapped = Ctor[COMPONENT_WRAPPER_KEY]
	if (Wrapped) return Wrapped === true ? Ctor : Wrapped

	Wrapped = wrapStatelessComponent(Ctor)

	Object.defineProperty(Wrapped, COMPONENT_WRAPPER_KEY, { configurable: true, value: true })
	Wrapped.displayName = Ctor.displayName
	Wrapped.propTypes = Ctor.propTypes
	Wrapped.defaultProps = Ctor.defaultProps
	Wrapped.__docgenInfo = Ctor.__docgenInfo

	Object.defineProperty(Ctor, COMPONENT_WRAPPER_KEY, { configurable: true, value: Wrapped })

	return Wrapped
}

function isStatelessComponent(c) {
	return typeof c === 'function' && !(c.prototype && c.prototype.render)
}

function wrapStatelessComponent(wrappedComponent) {
	class NewComponent extends Component {
		render() {
			return wrappedComponent(this.props, this.context)
		}
	}

	return NewComponent
}

let nextVnode = options.vnode
options.vnode = vnode => {
	if (typeof vnode.type === 'function' && !vnode.type.__patchedWithPropTypes) {
		patchWithPropTypes(vnode)
	}
	if (nextVnode) {
		nextVnode(vnode)
	}
}

Usage: Just add this to your project's index.js or import it as a simple file before the first preact render.
Also for PropTypes to work, you need the NODE_ENV environment variable to be set to "development".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants