I have an online restaurant app that fetches the menu items from firebase. It fetches everytime I add something to the cart, which makes the menu items disappear and then reappear for a second. It also scrolls back to the top of the page after every click. How do I prevent this reload? e.preventDefault() doesnt work. Is it due to the passing of data from the child to the parent? I'm not sure.
//imports
import React, { useState, useEffect } from "react";
const Menue = (props) => {
const [cartLength, setCartLength] = useState(0);
const [indischeGerichte, setIndischeGerichte] = useState([])
useEffect(() => {
fire.firestore().collection("Indische Gerichte")
.orderBy("id", "asc")
.get()
.then(snapshot => {
var ind = []
snapshot.forEach(doc => {
ind.push(doc.data())
})
setIndischeGerichte(ind)
}).catch(error => {
console.log(error)
});
}, [])
function addToCart(e, item) {
e.preventDefault();
var updatedCart = { ...props.cart };
if (!updatedCart[item.title]) {
updatedCart[item.title] = [1, item.price];
} else {
updatedCart[item.title][0]++;
}
setCartLength(cartLength + 1);
props.setTheCart(updatedCart, cartLength);
}
return (
<div>
<Typography variant="h3" component="h2" gutterBottom>
Speisekarte
</Typography>
<div id="ind">
<Typography variant="h4">Indian Foods:</Typography>
{indischeGerichte.map((indFood, idx) => {
return (
<div key={idx}>
<Card className="foodCard">
<Typography variant="h4">{indFood.title}</Typography>
<Button
variant="contained"
color="secondary"
onClick={(e) => addToCart(e, indFood)}
>
1x In den Einkaufswagen
</Button>
</Card>
</div>
);
})}
</div>
</div>
);
};
export default Menue;
Related
What I have is a list that was fetched from an api. This list will be filtered based on the input. But at the first render it will render nothing, unless I press space or add anything to the input.
usinnng react when i click on the button it filters but I want at the first render to show the whole list before clicking
import Button from "react-bootstrap/Button";
import Card from "react-bootstrap/Card";
import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import { useEffect, useState } from "react";
function News() {
const [news, setNews] = useState([]);
const [listing, setLists] = useState([]);
const fetchDataList = () => {
return fetch("https://api.npoint.io/d275425a434e02acf2f7")
.then((response) => response.json())
.then((data) => {
setLists(data.News);
// data.News[0].id = 0;
console.log(listing);
});
};
useEffect(() => {
fetchDataList();
}, []);
const fetchData = () => {
return fetch("https://api.npoint.io/91298d970c27e9a06518")
.then((response) => response.json())
.then((data) => {
setNews(data.newsCategory);
console.log(news);
});
};
useEffect(() => {
fetchData();
}, []);
const [filteredCat, setFiltredCat] = useState(null);
useEffect(() => {
setFiltredCat(setLists());
}, []);
function filteredCategory(typecat) {
let filteredCategory = listing.filter(
(type) => type.categoryID === typecat
);
return filteredCategory;
}
function handleCategory(e) {
let typeCategory = e.target.id
typeCategory
? setFiltredCat(filteredCategory(typeCategory))
: filteredCategory(setLists);
}
return (
<>
<p>Media</p>
<h2>Top </h2>
<div>
{news &&
news.map((idx) => (
<Button id={idx.id} variant="secondary" onClick={handleCategory}>
{idx.id} {idx.name}
</Button>
))}
</div>
<Container>
<Row>
<Col>
{filteredCat &&
filteredCat.map((list) => (
<Card style={{ width: "18rem" }}>
<Card.Img variant="top" src="holder.js/100px180" />
<Card.Body>
<Card.Title>{list.title}</Card.Title>
<Card.Text>{list.description}</Card.Text>
<Button variant="primary">{list.categoryID}</Button>
</Card.Body>
</Card>
))}
</Col>
</Row>
</Container>
</>
);
}
export default News;
You need to set the category as well when the data comes in,
Below your setNews(data.newsCategory); you can add setFiltredCat(filteredCategory(news[0].id.toString()));
edit:
To be sure the news has been set before setting your initial filter you could also add a hook which is watching for changes to news. Checkout this codesandbox for an example
useEffect(() => {
if (news.length <= 0) return;
setFiltredCat(filteredCategory(news[0].id.toString()));
}, [news]);
Edit: to return all listings initially you could use the same concept
useEffect(() => {
setFiltredCat(listing);
}, [listing]);
I am trying to revalidate the data on react-modal close using SWR in a NextJS project.
I am using the SWR hook like so.
const { data, error, isValidating } = useSWR(
process.env.NEXT_PUBLIC_APP_URL + `/api/users`,
fetcher,{
revalidateIfStale: true,
revalidateOnFocus: true,
revalidateOnMount:true,
}
);
useEffect(() => {
if (data) {
setUsers(data.users);
}
}, [data, isValidating, users]);
//handle loading..
//handle error..
return (
<main className="mx-auto max-w-7xl ">
<Header title="All users"/>
<UsersList users={users} />
</main>
);
I am fetching a list of users and displaying them.
const usersList = users.((user) => (
<div className="space-x-5 text-sm" key={user.id}>
{user.name}
<DisableModal id={user.id} isDisabled={user.active}/>
</div>
));
I have a react modal that allows us to disable the users, once I have disabled the users with handle click.
When the modal closes the data is not being refetched.
This is a sample modal from the docs.
When I close the modal, and can see the list of users. They are not refreshed and not using revalidations with use SWR.
export const DisableModal = ({
id,
isDisabled,
}) => {
const [disableModalIsOpen, setDisableModalIsOpen] = useState(false);
function closeDisableModal() {
setDisableModalIsOpen(false);
}
function openPublishModal() {
setDisableModalIsOpen(true);
}
const handleDisableUser = async () => {
//disable logic in rest call.
closeDisableModal();
}
....
}
You can revalidate the data manually using mutate when the onAfterClose callback in the modal gets triggered.
export const DisableModal = () => {
const [showModal, setShowModal] = useState(false);
const { mutate } = useSWRConfig()
return (
<>
<button onClick={() => { setShowModal(true) }}>Trigger Modal</button>
<ReactModal
isOpen={showModal}
onAfterClose={() => {
mutate(process.env.NEXT_PUBLIC_APP_URL + '/api/users')
}}
contentLabel="Minimal Modal Example"
>
<button onClick={() => { setShowModal(false) }}>Close Modal</button>
</ReactModal>
</>
)
}
Calling mutate(process.env.NEXT_PUBLIC_APP_URL + '/api/users') will broadcast a revalidation message to SWR hook with that given key. Meaning the useSWR(process.env.NEXT_PUBLIC_APP_URL + '/api/users', fetcher, { ... }) hook will re-run and return the updated users data.
The below code adds a next button to get the next 20 items from my backend, on clicking the button the data changes and I get my next 20 items, but the url does not change.
function PokemonList() {
const classes = useStyles();
let [pageNum, setPageNum] = useState(0);
const { loading, error, data } = useQuery(pokemonList, { variables: { pageNum: pageNum } });
function handleClick(e){
e.preventDefault();
setPageNum(parseInt(pageNum)+1)
}
if(error) {
return <h1> error</h1>;
}
if(loading) {
return <h1> loading</h1>;
}
return (
<div className="App">
{data.pokemonList.map((data) => (
<Card className={classes.card} variant='outlined'>
<CardHeader className={classes.titleHead} title={data.id} />
<CardMedia
className={classes.media}
component='img'
image={data.url}
title='image'
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='span'>
<p>{data.name}</p>
<br/>
<br/>
<br></br>
</Typography>
</CardContent>
</Card>
))}
<Link onClick={handleClick} className='characterlink2' to={`/pokemon/page/${parseInt(pageNum)+1}`}>
<button>
Next
</button>
</Link>
</div>
);
}
export default PokemonList;
How can I fix this? I am not sure that the "to" and "onClick" work together. How do I change the url along with the data?
Issue
e.preventDefault(); in the click handler prevents the default navigation action from occurring.
Solution
I don't see any reason for this action to be prevented, so I suggest removing this call to prevent the default action.
function handleClick(e){
setPageNum(page => page + 1);
}
Preferred solution
Assuming you've a route with path="/pokemon/page/:page" you should use the useParams hook and "sniff" the current page. This completely eliminates the need to synchronize the URL path and local React state, there's only one source of truth, the URL path.
import { useParams } from 'react-router-dom';
...
function PokemonList() {
const classes = useStyles();
const { page } = useParams();
const { loading, error, data } = useQuery(
pokemonList,
{ variables: { pageNum: page } },
);
if (error) {
return <h1>error</h1>;
}
if (loading) {
return <h1>loading</h1>;
}
return (
<div className="App">
{data.pokemonList.map((data) => (
...
))}
<Link
className='characterlink2'
to={`/pokemon/page/${Number(page) + 1}`}
>
<button type="button">Next</button>
</Link>
</div>
);
}
I am trying to toggle between add and remove buttons in reactjs, it works fine until I reload the page, how do I make this change persist? as the button changes to "add to bin" from "remove from bin" on reload. Below is my code explaining this:
import { useMutation } from "#apollo/client";
import { UPDATE_IMAGE } from "./mutation";
import { useState } from 'react';
function NewBin(props) {
const [uu, {err}] = useMutation(UPDATE_IMAGE);
const [toggle,setToggle] = useState(false)
const addBin = async () => {
await uu({
variables: {
id: props.data.id,
url: props.data.url,
description: props.data.description,
posterName: props.data.posterName,
binned: true,
userPosted: props.data.userPosted
},
});
};
const removeBin = async () => {
await uu({
variables: {
id: props.data.id,
url: props.data.url,
description: props.data.description,
posterName: props.data.posterName,
binned: false,
userPosted: props.data.userPosted
},
});
};
const comp1 = async () => {
addBin();
setToggle(true);
}
const comp2 = async () => {
removeBin();
setToggle(false);
}
return (
<div className="Appp">
{toggle ? <button onClick={() => comp2()}>Remove from Bin</button>
: <button onClick={() => comp1()}>Add to Bin</button>
}
</div>
);
}
export default NewBin;
NewBin's parent:
function UnsplashPosts() {
const classes = useStyles();
const { loading, error, data } = useQuery(unsplashImages);
if(error) {
return <h1> error</h1>;
}
if(loading) {
return <h1> loading</h1>;
}
return (
<div className="App">
{data.unsplashImages.map((data) => (
<Card className={classes.card} variant='outlined'>
<CardHeader className={classes.titleHead} title={data.posterName} />
<CardMedia
className={classes.media}
component='img'
image={data.url}
title='image'
/>
<CardContent>
<Typography variant='body2' color='textSecondary' component='span'>
<p>{data.description}</p>
<NewBin data={data}/>
<br/>
<br/>
<br></br>
</Typography>
</CardContent>
</Card>
))}
</div>
);
}
The binned field shows true or false if it is in the bin or not.
You can persist the toggle state to localStorage, and initialize from localStorage.
Use a state initializer function to read from localStorage and provide the initial state value.
Use an useEffect hook to persist the updated toggle state to localStorage upon update.
Example:
function NewBin(props) {
...
const [toggle, setToggle] = useState(() => {
// Load saved state from localStorage or provide fallback
return JSON.parse(localStorage.getItem("toggle")) ?? false;
});
useEffect(() => {
// Persist state to localStorage
localStorage.setItem("toggle", JSON.stringify(toggle));
}, [toggle]);
...
I am a beginner with React. I have a project I'm working on with some sample travel tours. I would like to use a "read more/show less" feature for the description of each tour. The read more/show less button is toggling, but it's showing more or less description for all of the tours when clicked, when I want it to just toggle the tour that's clicked. In other words, it's updating the state for ALL tours, rather than just the one that's clicked. Hopefully that makes sense. Please help! Thanks in advance.
import React, { useState, useEffect } from 'react';
import './index.css';
const url = 'https://course-api.com/react-tours-project';
const Tour = () => {
const [tourItem, setTourItem] = useState('');
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item) => {
return (
<div key={item.id}>
<article className='single-tour'>
<img src={item.image} alt={item.name} />
<footer>
<div className='tour-info'>
<h4>{item.name}</h4>
<h4 className='tour-price'>
${item.price}
</h4>
</div>
{readMore ? (
<p>
{item.info}
<button
onClick={() => setReadMore(false)}
>
Show Less
</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + '...'}
<button
onClick={() => setReadMore(true)}
>
Read More
</button>
</p>
)}
<button
className='delete-btn'
onClick={() => removeItem(item.id)}
>
Not Interested
</button>
</footer>
</article>
</div>
);
})}
</>
);
};
export default Tour;
Good question! It happened because you share the readMore state with all of the tour items. You can fix this by encapsulating the tour items into a component.
It should look something like this;
The component that encapsulates each tour items
import React, {useState} from "react";
import "./index.css";
const SpecificTourItems = ({item, removeItem}) => {
const [readMore, setReadMore] = useState(false);
return (
<div key={item.id}>
<article className="single-tour">
<img src={item.image} alt={item.name} />
<footer>
<div className="tour-info">
<h4>{item.name}</h4>
<h4 className="tour-price">${item.price}</h4>
</div>
{readMore ? (
<p>
{item.info}
<button onClick={() => setReadMore(false)}>Show Less</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + "..."}
<button onClick={() => setReadMore(true)}>Read More</button>
</p>
)}
<button className="delete-btn" onClick={() => removeItem(item.id)}>
Not Interested
</button>
</footer>
</article>
</div>
);
};
export default SpecificTourItems;
the component that fetch & maps all the tour items (your old component :))
import React, {useState, useEffect} from "react";
import SpecificTourItems from "./SpecificTourItems";
const url = "https://course-api.com/react-tours-project";
const Tour = () => {
const [tourItem, setTourItem] = useState("");
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item, key) => {
return (
<SpecificTourItems item={item} removeItem={removeItem} key={key} />
);
})}
</>
);
};
export default Tour;
I hope it helps, this is my first time answering question in Stack Overflow. Thanks & Good luck!