2
votes

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?

1
is it meant to be const {color} = props.color? Also, i have solved this problem before but cannot recall how i did it. Can you add in more information such as what was before the function CustomText(props){Alexander Hemming
It's an anti-pattern defining it within a functional component. Instead, it should be defined outside of a component (or in its own file) and passed props. This example shows how to pass props and set dynamic styles based upon them. In your case, pass props to Input like so: <Input color={color} onChange{...} value={...}/>, then you can access color within the styled-component template literal: background: ${({ color }) => color };.Matt Carlotta
Excellent, I knew I must have been missing something in styled-components. Thank you. I updated the example with the proper implementation. stackblitz.com/edit/react-mu6rlr-omeo5c?file=app%2Fmain.jsx @MattCarlottaTrevor Hewitt

1 Answers

1
votes

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