Specify focus on element which has active class name

Viewed 670

I´d like to help with this problem: I´ve got this example code below:

import React, { useRef, useEffect } from "react";

function App() {
  const inputRef = useRef(null);

  useEffect(() => {
    const testElement = document.querySelectorAll(".active");
    testElement.length > 0 && inputRef.current.focus();
   }, []);

  return (
    <div style={{width: 200, height: 190, display: "flex", flexDirection: "column", justifyContent: "space-between"}}>
      <input id={1} type="text" ref={inputRef} />
      <input id={2} type="text" ref={inputRef} />
      <input id={3} className={"active"} type="text" ref={inputRef} />
      <input id={4} type="text" ref={inputRef} />    
    </div>
  );
}

export default App;

And I like to know, how to focus on concrete element which has some class name. In my case it´s "active" class name on input field with id "3". But the problem is, that it´s not working and focused is the input element with id "4".

Thanky you.

1 Answers

Ideally in React, the class .active should be controlled by changing the state/properties. In this case, the action that changes the state (click for example), or the state itself can also control the focus (see 3rd example). In addition, we don't want to access the DOM directly.

In your case, the elements are sharing the same ref, and replace each other when render. That's why the last item is the only one that gets the focus.

To get what you want with refs, inputRef should be an array, and each input should use a function ref to add itself to the array.

Now you can scan the items in useEffect, find one that has the .active class and focus it.

const { useRef, useEffect } = React;

function App() {
  const inputRef = useRef([]);

  useEffect(() => {
    for(const r of inputRef.current) {
      if(r.classList.contains('active')) {
        r.focus();
        return;
      }
    }
   }, []);
   
   const addRef = r => inputRef.current.push(r)

  return (
    <div style={{width: 200, height: 190, display: "flex", flexDirection: "column", justifyContent: "space-between"}}>
      <input id={1} type="text" ref={addRef} />
      <input id={2} type="text" ref={addRef} />
      <input id={3} className={"active"} type="text" ref={addRef} />
      <input id={4} type="text" ref={addRef} />    
    </div>
  );
};

ReactDOM.render(
  <App />,
  root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

However, since you are already access the DOM anyway, you can use document.querySelector() directly. This is still not the React way, but at least it's less cumbersome (you don't need refs):

const { useEffect } = React;

function App() {
  useEffect(() => {
    const active = document.querySelector('.active');
    
    if(active) active.focus();
   }, []);

  return (
    <div style={{width: 200, height: 190, display: "flex", flexDirection: "column", justifyContent: "space-between"}}>
      <input id={1} type="text" />
      <input id={2} type="text" />
      <input id={3} className={"active"} type="text" />
      <input id={4} type="text" />    
    </div>
  );
};

ReactDOM.render(
  <App />,
  root
);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

If you work with a model passed via state/props (props in this case), you can use the state/properties to control the focus (and the class) - see comments in the code:

const { useRef, useEffect } = React;

// Each item renders it's own input, and focuses the item if the selected prop is true
const Item = ({ selected }) => {
  const inputRef = useRef(null);

  useEffect(() => {
    if(selected) inputRef.current.focus();
  }, [selected]);
    
  return (
    <input className={selected ? 'active' : ''} type="text" ref={inputRef} />
  );
};

// App renders a list of items
const App = ({ items }) => (
  <div style={{width: 200, height: 190, display: "flex", flexDirection: "column", justifyContent: "space-between"}}>
  {items.map(item => (
    <Item key={item.id} {...item} />
  ))}
  </div>
);

const items = [{ id: 1 }, { id: 2 }, { id: 3, selected: true }, { id: 4 }];

ReactDOM.render(
  <App items={items} />,
  root
);
.active {
  outline: 1px solid red;
}
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

Related