React js useState throws error when state is passed as prop - javascript

I am writing an ecommerce webshop using React js and Commerce.js
I am very confused as I am not able to identify the precise problem. But here's how it's happening:
My App.js:
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 (
<>
<Navbar totalItems={cart.total_items} />
{/* <Products products={products} onAddToCart={handleAddToCart} /> */}
{/* <Cart cartItems={cart} /> */}
</>
);
Now when I uncomment the <Cart cartItems={cart} />, React js Throws an error
This is the error in from the console
Uncaught Error: Objects are not valid as a React child (found: object with keys {raw, formatted, formatted_with_symbol, formatted_with_code}). If you meant to render a collection of children, use an array instead.
Interestingly enough, the single item is being passed on through the Cart.js but not without the error.
Here's Cart.js for Reference
const Cart = ({ cartItems }) => {
const classes = useStyles();
const EmptyCart = () => {
return (
<Typography variant="subtitle1">
You have no items in your cart. Start adding some :)
</Typography>
);
};
const FilledCart = () => {
return (
<>
<Grid container spacing={3}>
{cartItems.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<CartItem items={item} />
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant="h4">
Subtotal: {cartItems.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>
</>
);
};
if (!cartItems.line_items)
return <Typography variant="h4">Loading...</Typography>;
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} variant="h3">
Your Shopping Cart
</Typography>
{!cartItems.line_items.length ? <EmptyCart /> : <FilledCart />}
</Container>
);
};
Update:
Here's what Cart object looks like

Related

Multiple buttons triggering the same modal component

I have an videos array, which in turn has objects of type Video (typing below).
I need that when clicking on the button corresponding to a specific video, I can open only one modal with the information of the clicked video.
interface VideosInfo {
id: number;
title: string;
url: string;
quiz: boolean;
}
interface PagePros {
videos: VideosInfo[]
}
Below is the component that renders the array of videos through a map, notice that inside the map, I have an onClick function that calls the modal.
import { VideoModal } from '../index';
import { useVideos } from '../../../../hooks/Videos';
export const Videos: React.FC<VideoProps> = ({ module_id }) => {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const { getVideos, videos, loadingVideos } = useVideos();
const handleCloseModal = () => {
setModalOpen(false);
};
const VideosData = () => {
if (videos.length) {
return (
<List dense>
{videos?.map(video => (
<div key={video.id}>
<ListItem onClick={() => setModalOpen(true)} button>
<ListItemText primary={video.title} />
</ListItem>
<Divider />
<VideoModal
open={modalOpen}
handleClose={() => handleCloseModal()}
video={video}
video_id={video.id}
/>
</div>
))}
</List>
);
}
if (!videos.length && !loadingVideos) {
return (
<Typography variant="body1">
Não existem vídeos cadastrados neste módulo.
</Typography>
);
}
return <LoadingScreen text="Carregando vídeos..." />;
};
useEffect(() => {
getVideos(module_id);
}, [module_id, getVideos]);
return (
<Grid container spacing={2}>
<Grid item xs={12} md={12}>
<VideosData />
</Grid>
<Grid item xs={12} md={12}>
<Button variant="text" color="primary">
Novo Vídeo
</Button>
</Grid>
</Grid>
);
};
And below the VideoModal component:
export const VideoModal: React.FC<ModalProps> = ({
video,
open,
handleClose,
video_id,
}) => {
console.log('videos modal', video);
return (
<Dialog
open={open}
aria-labelledby="form-dialog-title"
onClose={handleClose}
>
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<h2>test</h2>
</DialogContent>
</Dialog>
);
};
I understand that the modal uses the "open" property to define whether it is open or not, but when I click the button and perform the setModalOpen, it renders a modal for each object in the array. I don't understand how I could assemble this correctly.
I solved it as follows, created a state called videoToModal of type VideosInfo and a function called handleModalOpen, passed the video parameter to the function, and in the function stored this video in the videoToModal state.
I instantiated the VideoModal component outside the map (obviously should have done this before) and passed the state to the VideoModal component's video parameter.
Below is the complete code for the component.
import React, { useEffect, useState } from 'react';
import {
Button,
Divider,
Grid,
IconButton,
List,
ListItem,
ListItemSecondaryAction,
ListItemText,
Tooltip,
Typography,
} from '#material-ui/core';
import { Delete, QuestionAnswer } from '#material-ui/icons';
import { useVideos } from '../../../../hooks/Videos';
import { useStyles } from './styles';
import { LoadingScreen } from '../../../../components/CustomizedComponents';
import { VideoModal } from '../index';
import { VideosInfo } from '../../../../hooks/Videos/types';
import { VideoProps } from './types';
export const Videos: React.FC<VideoProps> = ({ module_id }) => {
const [openModal, setOpenModal] = useState<boolean>(false);
const [videoToModal, setVideoToModal] = useState<VideosInfo>();
const classes = useStyles();
const { getVideos, videos, loadingVideos } = useVideos();
const handleCloseModal = () => {
setOpenModal(false);
};
const handleOpenModal = (video: VideosInfo) => {
setVideoToModal(video);
setOpenModal(true);
};
const VideosData = () => {
if (videos.length) {
return (
<List dense>
{videos?.map(video => (
<div key={video.id}>
<ListItem
className={classes.listItem}
onClick={() => handleOpenModal(video)}
button
>
<ListItemText
primary={video.title}
className={classes.listItemText}
/>
<ListItemSecondaryAction>
<Tooltip
placement="top"
title={
video.Quizzes?.length
? 'Clique para ver as perguntas'
: 'Clique para iniciar o cadastro de perguntas'
}
>
<IconButton edge="end" aria-label="delete">
<QuestionAnswer
color={video.Quizzes?.length ? 'primary' : 'action'}
/>
</IconButton>
</Tooltip>
<Tooltip placement="top" title="Deletar Vídeo">
<IconButton edge="end" aria-label="delete">
<Delete color="secondary" />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
<Divider />
</div>
))}
<VideoModal
open={openModal}
handleClose={() => handleCloseModal()}
video={videoToModal}
/>
</List>
);
}
if (!videos.length && !loadingVideos) {
return (
<Typography variant="body1">
Não existem vídeos cadastrados neste módulo.
</Typography>
);
}
return <LoadingScreen text="Carregando vídeos..." />;
};
useEffect(() => {
getVideos(module_id);
}, [module_id, getVideos]);
return (
<Grid container spacing={2} className={classes.container}>
<Grid item xs={12} md={12}>
<VideosData />
</Grid>
<Grid item xs={12} md={12}>
<Button variant="text" color="primary">
Novo Vídeo
</Button>
</Grid>
</Grid>
);
};
Instead of using
<div key={video.id}>
can you use,
<List dense>
{videos?.map((video,i) => (
<div key={i}>
<ListItem onClick={() => setModalOpen(true)} button>
<ListItemText primary={video.title} />
</ListItem>
<Divider />
<VideoModal
open={modalOpen}
handleClose={() => handleCloseModal()}
video={video}
video_id={video.id}
/>
</div>
))}
</List>

