i'm actually new at react, as a part of my intro a made one web app who picks some recipes from a API, actually everything is ok, but i want to made a message of "No results found" when the item searched return no results, but i don't really know where i made this. Here some of my actually code.
App.js
const App = () => {
const APP_ID = "x";
const APP_KEY = "x";
const [recipes, setRecipes] = useState([]);
const [search, setSearch] = useState("");
const [query, setQuery] = useState('chicken');
useEffect( () => {
getRecipes()
}, [query]);
const getRecipes = async () => {
const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`
);
const data = await response.json()
setRecipes(data.hits);
console.log(data)
};
const updateSearch = e => {
setSearch(e.target.value)
};
const getSearch = e => {
e.preventDefault();
setQuery(search);
setSearch("");
};
return (
<div className="App">
<form onSubmit={getSearch} className="search-form">
<input
placeholder="Search recipes here"
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button
className="search-button"
type="submit">
Buscar
</button>
</form>
<div className="recipes">
{recipes.map(recipe => (
<Recipe
key={recipe.recipe.label}
title={recipe.recipe.label}
calories={recipe.recipe.calories}
image={recipe.recipe.image}
ingridients={recipe.recipe.ingredients}
/>
))}
</div>
</div>
);
};
export default App;
recipe.js
const Recipe = ({title,calories,image,ingridients}) => {
return (
<div className={style.quadrado}>
<h1 className={style.recipe}>{title}</h1>
<ol className={style.list}>
{ingridients.map(ingridient =>(
<li>{ingridient.text}</li>
))}
</ol>
<img className={style.images} src={image} alt=""/>
<p>Calories: {calories}</p>
</div>
);
};
export default Recipe;
i make a connection with the "Edamam" API and get a list of recipes and then render on my web app, but when there's no results i want to put a message saying "Sorry, no results found".
I read some articles here, but i confess that react is kind confuse for me yet.
Thank you for you time!
You could do:
{recipes.lenght === 0 ? (<div>Sorry, no results found</div>)
: recipes.map(recipe => (
<Recipe
key={recipe.recipe.label}
title={recipe.recipe.label}
calories={recipe.recipe.calories}
image={recipe.recipe.image}
ingridients={recipe.recipe.ingredients}
/>
))}
You can check an example about this implementation: https://stackblitz.com/edit/react-typescript-usefetch
Related
Given the following form, I need whenever the form is submitted, the new post to be listed/rendered without having to refresh the page.
const PostCreate = () => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I tried adding this.forceUpdate() and this.setState(this.state), neither works, and I still have to refresh the page for the new post to show.
Here's how the posts are rendered:
const PostList = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
This is what App.js looks like
const App = () => {
return <div>
<h1>Create Post</h1>
<PostCreate></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList></PostList>
</div>;
};
export default App;
and is eventually rendered using:
ReactDOM.render(
<App></App>,
document.getElementById('root')
)
In your PostList, useEffect called once when you first load your component, so when you create new post, it will not be re-rendered
You should bring your fetchPost logic to your App component, and add function props onPostCreated to PostCreate component, trigger it after you finish creating your new post
The code should be:
const App = () => {
const [posts, setPosts] = useState({});
const fetchPosts = async () => {
await axios.get(`http://${queries_host}/posts`).then(response => {
setPosts(response.data);
}).catch(error => {
console.log(error)
});
};
useEffect(() => {
fetchPosts();
}, []);
return <div>
<h1>Create Post</h1>
<PostCreate onCreatePost={() => fetchPost()}></PostCreate>
<hr/>
<h1>Posts</h1>
<PostList posts={posts}></PostList>
</div>;
};
export default App;
const PostList = ({ posts }) => {
const renderedPosts = Object.values(posts).map(post => {
return <div className="card"
style={{width: '30%', marginBottom: '20px'}}
key={post.id}>
<div className="card-body">
<h3>{post.title}</h3>
<CommentList comments={post.comments}></CommentList>
<CommentCreate postId={post.id}></CommentCreate>
</div>
</div>
});
return <div>
{renderedPosts}
</div>;
}
export default PostList;
const PostCreate = ({ onCreatePost }) => {
const [title, setTitle] = useState('');
const onSubmit = async (event) => {
event.preventDefault();
await axios.post(`http://${posts_host}/posts/create`, {title}).catch(error => {
console.log(error)
})
onCreatePost && onCreatePost();
setTitle('');
};
return (<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input value={title} onChange={event => setTitle(event.target.value)}
className="form-control "/>
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>)
}
export default PostCreate;
I think the problem you are having is not in the code you have displayed. The component is indeed rerendering after you change its state and also when you forceUpdate() it. I assume the posts you are trying to display are taken from the same API that you post to. Even if this component is being rerendered, your GET request which gives the data to the component who renders it is not called again so the data doesn't update. You need to refetch it. This can be done by many different ways (useEffect(), callbacks, reactQuery refetch) depending on the rest of your code. I would need the component that renders the data and the API call to help you further.
Another thing that you didn't ask but is good practice. In your PostCreate component you don't need to manage the state of fields that are in the form, because it already does it for you. Just give a name to your inputs and use the form data. I've given an example below.
import { useState } from "react";
const PostCreate = () => {
const onSubmit = async (event) => {
event.preventDefault();
console.log(event.target.elements.title.value);
};
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Title</label>
<input name="title" className="form-control" />
</div>
<button className="btn btn-primary">Submit</button>
</form>
</div>
);
};
export default PostCreate;
Currently learning React and building a side project where i can render rss-feeds in my browser window. It works in a single component.
Original working component
function App (){
const [rssUrl, setRssUrl] = useState('');
const [items, setItems] = useState([]);
const getRss = async (e) => {
e.preventDefault();
const urlRegex =
/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/;
if (!urlRegex.test(rssUrl)) {
return;
}
const res = await fetch(`https://api.allorigins.win/get?url=${rssUrl}`);
const { contents } = await res.json();
const feed = new window.DOMParser().parseFromString(contents, 'text/xml');
const items = feed.querySelectorAll('item');
const feedItems = [...items].map((el) => ({
link: el.querySelector('link').innerHTML,
title: el.querySelector('title').innerHTML,
author: el.querySelector('author').innerHTML,
}));
setItems(feedItems);
};
}
return (
<div className="App">
<form onSubmit={getRss}>
<div>
<h1>Next Pod For Chrome</h1>
<label> rss url</label>
<br />
<input onChange={(e) => setRssUrl(e.target.value)} value={rssUrl} />
</div>
<input type="submit" />
</form>
{items.map((item) => {
return (
<div>
<h1>{item.title}</h1>
<p>{item.author}</p>
<a href={item.link}>{item.link}</a>
</div>
);
})}
</div>
);
}
export default App;
At the moment I try to separate the functionality into two components. How can I pass a link from one component to another one where I want to trigger a function handled by the first component?
Any tips are much appreciated. Thanks.
Current state of component to search for rss-feed
function Search() {
const [rssUrl, setRssUrl] = useState('');
const formatRss = async (e) => {
e.preventDefault();
const urlRegex =
/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/;
if (!urlRegex.test(rssUrl)) {
return;
}
console.log(rssUrl);
};
return (
<div className="App">
<form onSubmit={formatRss}>
<div>
<h1>Next Pod For Chrome</h1>
<label>rss url</label>
<br />
<input onChange={(e) => setRssUrl(e.target.value)} value={rssUrl} />
</div>
<input type="Submit" />
</form>
</div>
);
}
export default Search;
Current stage of component to parse and render
function List(props) {
const [items, setItems] = useState([]);
const formatRss = async (e) => {
e.preventDefault();
console.log(rssUrl);
const res = await fetch(`https://api.allorigins.win/get?url=${rssUrl}`);
const { contents } = await res.json();
const feed = new window.DOMParser().parseFromString(contents, 'text/xml');
const items = feed.querySelectorAll('item');
const feedItems = [...items].map((el) => ({
link: el.querySelector('link').innerHTML,
title: el.querySelector('title').innerHTML,
author: el.querySelector('author').innerHTML,
}));
setItems(feedItems);
};
return (
<div className="App">
{items.map((item) => {
return (
<div>
<h1>{item.title}</h1>
<p>{item.author}</p>
<a href={item.link}>{item.link}</a>
</div>
);
})}
</div>
);
}
export default List;
You can declare the state on both's parent, for example: App.js
And use prop to pass the variable to the component
like this:
export default function App() {
const [rssUrl, setRssUrl] = useState("");
return (
<div className="App">
<Search rssUrl={rssUrl} setRssUrl={setRssUrl} />
<List rssUrl={rssUrl} />
</div>
);
}
Below is the live example for you:
https://codesandbox.io/s/cocky-tharp-7d5uu8?file=/src/App.js
There are many platforms where you can put the demo project which make it easier for people to answer your question.
When I click on the search button the first time API response is "undefined" (based on what console.log says) but the second time it has the response from API.
Why does this happen?
xport default function Home() {
const [searchTerm, setSearchTerm] = useState('');
const fetcher = (url) => fetch(url).then((res) => res.json());
const [shouldFetch, setShouldFetch] = useState(false);
const { data, error } = useSWR(
() =>
shouldFetch
? `https://eu-central-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/lottigully-jjrda/service/movies/incoming_webhook/movies?arg=${searchTerm}`
: null,
fetcher
);
if (error) return 'An error has occurred.';
return (
<>
<main>
<div className={style.main_container}>
<NavBar />
<Hero />
</div>
<div className={style.search_container}>
<SearchBar
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<button
onClick={() => {
setShouldFetch(true);
console.log(searchTerm);
console.log(data);
}}
>
Search!
</button>
</div>
</main>
</>
);
}
I have the following code:
const HeaderMenu = ({ location }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const dispatch = useDispatch();
const handleLogout = doLogout(dispatch);
const handleLogoutOrder = async ({ stage }) => {
setIsModalOpen(true);
await dispatch(
fetchRejectionReasons({
siteId: 'USDJD',
serviceId: 'CAR',
stage: stage || EXIT_REASONS.EXIT
})
);
};
return(
<>
<Popup
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
logout
/>
<div className={styles.container}>
<div>
<Link data-testid="link" to="/">
<img src={logoUrl} />
</Link>
</div>
<div
role="button"
tabIndex="0"
onClick={
ORDER_ROUTES.includes(location.pathname)
? handleLogoutOrder
: handleLogout
}
data-testid="headermenu-logout-btn"
>
Logout
</div>
</div>
</>
);
};
)
...
I don't have any idea how to test handleLogoutOrder function in Jest using react testing library.. if anyone with experience on this can help me, will be greatly appreciated.
I've tested the Popup render.. I need to test setIsModalOpen(true) and the dispatch fetch function.
I am getting a object from "api" and set it to match but when i try do loop through or render it i get a error.
I have tryed Objct.keys maybe my syntax is wrong im not sure im still learning thx for any help.
const [match, setMatch] = useState();
const [search, setSearch] = useState('');
const [query, setQuery] = useState(4749875544)
useEffect(() => {
getData();
}, [query]);
const getData = async () => {
const response = await
fetch(`https://api.opendota.com/api/matches/${query}`)
const result = await response.json();
setMatch(result);
}
}
return (
<div className="App" >
<form onSubmit={getSearch}
className="search-form">
<input className="search-bar"
type="text"
value={search}
onChange={searchInput}
/>
<Button as="input"
type="submit"
value="Submit" />
</form>
<li>
{
Object.keys(match).map((oneKey,i)=>{
return (
<li key={i}>{match[oneKey]}</li>
)})
}
</li>
</div>
)}
First I would default the state to an Object. It is always good to default your state to the data types you will use. So at the top useState({}).
React can’t render an object. You have to render each key separately. In your map when you return the list item do it with match[oneKey].title or whatever key is actially valid.