How to use refs in react through react-redux , withRouter?

In order to pass a ref to a component wrapped by withRouter you need to call it wrappedComponentRef. I recommend having withRouter as the outermost wrapper, so your example would look like the following:

withRouter(connect(null, null, null, {forwardRef: true})(myCom));
<myCom wrappedComponentRef={ref => this.myCom = ref} />

The following example and description are adapted from a related answer of mine: Get ref from connected redux component withStyles

Below is code from a modified version of the react-redux todo list tutorial that shows the correct syntax. I've included here the two files that I changed (TodoList.js and TodoApp.js), but the sandbox is a fully working example.

In TodoApp, I use the ref (via the wrappedComponentRef property) on TodoList to get and display its height. The displayed height will only get updated if TodoApp re-renders, so I've included a button to trigger a re-render. If you add a couple todos to the todo list, and then click the re-render button, you will see that the new height of the list is displayed (showing that the ref is fully working).

In TodoList, I'm using withStyles to add a blue border around the todo list to show that withStyles is working, and I'm displaying the primary color from the theme to show that withTheme is working. I am also displaying the location object from withRouter to demonstrate that withRouter is working.

TodoList.js

import React from "react";
import { connect } from "react-redux";
import Todo from "./Todo";
import { getTodosByVisibilityFilter } from "../redux/selectors";
import { withStyles, withTheme } from "@material-ui/core/styles";
import clsx from "clsx";
import { withRouter } from "react-router-dom";

const styles = {
  list: {
    border: "1px solid blue"
  }
};

const TodoList = React.forwardRef(
  ({ todos, theme, classes, location }, ref) => (
    <>
      <div>Location (from withRouter): {JSON.stringify(location)}</div>
      <div>theme.palette.primary.main: {theme.palette.primary.main}</div>
      <ul ref={ref} className={clsx("todo-list", classes.list)}>
        {todos && todos.length
          ? todos.map((todo, index) => {
              return <Todo key={`todo-${todo.id}`} todo={todo} />;
            })
          : "No todos, yay!"}
      </ul>
    </>
  )
);

const mapStateToProps = state => {
  const { visibilityFilter } = state;
  const todos = getTodosByVisibilityFilter(state, visibilityFilter);
  return { todos };
};
export default withRouter(
  connect(
    mapStateToProps,
    null,
    null,
    { forwardRef: true }
  )(withTheme(withStyles(styles)(TodoList)))
);

TodoApp.js

import React from "react";
import AddTodo from "./components/AddTodo";
import TodoList from "./components/TodoList";
import VisibilityFilters from "./components/VisibilityFilters";
import "./styles.css";

export default function TodoApp() {
  const [renderIndex, incrementRenderIndex] = React.useReducer(
    prevRenderIndex => prevRenderIndex + 1,
    0
  );
  const todoListRef = React.useRef();
  const heightDisplayRef = React.useRef();
  React.useEffect(() => {
    if (todoListRef.current && heightDisplayRef.current) {
      heightDisplayRef.current.innerHTML = ` (height: ${
        todoListRef.current.offsetHeight
      })`;
    }
  });
  return (
    <div className="todo-app">
      <h1>
        Todo List
        <span ref={heightDisplayRef} />
      </h1>
      <AddTodo />
      <TodoList wrappedComponentRef={todoListRef} />
      <VisibilityFilters />
      <button onClick={incrementRenderIndex}>
        Trigger re-render of TodoApp
      </button>
      <div>Render Index: {renderIndex}</div>
    </div>
  );
}

Edit Todo App with Redux


Use compose method and try something like this

const enhance = compose(
  withStyles(styles),
  withRouter,
  connect(mapStateToProps, null, null, { forwardRef: true })
)

and use it before exporting component

export default enhance(MyComponent)