WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.46k stars 4.18k forks source link

Nested useSelect throwing error #17817

Closed augustuswm closed 5 years ago

augustuswm commented 5 years ago

While working on a hook library we ran into an issue that looks to stem from interactions between nested useSelect calls. I've tried to create a reduced example to trigger the error. Unsure if this is an error on our part with regards to usage of the hook / select, or a bug elsewhere.

To reproduce With SCRIPT_DEBUG set to true. Use the following to create a custom block and add it to a post. Save a draft of the post, and reload. React should report a warning of:

   Previous render            Next render
   ------------------------------------------------------
1. useCallback                useCallback
2. useContext                 useContext
3. useContext                 useContext
4. useMemo                    useMemo
5. useReducer                 useReducer
6. useRef                     useRef
7. useRef                     useRef
8. useRef                     useRef
9. useRef                     useRef
10. useRef                    useRef
11. useCallback               useLayoutEffect
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

and an error of:

Uncaught TypeError: Cannot read property 'length' of undefined
    at areHookInputsEqual (react-dom.js?ver=16.9.0:15190)
    ...

Test Hook:

let { useSelect } = wp.data;

function useTest() {
  return useSelect(s1 => {
    return useSelect(select => {
      let mediaId = select('core/editor').getEditedPostAttribute('featured_media');
      let entities = select('core').getEntityRecords('postType', 'attachment', { include: [mediaId] });

      return entities && entities.length > 0 && entities[0].id;
    })
  }, ["STATIC_VALUE"]);
}

Test Block:

let { registerBlockType } = wp.blocks;

registerBlockType('test/test', {
  title: 'TestBlock',
  description: 'useSelectTest',
  category: 'common',
  edit: function() {
    let value = useTest();
    return <div>{value}</div>;
  },
  save: function() {
    return null;
  }
});

Expected behavior This block should display an empty div or a div with the id of the featured media when one is assigned.

Environment Chrome 77.0.3865.90 macOS 10.14.5 WordPress 5.3-beta2-46382 Twenty Twenty (twentytwenty) 1.0

swissspidy commented 5 years ago

Curious: Why exactly are you nesting useSelect calls like that? You're not even using s1 in your example code.

augustuswm commented 5 years ago

Yeah, I should have noted, this example is pretty contrived to reduce it down. The actual implementations are more along the lines of nested hooks. Something like:

useFeaturedImage() {
  return useSelect(select => {
    return // .. use select to get featured image ...
  }
}

usePost() {
  return useSelect(select => {
    let image = useFeaturedImage();
    // ... use select to retrieve some other data...
  })
}

A number of smaller hooks that get composed.

Edit: Actually I think the use case here is invalid and probably violates the Hooks rule of not calling hooks in conditionals / loops / nested functions.