Skip to content
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

Cannot read property 'propTypes' of undefined at updateMemoComponent #1230

Closed
natew opened this issue Apr 19, 2019 · 6 comments
Closed

Cannot read property 'propTypes' of undefined at updateMemoComponent #1230

natew opened this issue Apr 19, 2019 · 6 comments

Comments

@natew
Copy link
Contributor

natew commented Apr 19, 2019

Description

Started seeing this one last night, I need to try and isolate it a bit more.

Actual behavior

Getting this error. Specifically, it seems to have after the second save on a certain page. Never the first save, and other pages are working normally.

Uncaught TypeError: Cannot read property 'propTypes' of undefined
    at updateMemoComponent (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:14308)
    at beginWork (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:15486)
    at performUnitOfWork (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:19109)
    at workLoop (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:19149)
    at renderRoot (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:19232)
    at performWorkOnRoot (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:20139)
    at performWork (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:20051)
    at performSyncWork (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:20025)
    at requestWork (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:19894)
    at scheduleWork (webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:19708)
webpack-internal:///../../node_modules/@hot-loader/react-dom/cjs/react-dom.development.js:16914 The above error occurred in the <InnerNotFoundBoundary> component:
    in InnerNotFoundBoundary (created by Context.Consumer)
    in ErrorBoundary (created by Layout)
    in div (created by ForwardRef(Gloss))
    in ForwardRef(Gloss)
    in ForwardRef(Gloss) (created by Layout)
    in SimpleProvider (created by Layout)
    in ThemeByName (created by Theme)
    in Theme (created by Layout)
    in ThemeProvide
    in Provider
    in Provider
    in Provider
    in Provider
    in Unknown (created by Layout)
    in Layout
    in NaviProvider (created by Router)
    in Router
    in Unknown (created by SiteRoot)
    in AppContainer (created by SiteRoot)
    in SiteRoot

Environment

React Hot Loader version: 4.8.4

Have the @hot-loader dom patch, and the following settings:

setConfig({
    logLevel: 'no-errors-please',
    pureSFC: true,
    pureRender: true,
    // disableHotRenderer: true,
  })

The page that it's running on is a bit large, I'll just paste it here anyway:

import {
  BorderRight,
  Button,
  Col,
  gloss,
  Input,
  List,
  Portal,
  RoundButton,
  Row,
  Section,
  Sidebar,
  SpaceGroup,
  SubTitle,
  SurfacePassProps,
  Toolbar,
  useMedia,
} from '@o/ui'
import { compose, mount, route, withView } from 'navi'
import React, { memo, useEffect, useRef, useState } from 'react'
import { useNavigation, View } from 'react-navi'
import { useScreenSize } from '../hooks/useScreenSize'
import { useSiteStore } from '../Layout'
import { Header } from '../views/Header'
import { MDX } from '../views/MDX'
import { SectionContent } from '../views/SectionContent'
import DocsInstall from './DocsPage/DocsInstall.mdx'

const views = {
  install: () => import('./DocsPage/DocsInstall.mdx'),
  buttons: () => import('./DocsPage/DocsButtons.mdx'),
  cards: () => import('./DocsPage/DocsCards.mdx'),
  progress: () => import('./DocsPage/DocsProgress.mdx'),
  lists: () => import('./DocsPage/DocsLists.mdx'),
  start: () => import('./DocsPage/DocsStart.mdx'),
}

export default compose(
  withView(req => {
    const id = req.path.slice(1)
    return (
      <DocsPage id={id}>
        <View />
      </DocsPage>
    )
  }),

  mount({
    '/': route({
      title: 'Docs',
      view: <DocsInstall />,
    }),
    '/:id': route(async req => {
      let id = req.params.id
      if (!views[id]) {
        return {
          view: () => <div>not found</div>,
        }
      }
      let ChildView = (await views[id]()).default || (() => <div>nada {id}</div>)
      return {
        view: <ChildView />,
      }
    }),
  }),
)

