Search code examples
reactjsreact-hooksstyled-componentskendo-react-ui

Memoizing a dynamic styled-component causes "Rendered fewer hooks than expected error."


I'm using styled-components. I have to use it to make the style changes because what I need to change is nested inside Kendo React Grid, as they outline in their docs: https://www.telerik.com/kendo-react-ui/components/styling/styled-components/

I need to style the component dynamically based on props. The problem this creates is that, because a new component is created every render cycle, text inputs lose focus as you type. I tried to wrap the component in useMemo to solve this, but it causes a "Rendered fewer hooks than expected" error. It seems useRef is being called in styled() from styled-components, so when it subsequently is skipped because of useMemo, it creates the hooks mismatch.

I created a simpler example here: https://stackblitz.com/edit/react-mu6rlr-omeo5c?file=app%2Fmain.jsx

function CustomText(props){
  const [state, setState] = useState('text')
  const {color} = props

  const Input = styled('input')`
    background-color: ${color}
  `

  return <Input value={state} onChange={useCallback(event => setState(event.target.value))}/>
}
// loses focus on update


function CustomTextMemo(props){
  const [state, setState] = useState('memoized')
  const {color} = props

  const Input = useMemo(
    () => styled('input')`
      background-color: ${color}
    `,
    [color]
  )

  return <Input value={state} onChange={useCallback(event => setState(event.target.value))}/>
}
// "Rendered fewer hooks than expected. This may be caused by an accidental early return statement."

The top text box loses focus on update. The lower memoized one hits the hook error.

What is a better pattern for solving this?


Solution

  • As Matt Carlotta pointed out in his comment, I was using an anti-pattern by defining the styled-component within the functional component. I thought it was necessary to define it where props were in scope in order to use props for the styling. What I'd missed in styled-components is that you can define the styles as functions of props, and it will behave as expected. I updated the example with the correct implementation.

    stackblitz.com/edit/react-mu6rlr-omeo5c?file=app%2Fmain.jsx