2

Fairly new to react and trying to get the router to work for me. I'm pulling in data from a third party api using a simple form. When the data is retrieved I'd like to display it in a new route, and then ultimately have another route for each item retrieved. When I submit the form I just get a '?' in the route params. If I enter the route manually then submit the form the data displays. How can I get the data to display on form submit?

import axios from "axios";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import "./App.css";
import RecipeList from "./RecipeList";
import Header from "./Header";

function App() {
  const [recipes, setRecipes] = useState([]);
  const [query, setQuery] = useState("");
  const [search, setSearch] = useState("");
  const APP_ID = "XXXXXXXX";
  const APP_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
  const url = `https://api.edamam.com/api/recipes/v2?type=public&q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`;

  const getRecipes = async () => {
    const res = await axios(url);
    const data = await res.data.hits;
    console.log(data);
    setRecipes(data);
  };
  useEffect(() => {
    getRecipes();
  }, [query]);

  const updateSearch = (e) => {
    setSearch(e.target.value);
    console.log(search);
  };

  const getSearchQuery = () => {
    e.preventDefault();
    setQuery(search);
    setSearch("");
  };
  
  return (
    <div className="App">
      <Header />
      <div>
        <div className="container">
          <form className="search-form" onSubmit={getSearchQuery}>
            <input
              className="search-input"
              type="text"
              value={search}
              onChange={updateSearch}
              placeholder="search by food name"
            />
            <button className="search-button" type="submit">
              Search Recipes
            </button>
          </form>
        </div>
      </div>
      {/* <RecipeList recipes={recipes}/> */}
      <Router>
        <Routes>
          <Route path="/recipes" element={<RecipeList recipes={recipes}/>} />
          <Route path="/recipes/:id" />
        </Routes>
      </Router>
    </div>
  );
}

export default App;

import React from "react";
import { Link } from "react-router-dom";

const RecipeList = ({ recipes }) => {
  console.log(recipes);
  return (
    <div>
      {recipes.map(({ recipe }, id) => (
        <Link to={`recipes/${recipe.label}`}>
          <p key={id}>{recipe.label}</p>
        </Link>
      ))}
    </div>
  );
};

export default RecipeList;
RRhodes
  • 501
  • 6
  • 19
  • Can you clarify what "enter the route manually then submit the form the data displays" means? Are you not navigating to "/recipes", then entering and submitting the form data via the UI? Are you saying the GET request is only `"https://api.edamam.com/api/recipes/v2?"`? without the query string params? Question/issue is unclear. – Drew Reese Dec 13 '21 at 04:06
  • I just meant that when i type '/recipes' in the url and then submit the form the data displays. When I submit the form on the '/' route nothing happens. – RRhodes Dec 13 '21 at 04:08
  • Well, you're not rendering any route content on a `"/"` path so I wouldn't expect anything to render other than the form. Are you wanting to render the form and the data on the `"/"` path, and the specific recipes on a `"/recipeId"` path? In other words, unnesting the routes? – Drew Reese Dec 13 '21 at 04:16

1 Answers1

1

If I understand your question/issue you are having issue linking to a specific recipe. I suspect it is because you are using a relative link, so you are linking to a "/recipes/recipes/<label".

Either use absolute link paths, i.e. using a leading "/":

<Link to={`/recipes/${recipe.label}`}>
  <p key={id}>{recipe.label}</p>
</Link>

Or use a correct relative path, i.e. only append the next level path segment, in other words, appending recipe.label to "/recipes":

<Link to={`${recipe.label}`}>
  <p key={id}>{recipe.label}</p>
</Link>

If you wanting to start on "/" and submit the form and navigate to "/recipes" then issue an imperative navigation after submitting the form. Import the useNavigate hook to issue the imperative navigation and move the Router to wrap the App component so the routing context is provided to it and the useNavigate hook can work properly.

import axios from "axios";
import { BrowserRouter as Router, Routes, Route, useNavigate } from "react-router-dom";
import "./App.css";
import RecipeList from "./RecipeList";
import Header from "./Header";

function App() {
  const navigate = useNavigate(); // <-- use navigate hook

  const [recipes, setRecipes] = useState([]);
  const [query, setQuery] = useState("");
  const [search, setSearch] = useState("");

  const APP_ID = "XXXXXXXX";
  const APP_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
  const url = `https://api.edamam.com/api/recipes/v2?type=public&q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`;

  const getRecipes = async () => {
    const res = await axios(url);
    const data = await res.data.hits;
    console.log(data);
    setRecipes(data);
  };

  useEffect(() => {
    getRecipes();
  }, [query]);

  const updateSearch = (e) => {
    setSearch(e.target.value);
    console.log(search);
  };

  const getSearchQuery = () => {
    e.preventDefault();
    setQuery(search);
    setSearch("");
    navigate("/recipes"); // <-- imperative navigation
  };
  
  return (
    <div className="App">
      <Header />
      <div>
        <div className="container">
          <form className="search-form" onSubmit={getSearchQuery}>
            <input
              className="search-input"
              type="text"
              value={search}
              onChange={updateSearch}
              placeholder="search by food name"
            />
            <button className="search-button" type="submit">
              Search Recipes
            </button>
          </form>
        </div>
      </div>
      <Routes>
        <Route path="/recipes" element={<RecipeList recipes={recipes}/>} />
        <Route path="/recipes/:id" />
      </Router>
    </div>
  );
}

index.js

<Router>
  <App />
</Router>
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Oh yes, you caught that as well. However the initial problem remains that the data still doesn't display when I submit the form. When I type in a query in the form input (at localhost:3000), I'm expecting the data to display in a new route (localhost:3000/recipes). That's what I'm currently trying to work out. – RRhodes Dec 13 '21 at 04:15
  • @RRhodes Ok, so you are on `"/"` and submit the form, and you want the path to now be `"/recipes"`? – Drew Reese Dec 13 '21 at 04:17
  • Yes, that's right. I'd like the form to be on every page so the user can keep searching, but I want the results to display on '/recipes'. Does that make sense? – RRhodes Dec 13 '21 at 04:19
  • @RRhodes Ok, I think I understand now. Please check updated answer. – Drew Reese Dec 13 '21 at 04:24
  • Trying to make this work - keep getting this error: useNavigate() may be used only in the context of a component. – RRhodes Dec 13 '21 at 04:43
  • @RRhodes Ah, move the `Router` so it wraps the `App` component. See updated answer. – Drew Reese Dec 13 '21 at 05:18