function DocsPage(props: { id?: string; children?: any }) {
  const screen = useScreenSize()
  const itemIndex = categories.all.findIndex(x => x['id'] === props.id) || 1
  const item = categories.all[itemIndex]
  const siteStore = useSiteStore()
  const [showSidebar, setShowSidebar] = useState(true)
  const [section, setSection] = useState('all')
  const toggleSection = val => setSection(section === val ? 'all' : val)
  const nav = useNavigation()
  const [search, setSearch] = useState('')
  const inputRef = useRef(null)

  const content = (
    <React.Fragment key="content">
      <DocsToolbar section={section} toggleSection={toggleSection} />
      <List
        search={search}
        selectable
        alwaysSelected
        defaultSelected={itemIndex}
        items={categories[section]}
        onSelect={rows => {
          nav.navigate(`/docs/${rows[0].id}`, { replace: true })
        }}
      />
    </React.Fragment>
  )

  const isSmall = screen === 'small'

  useEffect(() => {
    const keyPress = e => {
      // console.log('e', e.keyCode)
      switch (e.keyCode) {
        case 84: // t
          inputRef.current.focus()
          break
      }
    }
    window.addEventListener('keydown', keyPress)
    return () => {
      window.removeEventListener('keydown', keyPress)
    }
  }, [])

  useEffect(() => {
    inputRef.current && inputRef.current.focus()
  }, [inputRef.current])

  return (
    <MDX>
      <Portal prepend style={{ position: 'sticky', top: 10, zIndex: 10000000 }}>
        <Row margin={[0, 'auto']} pointerEvents="auto" pad={['sm', 100]} width="90%" maxWidth={800}>
          <Input
            ref={inputRef}
            onChange={e => setSearch(e.target.value)}
            flex={1}
            sizeRadius={10}
            size="xl"
            iconSize={16}
            icon="search"
            placeholder="Search the docs..."
            elevation={5}
            after={
              <Button tooltip="Shortcut: t" size="sm" alt="flat" fontWeight={600}>
                t
              </Button>
            }
          />
        </Row>
      </Portal>
      <Portal prepend>
        <Header slim />
      </Portal>
      <Portal>
        <FixedLayout>
          {isSmall ? (
            <Sidebar
              hidden={!showSidebar}
              zIndex={10000000}
              elevation={5}
              pointerEvents="auto"
              // @ts-ignore
              background={theme => theme.background}
            >
              {content}
            </Sidebar>
          ) : (
            <SectionContent pointerEvents="none" flex={1}>
              <Col position="relative" flex={1} width={300} pointerEvents="auto">
                {content}
                <BorderRight opacity={0.5} />
              </Col>
            </SectionContent>
          )}
        </FixedLayout>
      </Portal>

      <SectionContent fontSize={16} lineHeight={28}>
        <ContentPosition isSmall={isSmall}>
          <DocsContents
            onToggleSidebar={() => setShowSidebar(!showSidebar)}
            setTheme={siteStore.setTheme}
            theme={siteStore.theme}
            title={item ? item['title'] : undefined}
          >
            {props.children}
          </DocsContents>
        </ContentPosition>
      </SectionContent>
    </MDX>
  )
}

DocsPage.theme = 'light'

const ContentPosition = gloss<{ isSmall?: boolean }>({
  width: '100%',
  padding: [0, 0, 0, 300],
  isSmall: {
    padding: [0, 0, 0, 0],
  },
})

const FixedLayout = gloss({
  position: 'fixed',
  top: 100,
  left: 0,
  width: '100%',
  height: '100%',
  zIndex: 100000,
})

const DocsToolbar = memo(({ section, toggleSection }: any) => {
  return (
    <Toolbar background="transparent" pad="xs" justifyContent="center" border={false}>
      <RoundButton
        alt={section === 'docs' ? 'selected' : 'flat'}
        onClick={() => toggleSection('docs')}
      >
        Docs
      </RoundButton>
      <RoundButton alt={section === 'ui' ? 'selected' : 'flat'} onClick={() => toggleSection('ui')}>
        UI
      </RoundButton>
      <RoundButton
        alt={section === 'kit' ? 'selected' : 'flat'}
        onClick={() => toggleSection('kit')}
      >
        Kit
      </RoundButton>
    </Toolbar>
  )
})

const DocsContents = memo(({ setTheme, theme, title, onToggleSidebar, children }: any) => {
  const isSmall = useMedia({ maxWidth: 700 })
  return (
    <Section
      maxWidth={600}
      margin={[0, 'auto']}
      pad={['xl', 'xl', true, 'xl']}
      titleBorder
      space
      title={title || 'No title'}
      titleSize="xl"
      afterTitle={
        <SurfacePassProps iconSize={12}>
          <SpaceGroup space="xs">
            <Button
              icon="moon"
              tooltip="Toggle dark mode"
              onClick={() => setTheme(theme === 'home' ? 'light' : 'home')}
            />
            {isSmall && (
              <Button icon="panel-stats" tooltip="Toggle menu" onClick={onToggleSidebar} />
            )}
          </SpaceGroup>
        </SurfacePassProps>
      }
    >
      {children}
    </Section>
  )
})

