3

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?

Trevor Hewitt
  • 289
  • 2
  • 9
  • 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 Jun 04 '20 at 23:15
  • 2
    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](https://stackoverflow.com/questions/56047659/multiple-props-options-for-styled-components/56049471#56049471) shows how to pass props and set dynamic styles based upon them. In your case, pass props to `Input` like so: ``, then you can access `color` within the styled-component template literal: `background: ${({ color }) => color };`. – Matt Carlotta Jun 04 '20 at 23:29
  • Excellent, I knew I must have been missing something in styled-components. Thank you. I updated the example with the proper implementation. https://stackblitz.com/edit/react-mu6rlr-omeo5c?file=app%2Fmain.jsx @MattCarlotta – Trevor Hewitt Jun 05 '20 at 00:07
  • @MattCarlotta there are times when there are props to pass in so many places that it really would be nicer to have the styled components capture the context. I tried useMemo and got this same problem. I understand that you're supposed to define them outside, but is there a way around it for cases where it's too messy to pass all the props? – Jonathan Tuzman Mar 28 '22 at 16:13
  • @JonathanTuzman Have you tried using [React Context](https://reactjs.org/docs/context.html)? If not, that's the better approach. Otherwise, you can use styled-components [theming](https://styled-components.com/docs/advanced). – Matt Carlotta Mar 28 '22 at 19:50

1 Answers1

1

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

Trevor Hewitt
  • 289
  • 2
  • 9