dabakovich / react-native-controlled-mentions

Fully controlled React Native mentions component
MIT License
223 stars 82 forks source link

Hooks-based renderSuggestions function component causes invariant violation #44

Open lafiosca opened 3 years ago

lafiosca commented 3 years ago

Hello! Thank you for building such a useful library and for doing it with TypeScript :)

I am starting to use this library in my app for the first time, and I ran into a minor snag. I created a standalone MentionSuggestions function component to use in the partTypes prop of my MentionInput, but I ran into an invariant violation. Based on the usage example in the README, I expected any function component to work as renderSuggestions, but it turns out that is not the case. My MentionSuggestions component uses hooks, but because of the way the library currently renders the suggestions, the app runs into problems with conditional hook evaluations:

https://github.com/dabakovich/react-native-controlled-mentions/blob/8b286da2bec38ee3a02f7239af474a6e46708ad3/src/components/mention-input.tsx#L118-L125

Now, I realize I could wrap the component in another anonymous function, like you do in this example: https://github.com/dabakovich/react-native-controlled-mentions/issues/17#issuecomment-759360908

But that feels a bit redundant to me, and I'd prefer not to have to do that everywhere that I use the component. I created my own local patch of the library to allow me to use the component directly by rendering it as a React element. I will share a pull request for that change in case you are interested in incorporating it. I will understand if you do not want to incorporate this, as it is potentially a breaking change for people who are returning less typical ReactNode values from their renderSuggestions functions.

DeniferSantiago commented 3 years ago

Can you show how you were trying to implement the component assigned to renderSuggestions?

dabakovich commented 3 years ago

@lafiosca thanks a lot for your detailed feedback and substantive issue. Probably, I prefer to replace the old renderSuggestions with SuggestionsComponent in the next major release.

@DeniferSantiago for now, you can just put a function which returns ReactNode. Here is an example:

<MentionInput
  value={value}
  onChange={setValue}

  partTypes={[
    {
      trigger: '@',
      renderSuggestions: (props) => <YourFunctionalOrClassComponent {...props} />,
      textStyle: {fontWeight: 'bold', color: 'blue'},
    },
  ]}
/>
lafiosca commented 3 years ago

@dabakovich Thank you. I think that approach sounds very sensible. In the meantime I can continue using my patch.

@DeniferSantiago I probably should have given a clearer example of what I was talking about. I think @dabakovich understood, but I will share some more detail here for anyone else curious. Off the top of my head, here's a pseudocode example...

const MentionSuggestions = (props: MentionSuggestionsProps): JSX.Element | null => {
  const users = useSelector(selectUsers);  // <-- here's a hook call
  /* ... */
  return stuff;
}

/* ... */

<MentionInput
  value={value}
  onChange={setValue}

  partTypes={[
    {
      trigger: '@',
      renderSuggestions: MentionSuggestions,
      textStyle: styles.mentionText,
    },
  ]}
/>

Because of the way the renderSuggestions argument is used, in the part of the code referenced by my original post above, the function component cannot use hooks. React doesn't realize that it's rendering a component, so the hooks get attributed to the MentionInput component, which means it is conditionally calling a variable number of hooks, which breaks React's rules. The approach @dabakovich shared above wraps the component in JSX so that React knows to render it as a component and treat its hook calls separately, avoiding the invariant violation. It's just a little more redundant, the tiniest bit less efficient, and requires prop-spreading.

DeniferSantiago commented 3 years ago

I just saw your PR and understood, thanks for detailing it. It seems to me that the idea of ​​implementing it in another version is a good idea.

dabakovich commented 2 years ago

Hi all!

V3 release is almost ready, and you can play with external suggestions rendering using useMentions hook in the alpha pre-release: https://github.com/dabakovich/react-native-controlled-mentions/releases/tag/v3.0.0-alpha.2