I would like to make something like this.
onClick --> post_query() starts --> spinner() shows --> post_query() ends --> modal shows
The problem I have is that spinner is showing before post_query() starts.
And I think it's because state loading is true( const [loading, setLoading] = useState(true); )
How can I make the spinner start after the post_query() start? (I tried using useEffect() but failed to handle with useEffect)
function Header() {
const [query, setQuery] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [keys, setKeys] = useState([]);
const [loading, setLoading] = useState(true);
const modal_open = () => {
setIsOpen(true);
};
const modal_close = () => {
setIsOpen(false);
};
const post_query = (e) => {
var result = new Map()
axios.post('http://localhost:3000/api/' + query)
.then(function(response){
var content=JSON.parse(JSON.stringify(response)).data
for (var i=0;i<content.data.length;i++){
result.set(content.data[i].image_id, content.data[i].caption)
}
var key = Array.from(result.keys());
setKeys(key);
}).catch(err => {
console.log("error");
alert(err);
})
.finally(() => setLoading(false));
};
return (
<div className="header">
<Link to="/">
<img
src="logo.png"
className="header__logo"
/>
</Link>
<div className="header__search">
<input className="header__searchInput" type="search"
onChange={
(e) => {
setQuery(e.target.value);
}
}/>
<SearchIcon className="header__searchIcon"
onClick={(e)=> { post_query(); modal_open();}}/>
{loading && <div><CircularProgress className="spinner"/></div>}
{!loading &&
<Modal
open={isOpen}
onClose={modal_close} >
<Fade in={isOpen}>
<div className='modal_frame'>
<img src={'img'+keys[0]+'.jpg'} className='modal_img' />
</div>
</Fade>
</Modal>
}
</div>
</div>
);
}
export default Header;
Just add setLoading to true when call post_query and change initail value to false
const [loading, setLoading] = useState(false);
const post_query = (e) => {
var result = new Map()
setLoading(true)
axios.post('http://localhost:3000/api/' + query)
...
};
Related
I don't understand why my page can't recognize other pages when I click (for example on page 2, the same page appears again and again)
This is in MealNew.js component:
import React, {useEffect, useState } from "react";
import './MealNew.css';
import Card from "../UI/Card";
import AppPagination from "./AppPagination";
const MealNew = () => {
const [data, setData] = useState([]);
const [showData, setShowData] = useState(false);
const [query,setQuery] = useState('');
const[page,setPage] = useState(9);
const[numberOfPages,setNumberOfPages]= useState(10);
const handleClick = () => {
setShowData(true);
const link = `https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=991fbfc719c743a5896bebbd98dfe996&page=${page}`;
fetch (link)
.then ((response)=> response.json())
.then ((data) => {
setData(data.results)
setNumberOfPages(data.total_pages)
const elementFood = data?.map((meal,key) => {
return (<div key={key}>
<h1>{meal.title}</h1>
<img src={meal.image}
alt='e-meal'/>
</div> )
})
const handleSubmit = (e) => {
e.preventDefault();
handleClick();
}
useEffect(()=> {
handleClick();
},[page])
return (
<Card className="meal">
<form onSubmit={handleSubmit}>
<input
className="search"
placeholder="Search..."
value={query}
onChange={(e)=>setQuery(e.target.value)}/>
<input type='submit' value='Search'/>
</form>
<li className="meal">
<div className = 'meal-text'>
<h5>{showData && elementFood}</h5>
<AppPagination
setPage={setPage}
pageNumber={numberOfPages}
/>
</div>
</li>
</Card>
) }
export default MealNew;
This is in AppPagination.js component:
import React from "react";
import { Pagination } from "#mui/material";
const AppPagination = ({setPage,pageNumber}) => {
const handleChange = (page)=> {
setPage(page)
window.scroll(0,0)
console.log (page)
}
return (
<div >
<div >
<Pagination
onChange={(e)=>handleChange(e.target.textContent)}
variant="outlined"
count={pageNumber}/>
</div>
</div>
)
}
export default AppPagination;
Thanks in advance, I would appreciate it a lot
The only error I am getting in Console is this:
Line 64:3: React Hook useEffect has a missing dependency: 'handleClick'. Either include it or remove the dependency array react-hooks/exhaustive-deps
You are not following the spoonacular api.
Your link looks like this:
https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=<API_KEY>&page=${page}
I checked the spoonacular Search Recipes Api and there's no page parameter you can pass. You have to used number instead of page.
When you receive response from the api, it returns the following keys: offset, number, results and totalResults.
You are storing totalResults as totalNumberOfPages in state which is wrong. MUI Pagination count takes total number of pages not the total number of records. You can calculate the total number of pages by:
Math.ceil(totalRecords / recordsPerPage). Let say you want to display 10 records per page and you have total 105 records.
Total No. of Pages = Math.ceil(105/10)= 11
Also i pass page as prop to AppPagination component to make it as controlled component.
Follow the documentation:
Search Recipes
Pagination API
Complete Code
import { useEffect, useState } from "react";
import { Card, Pagination } from "#mui/material";
const RECORDS_PER_PAGE = 10;
const MealNew = () => {
const [data, setData] = useState([]);
const [showData, setShowData] = useState(false);
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
const [numberOfPages, setNumberOfPages] = useState();
const handleClick = () => {
setShowData(true);
const link = `https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=<API_KEY>&number=${page}`;
fetch(link)
.then((response) => response.json())
.then((data) => {
setData(data.results);
const totalPages = Math.ceil(data.totalResults / RECORDS_PER_PAGE);
setNumberOfPages(totalPages);
});
};
const elementFood = data?.map((meal, key) => {
return (
<div key={key}>
<h1>{meal.title}</h1>
<img src={meal.image} alt='e-meal' />
</div>
);
});
const handleSubmit = (e) => {
e.preventDefault();
handleClick();
};
useEffect(() => {
handleClick();
console.log("first");
}, [page]);
return (
<Card className='meal'>
<form onSubmit={handleSubmit}>
<input className='search' placeholder='Search...' value={query} onChange={(e) => setQuery(e.target.value)} />
<input type='submit' value='Search' />
</form>
<li className='meal'>
<div className='meal-text'>
<h5>{showData && elementFood}</h5>
<AppPagination setPage={setPage} pageNumber={numberOfPages} page={page} />
</div>
</li>
</Card>
);
};
const AppPagination = ({ setPage, pageNumber, page }) => {
const handleChange = (page) => {
setPage(page);
window.scroll(0, 0);
console.log(page);
};
console.log("numberOfPages", pageNumber);
return (
<div>
<div>
<Pagination
page={page}
onChange={(e) => handleChange(e.target.textContent)}
variant='outlined'
count={pageNumber}
/>
</div>
</div>
);
};
export default MealNew;
I want to get Movie list(API) when I submit,
so I made my API link by using Template literals like this ▼
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=8&page=${Math.round(Math.random()*100)}&query_term=${movieName}&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
};
then, I filled 'movieName' into the array of useEffect. because I want to Refetch the API everytime the 'movieName' Changed.
but! it dosen't work:(
what is the problem?
▼ code i wrote
import { useEffect, useRef, useState } from "react";
import Movie from "../components/Movie";
function Home(){
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const [movieSearch, setMovieSearch] = useState('');
const [movieName, setMovieName] = useState('');
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=8&page=${Math.round(Math.random()*100)}&query_term=${movieName}&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
};
const onChange = (event) =>{
setMovieSearch(event.target.value)
}
const onSubmit = (event)=>{
event.preventDefault();
setMovieName(movieSearch)
}
useEffect(() => {
getMovies();
}, [movieName]);
return (
<>
<h4>Search</h4>
<form onSubmit={onSubmit}>
<input
onChange={onChange}
type="text"
value={movieSearch}
placeholder="..."
></input>
</form>
{loading ? (
<h3>Loading</h3>
) : (
<div>
{movies.map((item) => (
<Movie
key={item.id}
id={item.id}
title={item.title}
year={item.year}
medium_cover_image={item.medium_cover_image}
rating={item.rating}
runtime={item.runtime}
genres={item.genres}
summary={item.summary}
/>
))}
</div>
)}
</>
);
}
why not trying to move getMovies() that is inside the useEffect to the onSubmit() function right after you set the movie name?
Because you don't update the answerName state on the onChange event of the search input
const onChange = (event) =>{
const value = event.target.value
setMovieSearch(value)
setMovieName(value)
}
I have a modal that pops up on a dashboard if a condition is true and renders a checkbox. I can't seem to toggle to Modal off on the onClick function. Here is an example of the code.
Dashboard
const conditionalAgreement = false;
<Modal showModal={showModal} conditionalAgreement={conditionalAgreement} />
Modal
const Modal = ({ conditionalAgreement }) => {
const [showModal, setShowModal] = useState(false);
const [checkboxCondition, setCheckboxCondition = useState(false);
useEffect(() => {
if (conditionalAgreement) {
setShowModal(true);
}
}, [conditionalAgreement]);
const OnChangeHandler = () => {
setCheckboxCondition(!setCheckboxCondition);
};
const OnClickHandler = () => {
setShowModal(false);
};
return (
<div className={css.modal}>
<div className={css.checkbox}>
<CheckboxComponent
value={checkboxCondition}
onChange={OnChangeHandler}
description={tick this box"}
/>
</div>
<div className={css.buttonContainer}>
<ButtonComponent
onClick={OnClickHandler}
>
Save
</ButtonComponent>
</div>
</div>
);
};
export default Modal;
Dashboard:
const Dashboard = () => {
const [showModal, setShowModal] = useState(false);
return (
{showModal && (
<Modal showModal={showModal} closeModal={() => setShowModal(false)} />
)}
)
}
Modal:
const Modal = ({ showModal, closeModal }) => {
const [checkboxCondition, setCheckboxCondition] = useState(false);
const onChangeHandler = () => {
setCheckboxCondition(!checkboxCondition);
};
const onClickHandler = () => {
closeModal();
};
return (
<div className={css.modal}>
<div className={css.checkbox}>
<CheckboxComponent
value={checkboxCondition}
onChange={onChangeHandler}
description={tick this box"}
/>
</div>
<div className={css.buttonContainer}>
<ButtonComponent
onClick={onClickHandler}
>
Save
</ButtonComponent>
</div>
</div>
);
};
Now, as mention by #RobinZigmond something in your Dashboard component should set showModal to true so that your Modal appears.
const Room = (props) => {
const [hasError, setErrors] = useState(false);
const [rooms, setRooms] = useState([]);
return (
<div> <a onClick={() => deleteRoom()}</div>
)
}
const deleteRoom = () => {
//How to update setRooms here
}
How do I update setRooms in deleteRoom method?
You simply have to define the deleteRoom function inside the Room component.
const Room = (props) => {
const [hasError, setErrors] = useState(false);
const [rooms, setRooms] = useState([]);
const deleteRoom = () => {
setRooms(...);
}
return (
<div> <a onClick={() => deleteRoom()}</div>
)
}
You can pass setRooms as an argument in deleteRoom and call it there.
For example
const Room = (props) => {
const [hasError, setErrors] = useState(false);
const [rooms, setRooms] = useState([]);
return (
<div> <a onClick={() => deleteRoom(setRooms)}</div>
)
}
const deleteRoom = (setRooms) => {
//How to update setRooms here
setRooms(...)
}
You have 2 options:
Put your deleteRoom method inside the Room component
const Room = (props) => {
const [hasError, setErrors] = useState(false);
const [rooms, setRooms] = useState([]);
const deleteRoom = () => {
... use your state..
}
return (
<div> <a onClick={() => deleteRoom()}</div>
)
}
Pass your useState functions to deleteRoom as arguments.
const Room = (props) => {
const [hasError, setErrors] = useState(false);
const [rooms, setRooms] = useState([]);
return (
<div> <a onClick={() => deleteRoom(setRooms, setErrors)}</div>
)
}
const deleteRoom = (setRooms, setErrors) => {
you can use setRooms() and setErrors() here
}
I was trying to pass response message back from server and displayed it in the UI, but seems whenever the response I passed in the second <Dialog>, the response is undefined, I think response is only defined in the handle submit function.
Is there a way that I can pass the response into the <Dialog>?
const AppNav = () => {
//...
const [popupOpen, setPopupOpen] = useState(false);
const [secondPopupOpen, setSecondPopupOpen] = useState(false);
const [input, setInput] = useState('');
const renderContent = () => {
return (
<form>
<input
type="text"
onChange={e => setInput(e.target.value)}
value={input}
onBlur={() =>delay(() =>setInput(''),150}
placeholder='placeholder here...'
/>
</form>
);
};
const renderAction = () => {
const handleSubmit = async() => {
//some server calls here
//response is what got back from server call
const response = await service.send(request)
try{
if(response.result ==='success'){
setPopupOpen(false);
setSecondPopupOpen(true);
}else { throw new Error()}
}
catch (err) {throw new Error()}
return response;
}
return (
<Button onClick={handleSubmit}>Submit</Button>
);
};
//...
return (
<Button onClick={() => setPopupOpen(true)}>open button</Button>
<Dialog
open={popupOpen}
renderContent={renderContent}
renderAction={renderAction}
/>
<Dialog
open={secondPopupOpen}
renderContent={(response) => <span><>{response.resultMessage}</span>}
renderAction={() => {}}
/>
)
}
Dialog.js
const Dialog = props => {
const { renderAction, renderContent, renderTitle} = props;
return (
render={
({getTitleProps, getContentProps, getActionProps}) => (
<DialogTitle {...getTitleProps()}>
{renderTitle ? renderTitle() : <span>Title</span>}
</DialogTitle>
<DialogContent {...getContentProps()}>
{renderContent ? renderContent() : <span>Content</span>}
</DialogContent>
<DialogAction {...getActionProps()}>
{renderAction ? renderAction() : <span>Action</span>}
</DialogAction>
)
}
)
}
I think you should refactor renderAction function and store the state of request somewhere as well as error ( or combine them into one state ).
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const handleSubmit = async() => {
const response = await service.send(request)
try {
if(response.result ==='success'){
setPopupOpen(false);
setSecondPopupOpen(true);
setResponse(response);
return;
}
setError(new Error('error'));
}
catch (error) {
setError(error);
}
}
const renderAction = () => {
if(response) {
return response;
}
if(error) {
return error.message // to-do
}
return <Button onClick={handleSubmit}>Submit</Button>
};