const titleItem = { titleProps: { size: 1.1 } }

const ListSubTitle = gloss(SubTitle, {
  margin: [20, 0, -2],
  fontWeight: 300,
  fontSize: 18,
})

const docsItems = [
  {
    selectable: false,
    hideBorder: true,
    children: <ListSubTitle>Start</ListSubTitle>,
  },
  {
    id: 'start',
    title: 'Getting started',
    ...titleItem,
  },
]

const uiItems = [
  {
    selectable: false,
    hideBorder: true,
    children: <ListSubTitle>User Interface</ListSubTitle>,
  },

  { id: 'lists', title: 'Lists', icon: 'th-list', group: 'Collections' },
  { id: 'tables', title: 'Tables', icon: 'th' },
  { id: 'tree', title: 'Tree', icon: 'diagram-tree' },
  { id: 'treeList', title: 'TreeList', icon: 'chevron-right' },
  { id: 'definitionList', title: 'DefinitionList', icon: 'list-columns' },

  {
    group: 'Views',
    id: 'surfaces',
    icon: 'layer',
    title: 'Surface',
    subTitle: 'Building block of many views',
  },
  { id: 'icons', icon: 'star', title: 'Icons', indent: 1 },
  { id: 'buttons', icon: 'button', title: 'Buttons', indent: 1 },
  { id: 'cards', title: 'Cards', icon: 'credit-card', indent: 1 },
  { id: 'install', title: 'Sections', icon: 'application' },
  { id: 'install', title: 'Popovers', icon: 'direction-right' },
  { id: 'install', title: 'Decorations', icon: 'clean' },
  { id: 'progress', title: 'Progress', icon: 'circle' },
]

const categories = {
  all: [...docsItems, ...uiItems],
  ui: uiItems,
  docs: docsItems,
  kit: uiItems,
}
@natew
Copy link
Contributor Author

natew commented Apr 19, 2019

Seems like what's tripping it up is the <ListSubTitle>Start</ListSubTitle>, that view is a a memo(forwardRef()) component, possible the usage of it inline in the file.

@theKashey
Copy link
Collaborator

Sorry - there is no way I could reproduce the problem, but you are trying to memo(undefined)

@Dr-Nikson
Copy link

Dr-Nikson commented Apr 29, 2019

I'm facing same error when using react.memo!

@theKashey

but you are trying to memo(undefined)

Not exactly... debugger says:

updateMemoComponent(/*...*/) {
    // ...
    var _type = Component.type; // <-- Component is a function and has no type!
    var _innerPropTypes = _type.propTypes; // <-- _type is undefined
    if (_innerPropTypes) {
      // Inner memo component props aren't currently validated in createElement.
      // We could move it there, but we'd still need this for lazy code path.
      checkPropTypes(_innerPropTypes, nextProps, // Resolved props
      'prop', getComponentName(_type), getCurrentFiberStackInDev);
    }
}

For some components it works, but for others - fails. Maybe it depends when memoized component is being used directly with some other HOC.

In my case: WithContext is a HOC, WorstInstruments is a memoized component

image

And I'm always getting hot-update error.

// withContext

 *
 * @param {React.Context<any>} Context
 * @param [opts = {}]
 *  @param [opts.mergeProps]
 * @returns {function(React.ComponentType<any>): React.ComponentType<any>}
 */
const withContext = (Context, opts = {}) => WrappedComponent => {
  const { mergeProps = defaultMergeProps } = opts;

  function WithContext(props) {
    return (
      <Context.Consumer>
        {ctx => {
          const propsToWrapped = mergeProps(ctx, props);

          return <WrappedComponent {...propsToWrapped} />;
        }}
      </Context.Consumer>
    );
  }

  return hoistNonReactStatics(WithContext, WrappedComponent);
};

@theKashey
Copy link
Collaborator

Probably I've found a problem. There are two different "memo" components - a MemoComponent, and SimpleMemoComponent.

RHL works for the second one, breaks for the first. I don't get yet, where which one is used(and how to create a test for it), but it's clear how I am breaking it

@theKashey
Copy link
Collaborator

theKashey commented May 12, 2019

This issue should be solved simultaneously with #1135, ie since v4.6.3.

I am trying to write a failing test, or at least some example to play with, not it always works :(

PS: testing with react-dom 16.8.6

@theKashey
Copy link
Collaborator

v4.8.5 should fix this

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

No branches or pull requests

3 participants