How to do loop in json file in react

I need basic help.
I have an array. When i click some link on the screen, i want to see just their page, i mean him/her name, not all of them.
First, user page has all of the users. If i click some of them, i want to go her/his page. How can i do this?
Users has userid, so it may help you.
ProfilePage codes: (it gives all of the users with item.name)
const ProfilePage = () => {
const { posts, users } = useContext(UserContext);
return (
<>
{users.map((item, index) => {
return (
<>
<h1>{item.name}</h1>
</>
);
})}
</>
);
};
Users page codes:
return (
<>
{users.map((item, index) => {
return (
<>
<a href={`/profile/${item.username}`}>
<List className={classes.root}>
<ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Remy Sharp" />
</ListItemAvatar>
<ListItemText
primary={item.email}
secondary={
<React.Fragment>
<Typography
component="span"
variant="body2"
className={classes.inline}
color="textPrimary"
>
{item.name}
</Typography>
{<br />}
{item.username}
</React.Fragment>
}
/>
</ListItem>
<Divider variant="inset" component="li" />
</List>
</a>
</>
);
})}
</>
);
Users page image:
Profile page image: (first users page. It should show just 1 name not all of them, i know i used map its wrong)
const ProfilePage = () => {
const { posts, users } = useContext(UserContext);
const { name } = useParams();//Your dynamic route name. Example: <Route path="/profile/:name">
const filteredUsers = users.filter(item => item.name === name);//Find all users with name Bret
//Ideally, you need to make a router with a dynamic ID, then there will be a unique user. Example: <Route path="/profile/:id">
//const { id } = useParams();
//const filteredUsers = users.filter(item => item.id === id);
return (
<>
{filteredUsers.map((item, index) => {
return (
<>
<h1>{item.name}</h1>
</>
);
})}
</>
);
};

dispatch is not updating reducer

