I am trying to use ref on a search input in my Header component which ISN'T a higher order component to my ResultsList component. I want to set focus on the Header's search input from the ResultsList component. It is intuitive from the Header because all I have to do is the below. What if I wanted to create a button in ResultsList which would focus on the input element in Header? How do I pass this ref? I have read about forwardRef but I am not passing my ref forwards. ResultsList is not a child of Header.
import React, { useState, useRef } from 'react';
import { useHistory } from 'react-router-dom';
const Header = () => {
const searchInput = useRef(null);
const history = useHistory();
const [searchValue, setSearchValue] = useState(keyword);
function handleChange(event) {
setSearchValue(event.target.value);
}
function handleSearch(event) {
event.preventDefault();
if(searchValue) {
history.push(`/search/${searchValue}`);
} else {
searchInput.current.focus();
}
}
return (
<form onSubmit={handleSearch} role="search">
<input
value={searchValue}
onChange={handleChange}
className="HeaderSearch__input"
id="header-search-input"
placeholder="Search a repository"
ref={searchInput}>
</input>
</form>
);
}
export default Header;
My App component looks like this
import React from 'react';
import Header from './Header';
import ResultsList from './ResultsList';
function App() {
return (
<>
<Header />
<ResultsList />
</>
);
}
export default App;
You will need to utilize the "Lifting State Up" pattern. Declare the react ref in App
and pass it to both components, to Header
to attach the ref to a node and to ResultsList
to access the ref and set "focus".
function App() {
const searchInputRef = useRef(null);
return (
<>
<Header searchInputRef={searchInputRef} />
<ResultsList searchInputRef={searchInputRef} />
</>
);
}
Attach and use the ref as you already are in Header
const Header = ({ searchInputRef }) => {
const history = useHistory();
const [searchValue, setSearchValue] = useState(keyword);
function handleChange(event) {
setSearchValue(event.target.value);
}
function handleSearch(event) {
event.preventDefault();
if(searchValue) {
history.push(`/search/${searchValue}`);
} else {
searchInputRef.current.focus();
}
}
return (
<form onSubmit={handleSearch} role="search">
<input
value={searchValue}
onChange={handleChange}
className="HeaderSearch__input"
id="header-search-input"
placeholder="Search a repository"
ref={searchInputRef}>
</input>
</form>
);
}
Similarly, you can access searchInputRef
in ResultsList
component as well.
function ResultsList({ searchInputRef }) {
...
<button
type="button"
onClick={() => searchInputRef.current?.focus()}
>
Set Search Focus
</button>
}
What if more deeply nested components need ref?
If the children components are not direct descendants then you can utilize a react context to allow children to access the ref without needing to pass it as a prop though the React tree.
Create and export the context.
const SearchInputRefContext = React.createContext(null);
Provide the context to children in App
import SearchInputRefContext from '.....';
function App() {
const searchInputRef = useRef(null);
return (
<SearchInputRefContext.Provider value={searchInputRef}>
<Header />
<ResultsList />
</SearchInputRefContext.Provider>
);
}
Access the context in any sub child component
const Header = () => {
const history = useHistory();
const searchInputRef = useContext(SearchInputRefContext);
const [searchValue, setSearchValue] = useState(keyword);
function handleChange(event) {
setSearchValue(event.target.value);
}
function handleSearch(event) {
event.preventDefault();
if(searchValue) {
history.push(`/search/${searchValue}`);
} else {
searchInputRef.current.focus();
}
}
return (
<form onSubmit={handleSearch} role="search">
<input
value={searchValue}
onChange={handleChange}
className="HeaderSearch__input"
id="header-search-input"
placeholder="Search a repository"
ref={searchInputRef}>
</input>
</form>
);
}
No matter how deeply nested
function ReallyDeepComponent() {
const searchInputRef = useContext(SearchInputRefContext);
...
<button
type="button"
onClick={() => searchInputRef.current?.focus()}
>
Set Search Focus
</button>
}
See this section if you happen to still be using class-based components.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With