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>
);
}
Related
Goal: I should display the specific contents of a specific button after one of three buttons was clicked. Then, after the specific button is clicked, all three buttons should be hidden and replaced with the contents of the clicked specific button.
Issue: I tried passing props and using if-else statement in terms of conditional rendering but I am having trouble figuring out how to properly state a condition for the functionality to work since the remaining if else statements are ignored. Only the Beef button is working but the rest of the buttons are not.
Source code:
import * as React from "react";
import { Stack } from '#mui/material';
import FoodTraysItemButton from "./FoodTraysItemButton";
import PastaNoodlesButtonsFT from "./foodTraysPages/PastaNoodlesButtonsFT";
import DessertsButtonsFT from "./foodTraysPages/DessertsButtonsFT";
import BeefButtonsFT from "./foodTraysPages/BeefButtonsFT";
import { useState } from "react";
function preventDefault(event) {
event.preventDefault();
}
export default function FoodTraysButtons(props) {
const [myBoolBeef, setmyBoolBeef] = useState(true);
const [myBoolDesserts, setmyBoolDesserts] = useState(true);
const [myBoolPastaNoodles, setmyBoolPastaNoodles] = useState(true);
function toggleBoolBeef() {
setmyBoolBeef(!myBoolBeef);
}
function toggleBoolDesserts() {
setmyBoolDesserts(!myBoolDesserts);
}
function toggleBoolPastaNoodles() {
setmyBoolPastaNoodles(!myBoolPastaNoodles);
}
return (
// stuck here: (I plan to use multiple separate if else statements to work the functionality out but it doesn't work)
<React.Fragment>
{(() => {
// only works here
if (myBoolBeef) {
return (<Landing toggleBoolBeef={toggleBoolBeef} />);
} else{
return <BeefFT/>;
}
// these are ignored:
if (myBoolDesserts) {
return (<Landing toggleBoolDesserts={toggleBoolDesserts} />);
} else{
return <DessertsFT/>;
}
if (myBoolPastaNoodles) {
return (<Landing toggleBoolPastaNoodles={toggleBoolPastaNoodles} />);
} else{
return <PastaNoodlesFT/>;
}
})()}
</React.Fragment>
);
}
function Landing(props) {
return (
<div>
<Stack spacing={0} direction="row" sx={{ mb: 4.5 }}>
<FoodTraysItemButton
title="Beef"
onClick={props.toggleBoolBeef}
/>
<FoodTraysItemButton
title="Desserts"
onClick={props.toggleBoolDesserts}
/>
<FoodTraysItemButton title="Pasta/Noodles" onClick={props.toggleBoolPastaNoodles} />
</Stack>
</div>
);
}
function BeefFT() {
return (
<div>
<BeefButtonsFT />
</div>
);
}
function DessertsFT() {
return (
<div>
<DessertsButtonsFT />
</div>
);
}
function PastaNoodlesFT() {
return (
<div>
<PastaNoodlesButtonsFT />
</div>
);
}
Full source codes in Codesandbox: https://codesandbox.io/s/show-hide-buttons-ralph-ecv9g2?file=/src/FoodTraysButtons.jsx:773-815
How it should look like:
Beef button:
Desserts button:
Pasta Noodles button:
In what way should I implement this in order to achieve its functionality?
Your responses would be highly appreciated as I am exploring MUI and React at the moment. It would be a really big help for my project. Thank you very much!!!
Update FoodTraysButtons to hold a single state, selection that is then used to conditionally render the Landing component or any of BeefFT, DessertsFT, or PastaNoodlesFT component.
export default function FoodTraysButtons(props) {
const [selection, setSelection] = useState();
const selectHandler = (selection) => setSelection(selection);
return (
<React.Fragment>
{!selection && <Landing onSelect={selectHandler} />}
{selection === "beef" && <BeefFT />}
{selection === "dessets" && <DessertsFT />}
{selection === "pastaNoodles" && <PastaNoodlesFT />}
</React.Fragment>
);
}
Update the Landing component to take a single onSelect prop callback.
function Landing({ onSelect }) {
const selectHandler = (selection) => () => onSelect(selection);
return (
<div>
<Stack spacing={0} direction="row" sx={{ mb: 4.5 }}>
<FoodTraysItemButton title="Beef" onClick={selectHandler("beef")} />
<FoodTraysItemButton
title="Desserts"
onClick={selectHandler("desserts")}
/>
<FoodTraysItemButton
title="Pasta/Noodles"
onClick={selectHandler("pastaNoodles")}
/>
</Stack>
</div>
);
}
You need a switch case block instead of multiple boolean value state. Consider this way of structuring your code:
const menuState = {
NONE: "none",
BEEF: "beef",
DESSERTS: "desserts",
PASTA: "pasta"
};
export default function FoodTraysButtons(props) {
const [selectedMenu, setSelectedMenu] = useState(menuState.NONE);
const renderMenu = () => {
switch (selectedMenu) {
case menuState.BEEF:
return <BeefFT />;
case menuState.DESSERTS:
return <DessertsFT />;
case menuState.PASTA:
return <PastaNoodlesFT />;
case menuState.NONE:
default:
return null;
}
};
return (
<React.Fragment>
{selectedMenu === menuState.NONE && (
<Landing setSelectedMenu={setSelectedMenu} />
)}
{renderMenu()}
</React.Fragment>
);
}
function Landing(props) {
return (
<div>
<Stack spacing={0} direction="row" sx={{ mb: 4.5 }}>
<FoodTraysItemButton
title="Beef"
onClick={() => props.setSelectedMenu(menuState.BEEF)}
/>
<FoodTraysItemButton
title="Desserts"
onClick={() => props.setSelectedMenu(menuState.DESSERTS)}
/>
<FoodTraysItemButton
title="Pasta/Noodles"
onClick={() => props.setSelectedMenu(menuState.PASTA)}
/>
</Stack>
</div>
);
}
Working Demo:
NOTE: If you want to always show the button menu then remove the selectedMenu === menuState.NONE wrapper condition.
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 use inView in my component. Until the posts were loaded, the user see the skeleton. But when I press delete button, the skeleton appears and dissappers. Can I stop this quick appearance when press the delete button.
class MyPosts extends Component {
deleteItem = (key) => {
this.props.deleteItem(key);
};
render() {
const { myPosts, classes } = this.props;
let posts = myPosts.map((item) => {
return (
<InView threshold={0}>
{({ ref, inView }) => (
<div ref={ref} inView={inView}>
{inView ? (
<Card>
<Typography >
{item.post}
</Typography>
<Button
onClick={() => this.deleteItem(item.key)}
>
Done
</Button>
</Card>
) : (
<Skeleton />
)}
</div>
)}
</InView>
);
});
return <div>{posts}</div>;
}
}
I have made a call to my api using useEffect and stored the array of items using useState hook but I'm finding it difficult to render those items into a custom component which will also have the data passed.
Here's my react snippets:
export default function CreateCast() {
const [open, setOpen] = useState(false);
const [bibleCastItems, setBibleCastItems] = useState([]);
const classes = useStyles();
const fabStyle = {
bottom: 50.0,
right: 30.0,
position: "fixed"
};
useEffect(()=>{
async function fetchData(){
var items = await APIService.getAllBibleCasts();
// console.log(items);
// console.log(items.data.bibleCasts);
setBibleCastItems([items.data.bibleCasts]);
// items.data.bibleCasts.length > 0 ? setBibleCastItems([items.data.bibleCasts])
// : setBibleCastItems([]);
}
fetchData();
}, []
);
// console.log('bibleCastItems length ' + bibleCastItems.length);
return (
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card plain>
<CardHeader plain color="primary">
<div className={classes.container}>
<div className={classes.left}>
<h4 className={classes.cardTitleWhite}>All BibleCasts</h4>
<p className={classes.cardCategoryWhite}>
Powered by our friends from <b>Unicorn Tech Consultants</b>{" "}
</p>
</div>
</div>
</CardHeader>
<CardBody>
{
bibleCastItems.map((item, index) => <CastItem key={index} bibleCast={item[index]}/>) // this is where I'm facing issue
// bibleCastItems.map((item, index) => {
// console.log(item);
// setMyItem(item);
// return <div key={index}>{index}</div>
// })
}
<div className={classes.right}>
<Fab style={fabStyle} onClick={handleClickOpen}>
<AddIcon />
</Fab>
<UploadFormDialog
open={open}
handleClose={handleClose}
/>
</div>
</CardBody>
</Card>
</GridItem>
</GridContainer>
);
function handleClickOpen(){
setOpen(true);
};
function handleClose(){
setOpen(false);
};
}
Here's my state from browser view:
[![state view][1]][1]
How do I map this state to be a list of components? I'm confused about it
As you can see, I'm using a dialog to create new items and close the dialog once the request is successful. I have one doubt here, how do I tell the main component hosting the dialog that the new data has been fetched and should be added to the state?
My main question here is how to map the items in state to return a list of <CastItem /> component
CastItem Component Snippet
export default function CastItem(props) {
let {bibleCast} = props;
const classes = useStyles();
return <GridContainer>
<GridItem xs={12} sm={6} md={4}>
<Card>
<CardHeader color="info" stats icon>
<CardIcon color="info">
<Streams />
</CardIcon>
</CardHeader>
<CardBody>
<h3 className={classes.cardTitle}>{bibleCast.title}</h3>
<p className={classes.cardCategory}> Reinhard Bonnke</p>
</CardBody>
</Card>
</GridItem>
</GridContainer>
}
CastItem.propTypes = {
bibleCast: PropTypes.object.isRequired,
}
JSON Response from API in console:
[![json response][2]][2]
If you were to create a state variable to represent this response as a list and display that list, how would you go about it, using hooks. Thank you.
[1]: https://i.stack.imgur.com/QkthN.png
[2]: https://i.stack.imgur.com/8Hf11.png
Mistake you are doing is in CreateCast component , form api you are already getting an array again you are passing it inside an array, so it is coming as nested array
Do like this
useEffect(()=>{
async function fetchData(){
var items = await APIService.getAllBibleCasts();
setBibleCastItems(items.data.bibleCasts);
}
fetchData();
}, []
);
For Maping do like this
{
bibleCastItems.map((item, index) => <CastItem key={index} bibleCast={item}/>)
}
// For question how to update parent from child follow below
There are two ways you can set data in a parent component , one is refetch from the api or pass from children to parent and update the state there
I have an example here how to update parent and children,to add names to a list,name list state is maintained in parent component here and child will pass back value to parent by adding name
import { useState } from "react";
import Child from "./Child";
export default function Parent() {
const [list, setList] = useState(["ram"]);
const handleAddName = (name) => {
if (name) {
setList([name, ...list]);
// or you can refetch the list from api here
}
};
return (
<div>
<div style={{ float: "left" }}>
<h1>I am a parent Component</h1>
<ul>
{list &&
list.map((item) => {
return <li key={item}>{item}</li>;
})}
</ul>
</div>
<Child handleSubmit={handleAddName} />
</div>
);
}
Child
import { useState } from "react";
export default function Child(props) {
const [name, setName] = useState("");
const updateNameList = (name) => {
if (name) {
props.handleSubmit(name);
//reset field after data is sent
// you can also save data here making post request respective api
setName("");
}
};
return (
<div style={{ float: "right" }}>
<h1>I am a Child Component</h1>
<p> Add names below</p>
<br />
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={() => updateNameList(name)}>Add</button>
</div>
);
}
refer to this codesand box
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;