I'm using useReducer and useContext as a flux store and i'm boggled on why i'm unable to update the stores state. Here is a sample of the code I'm working with.
reducer and initial state
export const localState = {
modal_open: false,
date: null
}
export const reducer = (state , action) => {
switch (action.type) {
case "OPEN_MODAL":
return {
...state,
modal_open: true
}
case "CLOSE_MODAL":
return {
...state,
modal_open: false
}
case "SET_DATE":
return {
...state,
date: action.payload
}
default:
return state;
}
};
context constructor and component wrapping
import React from "react";
const LocalWebSiteContext= React.createContext();
const WebsiteDetails = () => {
const [state, dispatch] = React.useReducer(reducer, localState)
const [averPositions, setAverPositions] = React.useState()
const classes = useStyles();
let match = useRouteMatch("/app/websites/:id/:slug");
let { id } = useParams();
const context = useContext(UserContext);
const history = useHistory();
//testing
const { localContext } = context
const websiteData = localContext.websites.find((el, idx) => {
if(el.website_id === id){
return el;
}
});
const userAndWebsiteData = {
...websiteData,
...localContext.user
}
if(!userAndWebsiteData){
console.log('Not a valid website');
history.push('/app/websites/');
// return;
}
return (
<>
<localWebSiteContext.Provider value={{state, dispatch}}>
{ userAndWebsiteData &&
<Page className={classes.root} title="Analytics Dashboard">
{/* { JSON.stringify(userAndWebsiteData) }
{ id } */}
<Header websiteData={userAndWebsiteData} />
<Grid className={classes.container} container spacing={3}>
<Grid item xs={12}>
<Overview websiteData={userAndWebsiteData} />
</Grid>
<Grid item lg={8} xl={9} xs={12}>
<CustomModal />
<FinancialStats websiteData={userAndWebsiteData}/>
</Grid>
<Grid item lg={4} xl={3} xs={12}>
{/* {
JSON.stringify(userAndWebsiteData.positionRangesData)}
*/}
<Top5To30 positionData={userAndWebsiteData.positionRangesData
? userAndWebsiteData.positionRangesData : {} } />
range bar chart
<EarningsSegmentation />
</Grid>
<Grid item lg={12} xs={12}>
<KeywordsList />
</Grid>
<Grid item lg={4} xs={12}>
<CustomerActivity />
</Grid>
</Grid>
</Page>
}
</localWebSiteContext.Provider>
</>
);
};
Component where i'm dispatching the payload to change date
const FinancialStats = props => {
const {state, dispatch} = React.useContext(localWebSiteContext)
const { websiteData, className, handleAverData, ...rest } = props;
const [dateId, setDateId] = React.useState(null)
const [disabled, setDisabled] = useState([]);
const handleModalOpen = () => {
const averPositions = websiteData.dailyAveragePositions;
if (averPositions){
const dateNum = parseInt(averPositions[0].d)
dispatch({type: 'OPEN_MODAL', payload: dateNum})
}
}
return (
<span
key={dataKey}
className="legend-item"
style={style}
>
<Surface width={10} height={10}>
<Symbols cx={5} cy={5} type="circle" size={50} fill={color} />
{active && (
<Symbols
cx={5}
cy={5}
type="circle"
size={25}
fill={'#FFF'}
/>
)}
</Surface>
<span>{dataKey}</span>
</span>
);
})}
</div>
);
};
The expected behavior is when I dispatch({type: 'SET_DATE', payload: dateNum) it will reflect that all other components subscribed to the useContext hook. But i cannot for the life of me get it to update from null... I'm able to switch the bool values. Even when I tried to set it locally in a useState hook it's still null? Maybe has something to do with renders? Thanks in advance!!

pass multiple refs to child components

Before diving to the main problem, my use case is I am trying to handle the scroll to a desired section. I will have navigations on the left and list of form sections relative to those navigation on the right. The Navigation and Form Section are the child component. Here is how I have structured my code
Parent.js
const scrollToRef = ref => window.scrollTo(0, ref.current.offsetTop);
const Profile = () => {
const socialRef = React.useRef(null);
const smsRef = React.useRef(null);
const handleScroll = ref => {
console.log("scrollRef", ref);
scrollToRef(ref);
};
return (
<>
<Wrapper>
<Grid>
<Row>
<Col xs={12} md={3} sm={12}>
<Navigation
socialRef={socialRef}
smsRef={smsRef}
handleScroll={handleScroll}
/>
</Col>
<Col xs={12} md={9} sm={12}>
<Form
socialRef={socialRef}
smsRef={smsRef}
/>
</Col>
</Row>
</Grid>
</Wrapper>
</>
);
};
Navigation.js(child component)
I tried using forwardRef but seems like it only accepts one argument as ref though I have multiple refs.
const Navigation = React.forwardRef(({ handleScroll }, ref) => {
// it only accepts on ref argument
const items = [
{ id: 1, name: "Social connections", pointer: "social-connections", to: ref }, // socialRef
{ id: 2, name: "SMS preference", pointer: "sms", to: ref }, // smsRef
];
return (
<>
<Box>
<UL>
{items.map(item => {
return (
<LI
key={item.id}
active={item.active}
onClick={() => handleScroll(item.to)}
>
{item.name}
</LI>
);
})}
</UL>
</Box>
</>
);
});
export default Navigation;
Form.js
I do not have idea on passing multiple refs when using forwardRef so for form section I have passed the refs as simple props passing.
const Form = ({ socialRef, smsRef }) => {
return (
<>
<Formik initialValues={initialValues()}>
{({ handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<Social socialRef={socialRef} />
<SMS smsRef={smsRef} />
</form>
);
}}
</Formik>
</>
);
};
Social.js
const Social = ({ socialRef }) => {
return (
<>
<Row ref={socialRef}>
<Col xs={12} md={3}>
<Label>Social connections</Label>
</Col>
<Col xs={12} md={6}></Col>
</Row>
</>
);
};
Can anyone help me at passing multiple refs so when clicked on the particular navigation item, it should scroll me to its respective component(section).
I have added an example below. I have not tested this. This is just the idea.
import React, { createContext, useState, useContext, useRef, useEffect } from 'react'
export const RefContext = createContext({});
export const RefContextProvider = ({ children }) => {
const [refs, setRefs] = useState({});
return <RefContext.Provider value={{ refs, setRefs }}>
{children}
</RefContext.Provider>;
};
const Profile = ({ children }) => {
// ---------------- Here you can access refs set in the Navigation
const { refs } = useContext(RefContext);
console.log(refs.socialRef, refs.smsRef);
return <>
{children}
</>;
};
const Navigation = () => {
const socialRef = useRef(null);
const smsRef = useRef(null);
const { setRefs } = useContext(RefContext);
// --------------- Here you add the refs to context
useEffect(() => {
if (socialRef && smsRef) {
setRefs({ socialRef, smsRef });
}
}, [socialRef, smsRef, setRefs]);
return <>
<div ref={socialRef}></div>
<div ref={smsRef}></div>
</>
};
export const Example = () => {
return (
<RefContextProvider>
<Profile>
<Navigation />
</Profile>
</RefContextProvider>
);
};

How to store checkbox values using localStorage? (react+hooks)

Here is my Todolist component, which contains a List, and all list items with checkboxes and with material list and checkboxes. Two props are passed: todos and deleteTodo.
const TodoList = ({ todos, deleteTodo}) => {
return (
<List>
{todos.map((todo, index) => (
<ListItem key={index.toString()} dense button>
<Checkbox disableRipple/>
<ListItemText key={index} primary={todo} />
<ListItemSecondaryAction>
<IconButton
aria-label="Delete"
onClick={() => {
deleteTodo(index);
}}
>
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
);
};
I figured out how to use local storage for storing the todos as an array, but have no idea how to store the checkbox values. Can somebody explain, what would be the strategy for that?
And here is the main app:
const initialValue = () => {
const initialArray = localStorage.getItem("todos");
return JSON.parse(initialArray);
};
const [todos, setTodos] = useState(initialValue);
useEffect(() => {
const json = JSON.stringify(todos);
localStorage.setItem("todos", json);
});
return (
<div className="App">
<Typography component="h1" variant="h2">
Todos
</Typography>
<TodoForm
saveTodo={todoText => {
const trimmedText = todoText.trim();
if (trimmedText.length > 0) {
setTodos([...todos, trimmedText]);
}
}}
/>
<TodoList
todos={todos}
deleteTodo={todoIndex => {
const newTodos = todos.filter((_, index) => index !== todoIndex);
setTodos(newTodos);
}}
/>
</div>
);
};
I would appreciate any suggestions or directions, how to tackle this problem. Thx
One approach would be to use the onChange callback of the Checkbox component
e.g. <Checkbox disableRipple onChange={(e)=> onCheckboxChange(e.event.target) /> (and whatever params you need)
and pass it up to your parent component through a prop, e.g.
const TodoList = ({ todos, deleteTodo, onCheckboxChange}) => {
You can then store the value in local storage the parent component.
There may be a more elegant approach

Categories

Resources