In a React project, I'am calling API to search recipes and load recipe on button click. My intention is to serve single API for both functions. Is there any appropriate solution?
App.js
const [query, setQuery] = useState("porridge");
const [recipes, setRecipes] = useState([]);
const [alert, setAlert] = useState("");
// const [checked, setChecked] = useState(false);
const [radioValue, setRadioValue] = useState('1');
const radios = [
{ name: 'Chicken', value: 'chicken', active: true},
{ name: 'Bread', value: 'bread' },
{ name: 'Fish', value: 'fish' },
{ name: 'Soup', value: 'soup' },
{ name: 'Rice', value: 'rice' },
{ name: 'Meat', value: 'meat' }
];
const url = `https://cors-anywhere.herokuapp.com/https://api.edamam.com/search?
q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}&from=0&to=12`;
{/* Load recipes on submit button click */}
const getData = async () => {
if (query !== "") {
const result = await Axios.get(url);
console.log(result)
if (!result.data.more) {
return setAlert("No food with such name");
}
setRecipes(result.data.hits);
setQuery("");
setAlert("");
} else {
setAlert("Please fill the form");
}
};
const onChange = e => setQuery(e.target.value);
const onSubmit = e => {
e.preventDefault();
getData();
};
{/* Load recipes on radio button click */}
const handleChange = async (e) => {
let checkValue = e.target.value;
if(checkValue) {
const result2 = await Axios.get(`https://cors-anywhere.herokuapp.com/https://api.edamam.com/search?q=${checkValue}&app_id=${APP_ID}&app_key=${APP_KEY}&from=0&to=12`);
setRecipes(result2.data.hits);
}
}
{/* Submit Button */}
<form onSubmit={onSubmit} className="search-form">
{alert !== "" && <Alert alert={alert} />}
<input
type="text"
name="query"
onChange={onChange}
value={query}
autoComplete="off"
placeholder="Search Food"
/>
<input type="submit" value="Search" />
<br/>
</form>
{/* Radio Button */}
<ButtonGroup toggle style={{width:'100%'}}>
{radios.map((radio, idx) => (
<ToggleButton
key={idx}
type="radio"
active="true"
variant="light"
name="radio"
value={radio.value}
checked={radioValue === radio.value}
onChange={(e) => {
handleChange(e);
setRadioValue(e.currentTarget.value)
}}
size="lg"
>
{radio.name}
</ToggleButton>
))}
</ButtonGroup>
<div className="recipes">
{recipes !== [] &&
recipes.map(recipe => <Recipe key={uuidv4()} recipe={recipe} />)}
</div>
As seen from above I've to call API two times, it would be better if same API is used for both functions. I tried to call from main API but query is not updated. Event value which I'am getting from radio button can't be taken as query outside function. So any better solution to tackle?
Related
bit of a newbie at js here and I'm creating a search filter for my NFT marketplace platform.
I've successfully been able to create a basic input function to search for the name of the item uploaded but because the category of the item is an array, I'm finding difficulty creating checkbox inputs to filter the search results by category.
Here's the piece my code with the search by name function:
export function Results() {
const { fetchNFTs, setError, currentAccount } = useContext(
NFTMarketplaceContext
);
const [nfts, setNfts] = useState([]);
const [nftsCopy, setNftsCopy] = useState([]);
useEffect(() => {
try {
// if (currentAccount) {
fetchNFTs().then((items) => {
setNfts(items.reverse());
setNftsCopy(items);
console.log(nfts);
});
// }
} catch (error) {
setError("Please reload the browser", error);
}
}, []);
const onHandleSearch = (value) => {
const filteredNFTS = nfts.filter(({ name }) =>
name.toLowerCase().includes(value.toLowerCase())
);
if (filteredNFTS.length === 0) {
setNfts(nftsCopy);
} else {
setNfts(filteredNFTS);
}
};
const onClearSearch = () => {
if (nfts.length && nftsCopy.length) {
setNfts(nftsCopy);
}
};
return {
<SearchBar
onHandleSearch={onHandleSearch}
onClearSearch={onClearSearch}
/>
}
And my search bar:
import React, { useEffect, useState } from "react";
const SearchBar = ({ onHandleSearch, onClearSearch }) => {
const [search, setSearch] = useState("");
const [searchItem, setSearchItem] = useState(search);
useEffect(() => {
const timer = setTimeout(() => setSearch(searchItem), 1000);
return () => clearTimeout(timer);
}, [searchItem]);
useEffect(() => {
if (search) {
onHandleSearch(search);
} else {
onClearSearch();
}
}, [search]);
return (
<div className="SearchBar">
<div className="section-filters-bar-actions">
<form className="form">
<div className="form-item split">
<div className="form-input small">
<label for="items-search">Search Items</label>
<input type="text" id="items-search" style={{width:'370px'}}
onChange={(e) => setSearchItem(e.target.value)}
value={searchItem}/>
</div>
</div>
</form>
</div>
</div>
);
}
export default SearchBar;
And finally the array in the upload page / function:
const UloadNFT = ({ uploadToIPFS, createNFT }) => {
const [price, setPrice] = useState("");
const [active, setActive] = useState(0);
const [name, setName] = useState("");
const [website, setWebsite] = useState("");
const [description, setDescription] = useState("");
const [royalties, setRoyalties] = useState("");
const [fileSize, setFileSize] = useState("");
const [category, setCategory] = useState(0);
const [properties, setProperties] = useState("");
const [image, setImage] = useState(null);
const router = useRouter();
const categoryArry = [
{
category: "AI Generated",
},
{
category: "Artwork",
},
{
category: "Audio",
},
{
category: "Collectible",
},
{
category: "Customization",
},
{
category: "Digital Land",
},
{
category: "Gaming",
},
{
category: "Utility",
},
];
return (
<div className="UloadNFT" style={{height:'1350px'}}>
<div style={{display:'inline-grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '0rem'}}>
<div className={Style.upload_box} style={{backgroundColor:'var(--primary-white)',padding:'30px 30px 30px 30px'}}>
<div className={formStyle.Form_box_input}>
<label htmlFor="nft">Item Name</label>
<input
type="text"
placeholder="Name your token..."
className={formStyle.Form_box_input_userName}
onChange={(e) => setName(e.target.value)}
style={{border:'1px solid #202020', backgroundColor:'transparent'}}
/>
</div>
// category upload input area
<div className={formStyle.Form_box_input}>
<label htmlFor="name">Choose Category</label>
<p className={Style.upload_box_input_para}>
Choose a specific category for your token
</p>
<br></br>
<div className={Style.upload_box_slider_div}>
{categoryArry.map((el, i) => (
<div
className={`${Style.upload_box_slider} ${
active == i + 1 ? Style.active : ""
}`}
key={i + 1}
onClick={() => (setActive(i + 1), setCategory(el.category))}
>
<p style={{textAlign:'center'}}>{el.category} </p>
</div>
))}
</div>
</div>
<Button
btnName="Create Token"
handleClick={async () =>
createNFT(
name,
price,
image,
description,
router,
category,
// royalties,
// fileSize,
website
// properties
)
}
/>
</div>
</div>
I know it's a lot here since I'm passing the value through multiple functions but I've tried to narrow it down as much as possible. Also the smart contract is working perfectly fine and holds the category variable. I'm sure I just need to call it properly now but it's an array.
Please help!
I tried to replace { name } and name.toLowerCase() with category just to test it but it said 'category.toLowerCase() is undefined'.
Then I tried category.toString().toLowerCase() but it gave the same result.
I don't understand why when I check (availability:1) or uncheck (availability:0) the checkbox, I don't have this info : availability:1 (for example), I only get availability: (empty same for trust and comments), when I console log to see the content of my data sent (whereas I can see the content of my status in console log).
export default function Display() {
const { menuId } = useParams();
const [forms, setForms] = useState();
const [status, setStatus] = useState("");
useEffect(() => {
axios.post("", menuId:parseInt(menuId))
.then((res) => {
console.log(res);
setForms(res.data.forms[0]);
})
.catch((err) => {
console.log(err);
});
}, [menuId]);
const [data, setData] = useState({
availability: "",
trust:"",
comments:"",
status:""
});
function submit(e) {
e.preventDefault();
axios.post(data.availability, data.trust, data.comments, data.status).then((res) => {
console.log(res.data);
});
}
return (
<div>
<div>
<button
type="button"
onClick={() => setStatus({ status: "save" })}
>
Save
</button>
</div>
<div>
<button
type="button"
primary
onClick={() => setStatus({ status: "not saved" })}
>
Not saved
</button>
</div>
</div>
<hr />
<form onSubmit={(e) => submit(e)}>
<span>
Availability : <Checkbox value={!!forms.types.availability} />
...
</span>
</form>
);
}
Checkbox :
export default function Checkbox({ v }) {
const [checked, setChecked] = useState(v);
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => setChecked(checked => !checked)}
/>
{v}
</label>
);
}
Do you see why please ?
My json from api for menuId:1:
{
"forms": [
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"types": [
{
"availability": 1,
"trust":0,
"comments":1
}
],
...
},
...
}
Updating checked state in the child component won't automatically update the forms state in the parent, since they are not related to one another.
Because const [checked, setChecked] = useState(v); only reads the initialState passed in as v from parent component. Then subsequent state changes are stored in checked, and forms state from parent will not be aware of it. That's why availability's value never gets updated.
Instead, you can pass in both state and setter function to child component:
export default function Display() {
const [forms, setForms] = useState();
return (
<div className="App">
availability:
{forms ? (
<Checkbox
value={!!forms.types[0].availability}
setForms={setForms}
forms={forms}
param={"availability"}
/>
) : (
"loading"
)}
</div>
)
}
export default function Checkbox({ value, setForms, forms, param }) {
function processNewForms(forms, param) {
const newForms = { ...forms }; // make a copy first
newForms.types[0][param] = newForms.types[0][param] === 0 ? 1 : 0; // toggle logic
return newForms;
}
return (
<label>
<input
type="checkbox"
checked={value}
onChange={(e) => setForms(processNewForms(forms, param)}
/>
{value}
</label>
);
}
See updated sandbox for demo
function Input() {
const [input, setInput] = useState("");
function handleSearch() {
let url = "https://google.com/search?q=${input}"
window.open(url)
}
return (
<div className="input-wrap">
<input
type="text"
className="input__search"
placeholder="Enter your search..."
value={input}
onChange={(e) => setInput(e.target.value)}></input>
<button
className="input__search--btn"
onClick={handleSearch}>
<i className="fa-solid fa-magnifying-glass"></i>
</button>
</div>
);
}
The search button when clicked will redirect you to a google search based on the value from the input field, below is the site for advanced search, when active the link will add an additional link after "https://google.com/search?q=${input}+site%3A${activepage}.com, how do I check if one or many sites are active then pass down its name to url
P/s: code for toggling websites
function WebButton({ icon, name }) {
const [active, setActive] = useState(false);
function handleToggle() {
setActive(!active);
}
return (
<button
className={active ? "websites-btn active" : "websites-btn"}
onClick={handleToggle}>
<i className={icon}></i>
<div className="websites-name">{name}</div>
</button>
);
}
You can keep a root level state to gather active links to a state. And pass it to the Input component.
Update your Input component to accept array called `` and update the handleSearch to use OR operation in google search.
function Input({ activeLinks }) {
const [input, setInput] = useState("");
function handleSearch() {
if (activeLinks.length > 0) {
let compundSearchURL = `https://google.com/search?q=${input}`;
activeLinks.forEach((link, i) => {
compundSearchURL += `+${i > 0 ? "OR+" : ""}site%3A${link}.com`;
});
window.open(compundSearchURL);
} else {
window.open(`https://google.com/search?q=${input}`);
}
}
return (
<div className="input-wrap">
<input
type="text"
className="input__search"
placeholder="Enter your search..."
value={input}
onChange={(e) => setInput(e.target.value)}
></input>
<button className="input__search--btn" onClick={handleSearch}>
<i className="fa-solid fa-magnifying-glass">Search</i>
</button>
</div>
);
}
Accept another function in WebButton called toggleActiveLink and a string called value which refers to the URL part. Call the function with the value inside handleToggle function.
function WebButton({ icon, name, toggleActiveLink, value }) {
const [active, setActive] = useState(false);
function handleToggle() {
setActive(!active);
toggleActiveLink(value);
}
return (
<button
className={active ? "websites-btn active" : "websites-btn"}
style={{ color: active ? "blue" : "unset" }}
onClick={handleToggle}
>
<i className={icon}></i>
<div className="websites-name">{name}</div>
</button>
);
}
In the main component you have to create a local state to handle the active links. Create the toggle function as given. It will add the value if it is not there otherwise remove it.
const urls = [
{ name: "Reddit", value: "reddit" },
{ name: "Quora", value: "quara" },
{ name: "Facebook", value: "facebook" },
{ name: "Stackoverflow", value: "stackoverflow" },
{ name: "Twitter", value: "twitter" }
];
function App() {
const [activeLinks, setActiveLinks] = useState([]);
const toggleActiveLink = (link) => {
const index = activeLinks.indexOf(link);
if (index < 0) {
setActiveLinks((prevLinks) => [...prevLinks, link]);
} else {
setActiveLinks((prevLinks) => prevLinks.filter((l) => l !== link));
}
};
return (
<>
<Input activeLinks={activeLinks} />
<div>
{urls.map(({ name, value }) => (
<WebButton
name={name}
value={value}
toggleActiveLink={toggleActiveLink}
/>
))}
</div>
</>
);
}
The following component is meant to be a To-Do list. I got multiple checkboxes. As soon as all checkboxes are checked, a console.log("all checked") should appear.
My idea is to check if the todo.lengh === checked.length, but it doesn't work.
Problem: trying to console.log(checked.length) doesn't work either so there must be the problem.
Can someone help me how to reach the checked.length?
import React from 'react';
import { AiOutlinePlusCircle } from 'react-icons/ai';
import { useState } from 'react';
function Checkboxes() {
const [todo, setToDo] = React.useState('');
const [todos, setToDos] = React.useState([]);
const [checked, setChecked] = useState(false);
function handleToDoSubmit(e) {
e.preventDefault();
const newToDo = {
id: new Date().getTime(),
text: todo,
completed: false,
};
setToDos([...todos].concat(newToDo));
setToDo('');
}
function toggleCompleteToDo(id) {
const updatedToDos = [...todos].map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
});
setToDos(updatedToDos);
}
function allChecked(checked) {
if (todo.length === checked.length) {
console.log('all checked');
}
}
return (
<div className="ToDoList">
<form className="goalInputToDo" onSubmit={handleToDoSubmit}>
<input
className="goalInput"
type="text"
onChange={(e) => setToDo(e.target.value)}
value={todo}
/>
<button className="AddGoalBtn" type="submit">
.
<AiOutlinePlusCircle size="2em" />
</button>
</form>
{todos.map((todo) => (
<div className="goalItem">
<div key={todo.id}>
<div>{todo.text}</div>
<input
type="checkbox"
onChange={() => {
toggleCompleteToDo(todo.id), allChecked(todo.checked);
}}
checked={todo.completed}
/>
</div>
</div>
))}
</div>
);
}
export default Checkboxes;
todo is a string, checked is a boolean, so I'm not sure how you wanted to use them to check if all the checkboxes are checked. What you could do instead is to check your todos array and check if every single item's completed prop is true.
You can use Array#every() to do this. It tests whether all elements in the array pass the test implemented by the provided function:
function allChecked() {
return todos.every(item => item.completed)
}
function App() {
const [todo, setToDo] = React.useState('');
const [todos, setToDos] = React.useState([{
id: new Date().getTime(),
text: 'First item',
completed: false,
}]);
function handleToDoSubmit(e) {
e.preventDefault();
const newToDo = {
id: new Date().getTime(),
text: todo,
completed: false,
};
setToDos(todos.concat(newToDo));
setToDo('');
}
function toggleCompleteToDo(id) {
const updatedToDos = todos.map((item) => {
if (item.id === id) {
item.completed = !item.completed;
}
return item;
});
setToDos(updatedToDos);
}
function allChecked() {
if (!todos.length) return false;
return todos.every((item) => item.completed);
}
return (
<div className="ToDoList">
<form className="goalInputToDo" onSubmit={handleToDoSubmit}>
<input
className="goalInput"
type="text"
onChange={(e) => setToDo(e.target.value)}
value={todo}
/>
<button className="AddGoalBtn" type="submit">
Add
</button>
</form>
{todos.map((item) => (
<div className="goalItem">
<div key={item.id}>
<input
type="checkbox"
onChange={() => {
toggleCompleteToDo(item.id), allChecked(item.checked);
}}
checked={item.completed}
/>
<span>{item.text}</span>
</div>
</div>
))}
<p>All checked: {allChecked() ? 'Yes' : 'No'}</p>
</div>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
<div id="root"></div>
<script src="https://unpkg.com/react/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>
I want to filter over an array using react hooks. It should be a fairly straight forward task, but I am assuming it is something to do with hooks being updated asynchronously, although this could be wrong.
I am fairly stumped, but have included a code sandbox and my code below:
const teams_data = [
"tottenham",
"arsenal",
"man utd",
"liverpool",
"chelsea",
"west ham"
];
function App() {
const [teams, setTeams] = React.useState(teams_data);
const [search, setSearch] = React.useState("");
return (
<div className="App">
<input
onChange={e => {
const test = teams.filter(team => {
return team.toLowerCase().includes(e.target.value.toLowerCase());
});
console.log("test: ", test);
// uncomment line below and teams is logged as I want
setTeams(test);
setSearch(e.target.value);
}}
type="text"
value={search}
/>
{teams.map(team => (
<p>{team}</p>
))}
</div>
);
}
You need to filter the original data :
const test = teams_data.filter(team => {
return team.toLowerCase().includes(e.target.value.toLowerCase());
});
https://codesandbox.io/s/thirsty-austin-uqx8k
You just need to add another state for search results
const [data , setData] = useState(teams);
const [query, setQuery] = useState('')
const[res , setRes] = useState([]);
return (
<div className="App container">
<form onSubmit = {(e) => e.preventDefault()}>
<input type = "search" className = "srh" placeholder = "search about..."
onChange = {(e) => {
const test = data.filter(team => {
return (
team.toLowerCase().includes(e.target.value.toLowerCase())
)
})
setRes(test)
if(e.target.value === '') setRes([])
}}
/>
</form>
<div>
{
res.map((item , i) => (
<p key = {i}>{item}</p>
))
}
</div>
</div>
);
I've made custom hook.
It receives the array as a first param
the search variable as a second
and the property you want to filter by
I hope it's helpfull
export function useSearch(array: any[], search: string, field: string) {
const filteredArray = array.filter((entry) => {
if (search === "") return entry;
else if (
entry[field].toLocaleLowerCase().includes(search.toLocaleLowerCase())
)
return entry;
});
return {
filteredArray
};
}
Them apply the filtered array to your map function
import { useSearch } from "./useSearch";
import { useState } from "react";
const array = [
{
id: 1,
name: "Humberto Guenzo Yoshimoto"
},
{
id: 2,
name: "Diego Braga"
},
{
id: 3,
name: "Hudson Teixeira"
},
{
id: 4,
name: "Matheus Doimo"
}
];
type FilteredArrayTypes = {
id: number;
name: string;
};
export default function App() {
const [searchByFullName, setSearchByFullName] = useState("");
const { filteredArray } = useSearch(array, searchByFullName, "name");
return (
<div className="App">
<h1>Search list</h1>
<input
onChange={(e) => setSearchByFullName(e.target.value)}
type="text"
value={searchByFullName}
placeholder="search"
/>
{filteredArray.map((entry: FilteredArrayTypes) => {
return (
<ul>
<li>{entry.name}</li>
</ul>
);
})}
</div>
);
}
Here goes a sandbox with the code: here