Sallwa
Sallwa

Reputation: 81

"Function components cannot be given refs" with forwardRef and connect in function component

Parent component (I deleted some irrelavant code):

import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react
import InputItem from 'src/components/InputItem'

 let tokenRef = useRef(null)
 return (
         <InputItem
           ref={tokenRef}
           placeHolder="xxxx"
         />
 )
}
const mapStateToProps = ({ form }) => {
 return {
   form
 }
}
const mapDispatchToProps = (dispatch, props) => {
 return {}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header)

Child function component (I deleted some irrelavant code):

import React, {
  useState,
  useRef,
  useMemo,
  forwardRef,
  useImperativeHandle,
  Ref
} from 'react'
import './index.less'
import { connect } from 'react-redux'
interface IOwnProps {
  placeHolder?: string
}
interface IStateProps {
  form: any
}
interface IDispatchProps {
}
type IProps = IStateProps & IOwnProps & IDispatchProps
const InputItem = forwardRef((props: IProps, ref: Ref<any>) => {
  const {
    placeHolder,
  } = props
  const ipt = useRef(null)

  useImperativeHandle(ref, () => {
    return {
      value: ipt.current.value,
      focus: () => ipt.current.focus()
    }
  })

  return (
    <div className="input-item" style={style}>
          <input
            type="text"
            placeholder={placeHolder}
            ref={ipt}
          ></input>
    </div>
  )
})
const mapStateToProps = ({ form }) => {}
const mapDispatchToProps = (dispatch, props) => {}
export default connect(mapStateToProps, mapDispatchToProps)(InputItem)

I get a warning which says I can't use a ref in a function component. However, I did use forwardRef in my child component to avoid such problems and it still won't work change useRef value to the wrapped component.

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Upvotes: 1

Views: 5451

Answers (1)

ford04
ford04

Reputation: 74500

You can add the { forwardRef : true } option:

export default connect(
  mapStateToProps, 
  mapDispatchToProps,
  null,
  { forwardRef: true } // this is the important line
)(InputItem)

const Comp = React.forwardRef(({counter, increment}, ref) => (
  <div ref={ref}>
    <p>Comp. counter state: {counter}</p>
    <button onClick={increment}>Increment</button>
  </div>
));

// a simple implementation with increment counter action
const store = Redux.createStore(s => s + 1, 0);
const mapStateToProps = state => ({ counter: state });
const mapDispatchToProps = dispatch => ({
  increment: () => dispatch({type: "increment"})
});

const ConnectedComp = ReactRedux.connect(
  mapStateToProps,
  mapDispatchToProps,
  null,
  { forwardRef: true } // this is the important line
)(Comp);

const App = () => {
  const setBlueColor = () => ref.current.style.color = "blue"
  const ref = React.useRef();
  return (
    <ReactRedux.Provider store={store}>
      <ConnectedComp ref={ref} />
      <button onClick={setBlueColor}>Set blue color</button>
    </ReactRedux.Provider>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js" integrity="sha256-JuJho1zqwIX4ytqII+qIgEoCrGDVSaM3+Ul7AtHv2zY=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js" integrity="sha256-7nQo8jg3+LLQfXy/aqP5D6XtqDQRODTO18xBdHhQow4=" crossorigin="anonymous"></script>

Upvotes: 3

Related Questions