I'm trying to fetch the shop and it's coupons, I have two model one for the shop and one for the coupon, also two routers, one for fetching shops and one for fetching coupons, the shops are fetching fine and showing in client side, but the coupons are not showing in the client side. When /coupons/${shopName} I try it in postman it works fine, but in the client side not, I don't know why. Console log is giving me [object Object]
export default function ShopPage() {
const [shop, setShop] = useState("");
const shopName = useParams().shopName;
const [coupons, setCoupons] = useState([]);
useEffect(() => {
const fetchShop = async () => {
const res = await axios.get(`/shops/${shopName}`);
setShop(res.data);
console.log(res.data);
};
fetchShop();
}, [shopName]);
useEffect(() => {
const fetchShopCoupons = async () => {
const response = await axios.get(`/coupons/${shopName}`);
setCoupons(response.data);
console.log("Shop Coupons are:" + response.data);
};
fetchShopCoupons();
}, []);
return (
<>
<Box>
<Stack>
<Stack >
<Avatar alt={(shop.shopName)}
src={shop.shopPic}/>
<Stack>
<Box>
<Typography>
{shop.shopName}
</Typography>
</Box>
</Box>
</Stack>
</Stack>
<Box>
<Coupons coupons={coupons}/>
</Box>
</Stack>
</Box>
</>
)
}
Coupons Component:
export default function Coupons({ coupons = [] }) {
const [filteredResults, setFilteredResults] = useState([]);
const [searchInput, setSearchInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const filter = (e) => {
const keyword = e.target.value;
if (keyword !== '') {
const filteredData = coupons.filter((coupon) => {
return Object.values(coupon)
.join('')
.toLowerCase()
.includes(searchInput.toLowerCase())
})
setFilteredResults(filteredData)
} else {
setFilteredResults(coupons);
}
setSearchInput(keyword);
}
console.log("filtered Coupons are:", filteredResults);
return (
<div className="coupons">
<div className="couponsContainer">
<div className="couponsSearchContainer">
<div className="couponsSearch">
<div class="couponsSearchIconContainer">
<SearchIcon class="w-5 h-5" />
</div>
<input type="text"
className="couponsSearchInput"
placeholder="بحث"
name="couponSearchText"
id="couponSearchText"
onChange={filter}
/>
</div>
{/* ENDS OF COUPONSSEARCHCONTAINER */}
</div>
{/* ENDS OF COUPONSSEARCH */}
<div className="couponsBox">
{isLoading ? (
<Box sx={{ display: 'flex' }}>
<CircularProgress />
</Box>
) : (
filteredResults.length > 0 ? (
filteredResults.map((f) => (
<Coupon coupon={f} />
))
) : (
coupons.sort((a, b) =>
new Date(b.createdAt) - new Date(a.createdAt))
.map((c) => (
<Coupon coupon={c} />
)))
)
}
</div>
{/* ENDS OF COUPONSBOX */}
</div>
{/* ENDS OF COUPONSCONTAINER */}
</div>
//ENDS OF COUPONS
);
}
Maybe you could try response.data.value or response.data.value[0], its works for me.
The problem can be with useEffect() you are using for fetching "shopCpupons". The dependency array of useEffect which fetches "shopCoupons" is empty('[]'), which means that that useEffect will immediately call the fetchShopCoupons() function as soon as the page loads, but at that time you will not have the "shopName" which is the endpoint you need in the fetchShopCoupons API call:
const response = await axios.get(/coupons/$**{shopName}**);
1st Solution) So the solution is that in the 2nd useEffect which fetches shop coupons you can add "shopname" as dependency in dependency array, just like you did it in 1st useEffect which fetches shopdetails, The code can look like this
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
export default function ShopPage() {
const [shop, setShop] = useState("");
const shopName = useParams().shopName;
const [coupons, setCoupons] = useState([]);
useEffect(() => {
const fetchShop = async () => {
const res = await axios.get(`/shops/${shopName}`);
setShop(res.data);
console.log(res.data);
};
fetchShop();
}, [shopName]);
useEffect(() => {
const fetchShopCoupons = async () => {
const response = await axios.get(`/coupons/${shopName}`);
setCoupons(response.data);
console.log("Shop Coupons are:" + response.data);
};
fetchShopCoupons();
}, [shopName]);
return (
<>
<Box>
<Stack>
<Stack >
<Avatar alt={(shop.shopName)}
src={shop.shopPic}/>
<Stack>
<Box>
<Typography>
{shop.shopName}
</Typography>
</Box>
</Box>
</Stack>
</Stack>
<Box>
<Coupons coupons={coupons}/>
</Box>
</Stack>
</Box>
</>
)
}
Here I added 'shopname' as dependency in 2nd useEffect.
2nd Solution: or else you can use only one useEffect and control your shop and coupons variables like this
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
export default function ShopPage() {
const [shop, setShop] = useState("");
const shopName = useParams().shopName;
const [coupons, setCoupons] = useState([]);
useEffect(() => {
const fetchShop = async () => {
const res = await axios.get(`/shops/${shopName}`);
setShop(res.data);
console.log(res.data);
};
const fetchShopCoupons = async () => {
const response = await axios.get(`/coupons/${shopName}`);
setCoupons(response.data);
console.log("Shop Coupons are:" + response.data);
};
fetchShop();
fetchShopCoupons();
}, [shopName]);
return (
<>
<Box>
<Stack>
<Stack >
<Avatar alt={(shop.shopName)}
src={shop.shopPic}/>
<Stack>
<Box>
<Typography>
{shop.shopName}
</Typography>
</Box>
</Box>
</Stack>
</Stack>
<Box>
<Coupons coupons={coupons}/>
</Box>
</Stack>
</Box>
</>
)
}
Here as you can see I merged 2 useEffects into one useEffect as both your APIs are dependent upon "shopname" dependency, so once you will get shopname from params the program will first call the fetchShop() function which will fetch shop details and after that it will call fetchCoupons() which will call coupons for the shop. You can also rearrange the order of these function calls as per the requirements of project. This approach will make you code leaner and cleaner. However you can choose to use the best fit solutions out of 2 as per the requirement of the project.
Related
Sometimes when I click the Add button the function is adding an empty array to the JSON file. But sometimes it works as intended. I've tried moving the variables and state around and it is still doing the same thing. The exercise prop comes from a search of an API and the prop is passed down to this component. The component displays a list of saved exercise cards that can be added to the database. Why is this happening?
import {
Button,
Card,
CardContent,
CardMedia,
Container,
Typography,
} from "#mui/material";
import { Box } from "#mui/system";
import React, { useState } from "react";
const ExerciseCard = ({ exercise }) => {
const [selectedExercise, setSelectedExercise] = useState([]);
const [selectedExerciseName, setSelectedExerciseName] = useState();
const [fetchedData, setFetchedData] = useState([]);
const addExerciseToDB = async () => {
await fetch("http://localhost:3001/savedexercises")
.then((res) => {
return res.json();
})
.then((data) => {
setFetchedData(data);
return fetchedData;
});
const savedFetchedName = fetchedData.map((fetched) => fetched.name);
setSelectedExercise([]);
setSelectedExercise({
apiId: exercise.id,
name: exercise.name,
target: exercise.target,
gifUrl: exercise.gifUrl,
});
setSelectedExerciseName(exercise.name);
if (savedFetchedName.includes(selectedExerciseName)) {
console.log("already added exercise");
} else {
console.log("adding new exercise");
await fetch("http://localhost:3001/savedExercises", {
method: "POST",
body: JSON.stringify(selectedExercise),
headers: { "Content-Type": "application/json" },
});
}
};
return (
<>
<Container maxWidth="xl">
<Box>
<Card>
<CardMedia
component="img"
alt={exercise.name}
image={exercise.gifUrl}
/>
<CardContent sx={{ pb: 2, height: "75px" }}>
<Typography variant="h5" sx={{ pb: 1 }}>
{exercise.name.toUpperCase()}
</Typography>
<Typography variant="body2">
{exercise.target.toUpperCase()}
</Typography>
</CardContent>
<Box>
<Box>
<Button
variant="contained"
color="error"
size="medium"
sx={{ m: 2 }}
onClick={() => addExerciseToDB()}
>
Add
</Button>
</Box>
</Box>
</Card>
</Box>
</Container>
</>
);
};
export default ExerciseCard;
await fetch("http://localhost:3001/savedexercises")
.then((res) => {
return res.json();
})
.then((data) => {
setFetchedData(data);
return fetchedData;
});
I do have the category id and I can also fetch singleCategory but i'm unable to fetch the product arrayObj within the categories arrayObj?
i want to create a single category page which displays all products in the selected category
all the requests and data is fetched from commerce.js api
import { Grid, Container, Typography } from "#material-ui/core";
import { Link } from "react-router-dom";
import Product from "../Product";
import Spinner from "../Spinner";
import Banner from "../Banner";
import "./style.css";
const Products = ({ categories, addProduct }) => {
const [singleCategory, setSingleCategory] = useState({});
const fetchCategory = async (id) => {
const response = await commerce.categories.retrieve(id);
setSingleCategory({ response });
};
useEffect(() => {
const id = window.location.pathname.split("/");
fetchCategory(id[2]);
}, []);
console.log(singleCategory)
console.log(categories)
if (!categories.length) return <Spinner />;
return (
<div>
<Banner />
<div id="products">
{categories.map((category, index) =>
category.productsData.length ? (
<div
key={category.id}
className="contents"
style={{
backgroundImage:
index % 2 !== 0
? "linear-gradient(to bottom right, #3d4a5d,#3d4a5d, #bb86fc)"
: "",
}}
>
<Container>
<Link className="headline" to={`category-view/${category.id}`}>
<Typography className="headline" variant="h3" component="h2">
{category.name}
</Typography>
</Link>
<Grid container spacing={4}>
{category.productsData.map((product) => (
<Grid key={product.id} item xs={12} sm={6} md={4}>
<Product
product={product}
addProduct={addProduct}
categoryName={category.name}
/>
</Grid>
))}
</Grid>
</Container>
</div>
) : null
)}
</div>
</div>
);
};
export default Products;
this is my app.js
const App = () => {
const [categories, setCategories] = useState([]);
const fetchProducts = async () => {
const { data: products } = await commerce.products.list({ limit: 1000 });
const { data: categoriesData } = await commerce.categories.list();
const productsPerCategory = categoriesData.reduce((acc, category) => {
return [
...acc,
{
...category,
productsData: products.filter((product)=>
product.categories.find((cat) => cat.id === category.id)),
},
];
}, []);
setCategories(productsPerCategory);
};
I have a datagrid table in which I'm getting my database data from an API call and I have written the table code in one file. I also have a search functionality where you can search for a particular record inside the table, but this search code is in another file. I am having difficulty of passing my state variable containing the search parameter from my search file to the table file. I have separated all my components in different pages since it'd be easier to structure them using a grid in my App.js. How do I get my search query to my table file next?
My search code:
export default function SearchInput() {
const [searchTerm, setSearchTerm] = React.useState('');
return (
<Grid item xs={3}>
<Box mt={1.6}
component="form"
sx={{
'& > :not(style)': { m: 1, width: '20ch', backgroundColor: "white", borderRadius: 1},
}}
noValidate
autoComplete="off"
>
<TextField
placeholder="Search Customer ID"
variant="outlined"
size="small"
sx={{input: {textAlign: "left"}}}
onChange={(event) => {
setSearchTerm(event.target.value);
console.log(searchTerm);
}}
/>
</Box>
</Grid>
);
}
My table code:
export default function DataTable() {
const [pageSize, setPageSize] = React.useState(10);
const [data, setData] = React.useState([]);
useEffect(async () => {
setData(await getData());
}, [])
return (
<div style={{ width: '100%' }}>
<DataGrid
rows={data}
columns={columns}
checkboxSelection={true}
autoHeight={true}
density='compact'
rowHeight='40'
headerHeight={80}
disableColumnMenu={true}
disableSelectionOnClick={true}
sx={datagridSx}
pageSize={pageSize}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
rowsPerPageOptions={[5, 10, 15]}
pagination
/>
</div>
);
}
App.js
function App() {
return (
<div className="App">
<Container maxWidth="false" disableGutters="true">
<Grid container spacing={0}>
<ABClogo />
<HHHlogo />
</Grid>
<Grid container spacing={0}>
<LeftButtonGroup />
<SearchInput />
<RightButtonGroup />
</Grid>
<Grid container spacing={0}>
<DataTable />
<TableFooter />
</Grid>
</Container>
</div>
);
}
Here is a minimal example using createContext(), and useReducer() to lift up state and share it between components, similar to what you are after, but as jsNoob says, there are multiple options. This is one I'm comfortable with.
The concept is explained here: https://reactjs.org/docs/context.html
Essentially you can create 'global' state at any point in your component tree and using Provider / Consumer components, share that state and functionality with child components.
//Main.js
import React, { createContext, useContext, useReducer } from 'react';
const MainContext = createContext();
export const useMainContext => {
return useContext(MainContext);
}
const mainReducer = (state, action) => {
switch(action.type){
case: 'SOMETHING':{
return({...state, something: action.data});
}
default:
return state;
}
}
export const Main = () => {
const [mainState, mainDispatch] = useReducer(mainReducer, {something: false});
const stateOfMain = { mainState, mainDispatch };
return(
<MainContext.Provider value={stateOfMain}>
<MainContext.Consumer>
{() => (
<div>
<Nothing />
<Whatever />
</div>
)}
</MainContext.Consumer>
</MainContext.Provider>
)
}
Then you can have the other components use either or both of the main state and dispatch.
//Nothing.js
import {mainContext} from './Main.js'
const Nothing = () => {
const { mainState, mainDispatch } = useMainContext();
return(
<button onClick={() => {mainDispatch({type: 'SOMETHING', data: !mainState.something})}}></button>
)
}
Clicking the button in the above file, should change the display of the below file
//Whatever.js
import {mainContext} from './Main.js'
const Whatever = () => {
const { mainState } = useMainContext();
return(
<div>{mainState.something}</div>
);
}
I'm building a shopping cart component and trying to format the cart layout. I just want to see the layout of the cart so I commented out the products component which should get me the cart layout alone, so I can see what it looks like and what's going on, however I keep getting the following error: "TypeError: Cannot read property 'length' of undefined" when I try to compile it in react.
My shopping cart component code:
import React from "react";
import { Container, Typography, Button, Grid } from "#material-ui/core";
import useStyles from "./styles";
const Cart = ({ cart }) => {
const isEmpty = !cart.line_items.length;
const classes = useStyles();
const EmptyCart = () => (
<Typography variant="subtitle1">No items selected</Typography>
);
const FilledCart = () => (
<>
<Grid container spacing={3}>
{cart.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<div>{item.name}</div>
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant="h4">
Subtotal: {cart.subtotal.formatted_with_symbol}
</Typography>
<div>
<Button
className={classes.emptyButton}
size="large"
type="button"
variant="contained"
color="secondary"
>
Empty Cart
</Button>
<Button
className={classes.checkoutButton}
size="large"
type="button"
variant="contained"
color="primary"
>
Checkout
</Button>
</div>
</div>
</>
);
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} variant="h3">
Your Shopping Cart
</Typography>
{isEmpty ? <EmptyCart /> : <FilledCart />}
</Container>
);
};
export default Cart;
The error is specifically on the line:
const isEmpty = !cart.line_items.length;
Also my App JS code:
mport React, { useState, useEffect } from "react";
import { Products, Navbar, Cart } from "./components";
import { commerce } from "./lib/commerce";
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
};
const fetchCart = async () => {
setCart(await commerce.cart.retrieve());
};
const handleAddToCart = async (productId, quantity) => {
const item = await commerce.cart.add(productId, quantity);
setCart(item.cart);
};
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
console.log(cart);
return (
<div>
<Navbar totalItems={cart.total_items} />
{/*<Products products={products} onAddToCart={handleAddToCart} /> */}
<Cart cart={cart} />
</div>
);
};
export default App;
Any help is much appreciated.
Issue
There is no cart.line_items on the initial render since initial state is an empty object ({}).
const [cart, setCart] = useState({});
Solution
Provide valid initial state for the initial render so there's a truthy, defined cart.line_items object from which to have a length property, i.e. so !cart.line_items.length; can resolve to a value and not throw an error.
const [cart, setCart] = useState({ line_items: [] });
I have two components that I'm rendering based on the condition of a state, but I'm running into a problem where the wrong component is displayed a split second before the right component is displayed.
Fetching data async:
const [test, setTest] = useState();
const [loading, setLoading] = useState();
const [error, setError] = useState();
const fetchData = async () => {
console.log("running");
setLoading(true);
setError(false);
try {
const result = await axios(
"https://jsonplaceholder.typicode.com/posts/props.selectedId" // Is dynamic and changes on user click
);
setTest(result.data);
} catch (error) {
if (error.response.status == 404) {
setError(error);
setTest(null);
}
}
setLoading(false);
};
Rendering:
return (
<div className={classes.root}>
{!loading && !error && test? (
<div>
<Card className={classes.card}>
<CardContent>
<Title>Adattributes</Title>
<Typography variant="h6" component="h1">
name
</Typography>
<Grid container spacing={3}>
<Grid item xs={3}>
<Typography variant="subtitle2" component="h1">
address
</Typography>
{test.title}
</Grid>
</Grid>
</CardContent>
<CardActions>
<Component1
value={test}
setTest={setTest}
/>
</CardActions>
</Card>
</div>
) : (
<Component2 setTest={setTest} />
)}
</div>
);
});
Am I doing something wrong with the conditional rendering? Or do it have something to do with fetching async?
The initial state of your test state is falsy. This, plus operator precedence can lead to wrong errors. See http://www-lia.deis.unibo.it/materiale/JS/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence.html.
Probably what you want to so is:
(!loading && !error && test) ? ... : ...
In the first line you use useState hook without initial state, so your state become undefined (remember that undefined is falsy value).
const [test, setTest] = useState();
Either you should set initial state for test, or change your condition for rendering component.