最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why can't React's forwardRef work well with Material UI's styled? - Stack Overflow

programmeradmin0浏览0评论

When using forwardRef with styled, I see this odd error : "React Hook ... cannot be called inside a callback. React Hooks must be called in a React function ponent or a custom React Hook function."

For example:

import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

const FooComponent = styled(({ className }) => {
  const { t } = useTranslation();
  return <div className={ className }>{ t('This is fine') }</div>;
})(() => ({
  // CSS styles...
});
import { forwardRef } from 'react';
import { useTranslation } from '../lib/i18n';

const FooComponent = forwardRef(((), ref) => {
  const { t } = useTranslation();
  return <div ref={ref}>{ t('This is fine, too!') }</div>;
});
import { forwardRef } from 'react';
import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

const FooComponent = forwardRef( styled(({ className }, ref) => {
  const { t } = useTranslation();    // ERROR here!
  return <div ref={ref} className={ className }>{ t('This is NOT fine') }</div>; 
})(() => ({
  // CSS styles...
})));

Why is there an error in the 3rd ponent? Is the only solution to have a custom ref prop?

When using forwardRef with styled, I see this odd error : "React Hook ... cannot be called inside a callback. React Hooks must be called in a React function ponent or a custom React Hook function."

For example:

import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

const FooComponent = styled(({ className }) => {
  const { t } = useTranslation();
  return <div className={ className }>{ t('This is fine') }</div>;
})(() => ({
  // CSS styles...
});
import { forwardRef } from 'react';
import { useTranslation } from '../lib/i18n';

const FooComponent = forwardRef(((), ref) => {
  const { t } = useTranslation();
  return <div ref={ref}>{ t('This is fine, too!') }</div>;
});
import { forwardRef } from 'react';
import { styled } from '@mui/material/styles';
import { useTranslation } from '../lib/i18n';

const FooComponent = forwardRef( styled(({ className }, ref) => {
  const { t } = useTranslation();    // ERROR here!
  return <div ref={ref} className={ className }>{ t('This is NOT fine') }</div>; 
})(() => ({
  // CSS styles...
})));

Why is there an error in the 3rd ponent? Is the only solution to have a custom ref prop?

Share Improve this question asked Jan 23, 2023 at 16:08 Yanick RochonYanick Rochon 53.6k29 gold badges145 silver badges226 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 8

The main issue with your code is that styled() doesn't expect a second ref parameter, and doesn't know what to do with it. In contrast, forwardRef() does expect you to provide a function with a second ref parameter.

So to solve your problem the way you apply forwardRef() and styled() should be swapped (note that parentheses also slightly change).

const FooComponent = styled( forwardRef(({ className }, ref) => {
  const { t } = useTranslation();
  return <div ref={ref} className={ className }>{ t('This is NOT fine') }</div>; 
}))(() => ({
  // CSS styles...
}));

// or maybe easier to understand

const UnstyledFooComponent = forwardRef(({ className }, ref) => {
  const { t } = useTranslation();
  return <div ref={ref} className={ className }>{ t('This is NOT fine') }</div>; 
});

const styling = (() => ({
  // CSS styles...
});

const FooComponent = styled(UnstyledFooComponent)(styling);

This will pass the the (props, ref) => ... callback to forwardRef(), which understands the second ref parameter.

We'll then pass the resulting ponent to styled().

It's then up to styled() to pass the ref, from the trickery that styled() does, down to your ponent. Luckily styled() keeps this into account and creates a styled ponent that forwards the ref down.

If styled() didn't forward the ref down things would get a lot more plicated.

const { useRef, useEffect, forwardRef } = React;
const { styled } = MaterialUI;

const FooComponent = styled(forwardRef(({ className }, ref) => {
  const { t } = I18n.useTranslation();
  return <div ref={ref} className={ className }>{ t('This is fine') }</div>;
}))(() => ({
  backgroundColor: "red"
}));

function App() {
  const fooRef = useRef();

  useEffect(() => {
    console.log(fooRef.current);
  }, []);
  
  return <FooComponent className="foo" ref={fooRef} />;
}

// mock `useTranslation()`
const I18n = React.createContext();
I18n.useTranslation = function () {
  const translations = React.useContext(I18n);

  const t = React.useCallback((text) => {
    if (!(text in translations))
      throw new Error(`no translation found for "${text}"`);

    return translations[text];
  }, [translations]);
      
  return { t, translations };
};

const translations = {
  nl: {
    "This is fine": "Dit is oké",
  }
};

ReactDOM
  .createRoot(document.querySelector("#root"))
  .render(
    <React.StrictMode>
      <I18n.Provider value={translations.nl}>
        <App />
      </I18n.Provider>
    </React.StrictMode>
  );
<link rel="stylesheet" href="https://fonts.googleapis./css?family=Roboto:300,400,500,700&display=swap" />
<script crossorigin src="https://unpkg./react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg./react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg./@mui/material@5/umd/material-ui.development.js"></script>
<div id="root"></div>

发布评论

评论列表(0)

  1. 暂无评论