In React, it's best practice to control the .active
class by updating the state/properties. Whether it's through an action like a click event or directly changing the state, the focus can also be controlled (as shown in the 3rd example). It's important to avoid direct DOM manipulation.
If multiple elements share the same ref and they replace each other during renders, only the last item will receive focus. To address this with refs, you should use an array for inputRef
and have each input add itself to the array using a function ref.
You can then iterate through the items in a useEffect
hook, find the one with the .active
class, and set focus on 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>
Alternatively, if you're already manipulating the DOM, you can directly use document.querySelector()
. While not the recommended React approach, it is simpler and doesn't require 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're working with a model passed via state/props, you can utilize the state/properties to manage focus and 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>