diff --git a/README.md b/README.md index 8e4f6df..3820fe9 100644 --- a/README.md +++ b/README.md @@ -407,6 +407,32 @@ This is not necessary if you use React Router 4.4+. You can find more details an

+
+Usage with React Developer Tools. +

+ +If you want React Developer Tools to recognize your reactive view components with names, you have to pass a **named component** to the `view` wrapper function instead of an anonymous one. + +```jsx +import React from 'react'; +import { view, store } from 'react-easy-state'; +import Table from 'rc-table'; +import cloneDeep from 'lodash/cloneDeep'; + +const user = store({ + name: 'Rick', +}); + +const componentName = () => ( +
{user.name}
+); + +export default view(componentName); +``` + +
+

+
Passing nested data to third party components.

diff --git a/__tests__/staticProps.test.js b/__tests__/staticProps.test.js deleted file mode 100644 index ddd9151..0000000 --- a/__tests__/staticProps.test.js +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable react/forbid-foreign-prop-types */ -/* eslint-disable no-multi-assign */ -import { Component } from 'react'; -// eslint-disable-next-line import/no-unresolved -import { view } from 'react-easy-state'; - -describe('static props', () => { - test('view() should proxy static properties from wrapped components', () => { - class Comp extends Component {} - function FuncComp() {} - - Comp.displayName = FuncComp.displayName = 'Name'; - Comp.contextTypes = FuncComp.contextTypes = {}; - Comp.propTypes = FuncComp.propTypes = {}; - Comp.defaultProps = FuncComp.defaultProps = {}; - Comp.customProp = FuncComp.customProp = {}; - - const ViewComp = view(Comp); - const ViewFuncComp = view(FuncComp); - - expect(ViewComp.displayName).toBe(Comp.displayName); - expect(ViewComp.contextTypes).toBe(Comp.contextTypes); - expect(ViewComp.propTypes).toBe(Comp.propTypes); - expect(ViewComp.defaultProps).toBe(Comp.defaultProps); - expect(ViewComp.customProp).toBe(Comp.customProp); - - expect(ViewFuncComp.displayName).toBe(FuncComp.displayName); - expect(ViewFuncComp.contextTypes).toBe(FuncComp.contextTypes); - expect(ViewFuncComp.propTypes).toBe(FuncComp.propTypes); - expect(ViewFuncComp.defaultProps).toBe(FuncComp.defaultProps); - expect(ViewFuncComp.customProp).toBe(FuncComp.customProp); - }); - - test('view() should proxy static methods', () => { - class Comp extends Component { - static getDerivedStateFromError() {} - - static customMethod() {} - } - - const ViewComp = view(Comp); - expect(ViewComp.getDerivedStateFromError).toBe( - Comp.getDerivedStateFromError, - ); - expect(ViewComp.customMethod).toBe(Comp.customMethod); - }); - - test('view() should proxy static getters', () => { - class Comp extends Component { - static get defaultProp() { - return { key: 'value' }; - } - - static get customProp() { - return { key: 'hello' }; - } - } - - const ViewComp = view(Comp); - expect(ViewComp.defaultProps).toEqual(Comp.defaultProps); - expect(ViewComp.customProp).toEqual(Comp.customProp); - }); -}); diff --git a/__tests__/staticProps.test.jsx b/__tests__/staticProps.test.jsx new file mode 100644 index 0000000..2902854 --- /dev/null +++ b/__tests__/staticProps.test.jsx @@ -0,0 +1,115 @@ +/* eslint-disable react/forbid-foreign-prop-types */ +/* eslint-disable no-multi-assign */ +import React, { Component } from 'react'; +import { render, cleanup } from '@testing-library/react/pure'; +// eslint-disable-next-line import/no-unresolved +import { view } from 'react-easy-state'; +import PropTypes from 'prop-types'; + +describe('static props', () => { + afterEach(cleanup); + + test('view() should proxy defaultProps for class components', () => { + class MyCustomCompName extends Component { + render() { + return
{this.props.name}
; + } + } + + MyCustomCompName.defaultProps = { + name: 'Bob', + }; + + const WrappedComp = view(MyCustomCompName); + const { container } = render(); + expect(container).toHaveTextContent('Bob'); + }); + + test('view() should proxy defaultProps for functional components', () => { + const MyCustomCompName = props => { + return
{props.name}
; + }; + + MyCustomCompName.defaultProps = { + name: 'Bob', + }; + + const WrappedComp = view(MyCustomCompName); + const { container } = render(); + expect(container).toHaveTextContent('Bob'); + }); + + test('view() should proxy propTypes for class components', () => { + class MyCustomCompName extends Component { + render() { + return
{this.props.name}
; + } + } + + MyCustomCompName.propTypes = { + name: PropTypes.string.isRequired, + }; + + const ViewComp = view(MyCustomCompName); + + const errorSpy = jest + .spyOn(console, 'error') + .mockImplementation(message => + expect(message.indexOf('Failed prop type')).not.toBe(-1), + ); + render(); + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); + + test('view() should proxy propTypes for functional components', () => { + const MyCustomCompName = props => { + return
{props.number}
; + }; + + MyCustomCompName.propTypes = { + number: PropTypes.number.isRequired, + }; + + const ViewComp = view(MyCustomCompName); + + const errorSpy = jest + .spyOn(console, 'error') + .mockImplementation(message => + expect(message.indexOf('Failed prop type')).not.toBe(-1), + ); + render(); + expect(errorSpy).toHaveBeenCalled(); + errorSpy.mockRestore(); + }); + + test('view() should proxy static methods', () => { + class Comp extends Component { + static getDerivedStateFromError() {} + + static customMethod() {} + } + + const ViewComp = view(Comp); + expect(ViewComp.getDerivedStateFromError).toBe( + Comp.getDerivedStateFromError, + ); + expect(ViewComp.customMethod).toBe(Comp.customMethod); + }); + + test('view() should proxy static getters', () => { + class Comp extends Component { + static get defaultProp() { + return { key: 'value' }; + } + + static get customProp() { + return { key: 'hello' }; + } + } + + const ViewComp = view(Comp); + expect(ViewComp.defaultProps).toEqual(Comp.defaultProps); + expect(ViewComp.customProp).toEqual(Comp.customProp); + }); +}); diff --git a/src/view.js b/src/view.js index 73ca6ea..fa919b3 100644 --- a/src/view.js +++ b/src/view.js @@ -74,8 +74,6 @@ export function view(Comp) { isInsideFunctionComponent = false; } }; - ReactiveComp.displayName = Comp.displayName || Comp.name; - ReactiveComp = memo(ReactiveComp); } else { const BaseComp = isStatelessComp ? Component : Comp; // a HOC which overwrites render, shouldComponentUpdate and componentWillUnmount @@ -170,5 +168,7 @@ export function view(Comp) { }); } - return ReactiveComp; + return isStatelessComp && hasHooks + ? memo(ReactiveComp) + : ReactiveComp; }