React Router - Search bar redirects to a /profile/X page

Viewed 42

I wanted to add a crucial functionality to my React project. The idea is to have a Home page that acts like a landing page. In this Home page ("localhost:3000/Home") there is a search form that lets you retrieve some statistics of the account you typed in it. What I wanted to do, with React Router, is to redirect to a new page ("localhost:3000/profile/X" , where X is the account name typed in the Home search bar) that displays the statistics of the account X searched. Also, the application should "listen" and redirect even when the account is manually typed in the URL bar of the browser: so, to reach the statistics page of the account X, the user can type in the search form "X" and click submit to get redirected to "/profile//X", but ALSO the user should be able to see that page by manually typing in the URL search bar : "localhost:3000/profile/X", without having necessarily the need to land on the home page and use the form to search.

My intention was to create a Profile.js React component, which does immediately a call (fetch()) to a 3rd party API that retrieves the statistics. The component should read the URL (which, again, should be like "localhost:3000/profile/JonSnow"), then extract the "JonSnow" part to make a API call with that string as a parameter. The problem here is to read the URL, I thought of using the React Router hook useParams:

Profile.js

// import statements

export const Profile = () =>
{
  const { param } = useParams();

  fetch("externalapi.com/${param}")  // .then(.........)
}

I'm not sure how { param } grabs the part of the URL that I want, honestly. It surely has something to do with how the Route is defined in the Routes component (index.js attached below).

Now the route file should be like:

index.js

{
  // import statements
  
  <BrowserRouter>
    <Routes>
      <Route exact path="/home" element={ <App /> } />
      <Route path="/about" element={ <About /> } />
      <Route path="/contacts" element={ <Contacts /> } />
      <Route path="/profile" element={ <Profile /> } >
         <Route path="????" element={ <Profile /> } />
      </Route>
    </Routes>
  </BrowserRouter>

I figured out that a nested route for /profile might be handy, but I don't know how to make path="????" in that nested route to work for potentially every call with any string. In other words, how can I make BrowserRouter to listen to "/profile/whateverStringIsTyped" ?

Also, this is the landing page component: App.js

// import statements

function App() {
  return (
    <div className="App">
      <form className="accountSearch" onSubmit={(event) => { event.preventDefault(); } }>
        <input type="search" id="search-recent" placeholder="Enter a name..."></input>                
        <Link to={location => {  elem = document.getElementById("search-recent");
                                 return ("/profile/" + elem.value() ); }} >      
             <button type="submit">Search</button>
        </Link>
      </form>
    </div>
  );
}

export default App;

And this is the 3rd question: besides not using the hook useRef instead of document.getElementById, is there anything wrong with using the React Router Link component to redirect to "localhost:3000/profile/X" ?

1 Answers

If Profile is the component doing the fetching based on a route path param then this is what I'd suggest:

Render Profile on two paths, one with a route parameter and one without.

<BrowserRouter>
  <Routes>
    <Route path="/home" element={<App />} />
    <Route path="/about" element={<About />} />
    <Route path="/contacts" element={<Contacts />} />
    <Route path="/profile">
      <Route index element={<Profile />} />
      <Route path=":id" element={<Profile />} />
    </Route>
  </Routes>
</BrowserRouter>

The Profile component reads the id route path param and issues the fetch request in a useEffect hook if present.

export const Profile = () => {
  const { id } = useParams();
  const [data, setData] = React.useState();

  useEffect(() => {
    fetch("externalapi.com/${id}")
      .then(response => {
        // update local state with fetched data
      });
  }, [id]);

  return data ? (
    ...JSX for fetched data...
  ) : (
    <div>No Data</div>
  );
};

See also:

Instead of a Link component I might suggest using the button element with the navigate function to issue the imperative navigation action via the form element's onSubmit action.

Example:

import { useNavigate } from 'react-router-dom';

function App() {
  const navigate = useNavigate();

  const search = e => {
    e.preventDefault();
    navigate("/profile/" + e.target.search.value);
  };

  return (
    <div className="App">
      <form className="accountSearch" onSubmit={search}>
        <input
          type="search"
          id="search"
          placeholder="Enter a name..."
        />
        <button type="submit">Search</button>
      </form>
    </div>
  );
}
Related