So I've created this array of objects and I would like to add some of them to favorites page. I thought I'd create onClick boolean change to each element and once value true object would be added favorites page. First of all, I am not sure if that's the right way to do it and secondly, now I am struggling with the fact that value of boolean in each element in the list is changing with that click instead of desired one. Probably I bit more than I could chew hence I am asking for some guidance.
MeetupList file
import { useState } from 'react'
import classes from './MeetupList.module.css'
import { IState as Props } from '../../pages/AllMeetups'
import Card from '../ui/Card'
interface IProps {
meetups: Props['meetupsy']
}
const MeetupList: React.FC<IProps> = ({meetups}) => {
const [toggle, setToggle] = useState(false)
const toggler = () => {
toggle ? setToggle(false): setToggle(true)
}
const renderList = ():JSX.Element[] => {
return meetups.map((meetup) => {
return(
<Card key={meetup.id}>
<li className={classes.list}>
<div className={classes.image}>
<img src={meetup.image} alt={meetup.title} />
</div>
<div className={classes.listheader}>
<h3>{meetup.title}</h3>
<address>{meetup.address}</address>
<p>{meetup.description}</p>
</div>
<div className={classes.actions}>
<button onClick={toggler}>{toggle ? <span>To Favorite</span>:<span>Not Favorite</span>}</button>
</div>
</li>
</Card>
)
})
}
return (
<ul className={classes.render}>
{renderList()}
</ul>
)
}
export default MeetupList;
All Meetups Page
import React, {useState, useEffect} from 'react'
import MeetupList from '../components/meetups/MeetupList'
import NewMeetupForm from '../components/meetups/NewMeetupForm'
import Popup from '../components/ui/Popup'
//import classes from './AllMeetups.module.css'
import './AllMeetups.css'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faCalendarPlus } from '#fortawesome/free-regular-svg-icons'
export interface IState {
meetupsy: {
id: number
image: string
title: string
address: string
description: string
}[]
}
const AllMeetups = () => {
const [meetups, setMeetups] = useState<IState['meetupsy']>([
{
id: 123,
image: 'https://i.imgur.com/i4WpOUM.jpg',
title: 'Car Meetup',
address: 'Baker Street 14',
description: 'EUDM Meetup'
},
{
id: 1233,
image: 'https://cdn.pixabay.com/photo/2017/12/09/08/18/pizza-3007395__480.jpg',
title: 'Cooking Show',
address: 'Downtown 14',
description: 'Pizza Cooking Show'
}
])
useEffect(() => {
const data = window.localStorage.getItem('MEETUP_LIST');
if (data !== null) setMeetups(JSON.parse(data))
},[])
useEffect(() => {
window.localStorage.setItem('MEETUP_LIST', JSON.stringify(meetups))
}, [meetups])
const [buttonPopup, setButtonPopup] = useState(false);
return (
<section>
<h1 className='oke'>All Current Meetups</h1>
<MeetupList meetups={meetups} />
<button className='open' onClick={() => setButtonPopup(true)}><FontAwesomeIcon icon={faCalendarPlus} /></button>
<Popup trigger={buttonPopup} setTrigger={setButtonPopup}>
<NewMeetupForm meetups={meetups} setMeetups={setMeetups} />
</Popup>
</section>
)
}
export default AllMeetups
Related
I have a context api in my application. At the moment I only keep the categories and i have 1 initial category. I print the categories in App.js with the map function. I have defined a function called addCategoryHandler in context api and I want to update my state by calling it in AddCategory component. But when I click the button state.categories returns undefined. I guess I'm missing something about lifecyle but I couldn't quite understand. Can you help?
Here is the codesandbox link: https://codesandbox.io/s/hungry-zeh-kwolr8
App.js
import AvailableProducts from './components/AvailableProducts.js';
import Category from './components/Category.js';
import Review from './components/Review.js';
import AddCategory from './components/AddCategory';
import { useAppContext } from './context/appContext';
import './assets/styles/App.scss';
export default function App() {
const { categories } = useAppContext();
return (
<main>
<h1>Initial Screen</h1>
<div className='container'>
<div className='container__left-side'>
<AvailableProducts />
<Review />
</div>
<div className='container__right-side'>
{categories.map((category) => (
<Category
key={category.id}
id={category.id}
title={category.title}
/>
))}
<AddCategory />
</div>
</div>
</main>
);
}
Context Api
import React, { useContext, useState } from "react";
import generateCategoryTitle from "../utils/GenerateCategoryTitle";
const AppContext = React.createContext();
const initialState = {
categories: [{ id: 1, title: "Category 1", products: [] }]
};
const AppProvider = ({ children }) => {
const [state, setState] = useState(initialState);
console.log(state);
const addCategoryHandler = () => {
// const { newId, newCategoryTitle } = generateCategoryTitle(state.categories);
// // const newCategory = [{ id: newId, title: newCategoryTitle, products: [] }];
// setState((prevState) => {
// console.log([...prevState.categories,...newCategory]);
// });
console.log("add category clicked");
};
return (
<AppContext.Provider value={{ ...state, addCategoryHandler }}>
{children}
</AppContext.Provider>
);
};
const useAppContext = () => useContext(AppContext);
export { AppProvider, useAppContext };
Add Category Component
import "../assets/styles/AddCategory.scss";
import { useAppContext } from "../context/appContext";
const AddCategory = () => {
const { addCategoryHandler } = useAppContext();
return (
<button
className="add-categoryn-btn"
type="button"
onClick={addCategoryHandler}
>
Add Category
</button>
);
};
export default AddCategory;
I am trying to understand the useContext hook a little bit better. I am playing around with this codesandbox which adds items from the left side component to the right side.
Now, I would like to count the number of times they are added to the list (how many times their resp. add to cart button has been clicked on) and display that next to them.
Do I need to create a completely new context and state hook to have access to the resp. items' values all over?
I tried implementing a counter in the addToItemList(item) function without success. I also tried to implement a counter function outside of it and then implement a global handleClick function in vain as well.
Thanks in advance for any tips and hints!
The code for the list of items:
import { useContext } from "react";
import { data, Item } from "./data";
import { ItemListContext } from "./ItemList.context";
const items: Item[] = data;
export default function ItemList() {
const { itemList, setItemList } = useContext(ItemListContext); // get and set list for context
const addItemToItemList = (item: Item) => {
//you are using the itemList to see if item is already in the itemList
if (!itemList.includes(item)) setItemList((prev) => [...prev, item]);
};
return (
<div className="itemlist">
{items.map((item, index) => (
<div style={{ marginBottom: 15 }} key={index}>
<div style={{ fontWeight: 800 }}>{item.name}</div>
<div>{item.description}</div>
<button onClick={() => addItemToItemList(item)}>
Add to sidebar
</button>
</div>
))}
</div>
);
}
And the code for the container which contains the added items:
import { useContext } from "react";
import { ItemListContext } from "./ItemList.context";
import { Link, useHistory } from "react-router-dom";
export default function ItemContainer() {
const history = useHistory(); //useHistory hooks doc: https://reactrouter.com/web/api/Hooks
const { itemList } = useContext(ItemListContext);
const onNavigate = () => {
history.push("/selectedItemList");
};
return (
<div style={{ flexGrow: 4 }}>
<h1 style={{ textAlign: "center" }}>List of items</h1>
<p>Number of items: {itemList.length}</p>
{itemList.length > 0 && (
<ul>
{itemList.map((item, i) => (
<li key={i}>{item.name}</li>
))}
</ul>
)}
<div>
<button>
{" "}
<Link to="/selectedItemList"> selected list details</Link>
</button>
<div>
<button type="button" onClick={onNavigate}>
selected list with useHistory hook
</button>
</div>
</div>
</div>
);
}
ItemList.context.tsx
import React, {
createContext,
Dispatch,
FunctionComponent,
useState
} from "react";
import { Item } from "./data";
type ItemListContextType = {
itemList: Item[]; // type of your items thata I declare in data.ts
setItemList: Dispatch<React.SetStateAction<Item[]>>; //React setState type
};
export const ItemListContext = createContext<ItemListContextType>(
{} as ItemListContextType
);
export const ItemListContextProvider: FunctionComponent = ({ children }) => {
const [itemList, setItemList] = useState<Item[]>([]);
return (
<ItemListContext.Provider
value={{ itemList: itemList, setItemList: setItemList }}
>
{children}
</ItemListContext.Provider>
);
};
Add wrapper methods to the context.
This assumes names are unique.
type ItemListContextType = {
itemList: Item[]; // type of your items thata I declare in data.ts
addItem: (item: Item) => void;
removeItem: (item: Item) => void;
counter: { [itemName: string]: number };
};
export const ItemListContext = createContext<ItemListContextType>(
{} as ItemListContextType
);
export const ItemListContextProvider: FunctionComponent = ({ children }) => {
const [itemList, setItemList] = useState<Item[]>([]);
const [counter, setCounter] = useState<{ [itemName: string]: number }>({});
const addItem = useCallback((item) => {
setCounter((prev) => ({
...prev,
[item.name]: (prev[item.name] || 0) + 1,
}));
setItemList((prev) => [...prev, item]);
}, []);
const removeItem = useCallback((itemName) =>
setItemList((prev) => prev.filter((it) => it.name !== itemName)), []
);
return (
<ItemListContext.Provider
value={{ itemList: itemList, addItem, removeItem, counter }}
>
{children}
</ItemListContext.Provider>
);
};
I have a demo here
I have a simple list of products and a cart that I would like to add the products to.
The Products and Cart are separate components in the index file.
I have the function to add the products to the cart in the Products components but how do I pass this to the Cart component that is outside the Products component.
import React, { useState } from "react";
import { render } from "react-dom";
import Cart from "./Cart";
import Products from "./Products";
import "./style.css";
const App = () => {
return (
<div>
<Products />
<Cart />
</div>
);
};
render(<App />, document.getElementById("root"));
https://stackblitz.com/edit/react-ts-txpsds
// index.tsx
import React from "react";
import { render } from "react-dom";
import Cart from "./Cart";
import { CartProvider } from "./context";
import Products from "./Products";
import "./style.css";
const App = () => {
return (
<CartProvider>
<div>
<Products />
<Cart />
</div>
</CartProvider>
);
};
render(<App />, document.getElementById("root"));
// Products.tsx
import React, { createContext, useCallback, useContext, useState } from "react";
import { AddCartContext } from "./context";
import { IProduct } from "./interface";
const Products = () => {
const addItems = useContext(AddCartContext);
const items = [
{
id: 1,
name: "Product One",
price: 20
},
{
id: 2,
name: "Product Two",
price: 56
},
{
id: 3,
name: "Product Three",
price: 13
}
];
const handleClick = (
e: React.MouseEvent<HTMLInputElement, MouseEvent>,
item: IProduct
) => {
e.preventDefault();
addItems(item);
};
const listItems = items.map(item => (
<div key={item.id}>
{`${item.name}: £${item.price}`}
<input type="submit" value="+" onClick={e => handleClick(e, item)} />
</div>
));
return (
<div>
<div>
<h2>Products</h2>
{listItems}
</div>
</div>
);
};
export default Products;
const Cart = () => {
const items = useContext(CartContext);
const cartItems = items.map((item, index) => (
<div key={index}>{`${item.name}: £${item.price}`}</div>
));
return (
<div>
<h2>Cart</h2>
{cartItems}
</div>
);
};
// context.tsx
import React, { createContext, useCallback, useRef, useState } from "react";
export const CartContext = createContext([]);
export const AddCartContext = createContext(item => {});
export function CartProvider(props) {
const [items, setItems] = useState([]);
const itemsRef = useRef(items);
itemsRef.current = items;
return (
<AddCartContext.Provider
value={useCallback(item => {
setItems([...itemsRef.current, item]);
}, [])}
>
<CartContext.Provider value={items}>
{props.children}
</CartContext.Provider>
</AddCartContext.Provider>
);
}
There are 2 work-arounds for your problem.
You can make the Cart component as the child component of Products through which you can pass the addToCart() as Props to Cart. [but it is not meaningful]
You can bring the state from Product Component to App i.e make the App as a stateful component and for products and Cart, make them as statelesss. Pass the data and methods as props.
For the second option, check the link.
If you want to share a property or function between multiple components you need to put that property or function in closest parent of those components so you can pass them as props.
In your case try to add your function to your App Component and then pass the function to both Products and Cart Components
Take a look at the react docs for Lifting state up.
Move your cart state up into the closest common ancestor - App.
From App, pass cart and setCart as props into both Products and Cart as needed.
import React, { useState, Dispatch, SetStateAction } from "react";
import { render } from "react-dom";
interface IProduct {
id: number;
name: string;
price: number;
}
const App = () => {
const [cart, setCart] = useState<IProduct[]>([]);
return (
<div>
<Products cart={cart} setCart={setCart} />
<Cart cart={cart} />
</div>
);
};
function Cart({ cart = [] }: { cart: IProduct[] }) {
return (
<div>
<h2>Cart</h2>
{cart.map(item => (
<div>{`${item.name}: £${item.price}`}</div>
))}
</div>
);
}
function Products({
cart,
setCart
}: {
cart: IProduct[];
setCart: Dispatch<SetStateAction<IProduct[]>>;
}) {
const items: IProduct[] = [{id: 1,name: "Product One",price: 20},{id: 2,name: "Product Two",price: 56},{id: 3,name: "Product Three",price: 13}];
const handleClick = (
e: React.MouseEvent<HTMLInputElement, MouseEvent>,
item: IProduct
) => {
e.preventDefault();
setCart([...cart, item]);
};
return (
<div>
<div>
<h2>Products</h2>
{items.map(item => (
<div>
{`${item.name}: £${item.price}`}
<input
type="submit"
value="+"
onClick={e => setCart([...cart, item])}
/>
</div>
))}
</div>
</div>
);
}
render(<App />, document.getElementById("root"));
Stackblitz
Keep a common items variable and a function addItems in App.tsx and you need to pass this function as prop to Product component which when adds a product will call this same function in App.tsx file and update the items list.And this items list can be passed to the Cart component.
Here check this live demo:https://stackblitz.com/edit/react-ts-qq5cea?file=index.tsx
I improved your code a little, the main thing that needed to be done was to move the state to a higher level
https://stackblitz.com/edit/react-ts-iicy7v?file=Shop.tsx
const App = () => {
return (
<div>
<Shop Products={Products} Cart={Cart} />
</div>
);
};
move logic to Shop:
const Shop = ({ Products, Cart }) => {
const [cart, setCart] = useState([]);
const addToCart = (item: IProduct) => {
setCart([...cart, item]);
};
const removeFromCart = (item: IProduct) => {
const itemWillBeRemoved = cart.find(e => e.id === item.id);
const index = cart.indexOf(itemWillBeRemoved);
const newCart = [...cart];
newCart.splice(index, 1);
setCart(newCart);
};
const items = [
{
id: 1,
name: "Product One",
price: 20
},
{
id: 2,
name: "Product Two",
price: 56
},
{
id: 3,
name: "Product Three",
price: 13
}
];
return (
<div>
<Products items={items} addToCart={addToCart} />
<Cart items={cart} removeFromCart={removeFromCart} />
</div>
);
};
But the best way - use State Management
Redux
There is component that displays a list of users.
It displays 10 users each.
Once I get to the bottom of the list, i want to add the next 10 items to the list.
I want it to feel like a messenger.
How can I determine the bottom of the list?
import React, { FunctionComponent } from 'react';
import { User } from 'components/molecules/User';
import { IUsers } from 'domain/room';
type Props = {
users: IUser;
onClickUser: (id: number) => void;
};
export const UserList: FunctionComponent<Props> = ({
users,
onClickUser,
}) => {
return (
<div style={{ overflow: 'auto' }}>
{users.map((user) => (
<div key={user.id} onClick={() => onClickUser(user.id)}>
<User
name={user.name}
img={user.pictureUrl}
status={user.status}
/>
</div>
))}
);
}
import { Label } from 'components/atoms/Label';
import { RoundedIcon } from 'components/atoms/RoundedIcon';
import React, { FunctionComponent } from 'react';
type Props = {
name: string;
img: string;
status: string;
};
export const User: FunctionComponent<Props> = ({
name,
img,
status,
}) => {
return (
<div>
<RoundedIcon size={65} url={img} />
<Label weight={700} size={14}>
{name}
</Label>
<Label size={12} height={18}>
{status}
</Label>
</div>
);
};
I am developing a front end application using react hooks where I need to upload one/multiple images.
Then i need to implement two features on the uploaded images (shown as bootstrap grid)
1.Drag/click to select one/multiple images (I am using react-selecto library for this)
2.Drag and change the order of the selected images (I and using react-dnd for this)
The problem here is:
1.When I use react-dnd alone it works absolutely fine.
2.When I use both react-selecto and react-dnd, then react-dnd no more works. Only react-selecto works.
To get a more clearer picture, when both the libraries are used together, user can select one/more images by clicking/dragging but after that when user tries to change the order by dragging selected images,nothing happens (no mouse response) and images cannot be moved from its position.
Thanks in advance. Need help.
The code is here:
App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import { DndProvider } from "react-dnd";
import {HTML5Backend} from "react-dnd-html5-backend";
import UploadForm from "./upload";
import Images from "./images";
const App = () => {
const [data, setData] = useState([]);
const addImages = (images, description) => {
let t = {
images: images,
description: description,
};
setData((data) => data.concat(t));
};
useEffect(() => {
console.log("useState", data);
}, [data]);
return (
<div>
<UploadForm onSubmit={addImages} />
<DndProvider backend={HTML5Backend}>
<Images imgData={data}/>
</DndProvider>
</div>
);
};
export default App;
images.js
import React, { useEffect, useState, useRef } from "react";
import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
import Image from "./image";
import update from "immutability-helper";
import Selecto from "react-selecto";
const Images = ({ imgData }) => {
const [images, setImages] = useState([]);
useEffect(() => {
let temp = [];
imgData.forEach((item) => {
item.images.forEach((image) => {
let t = {
key: image.text,
photo: image,
description: item.description,
};
temp.push(t);
});
});
setImages(temp);
}, [imgData]);
useEffect(() => {
console.log(images);
}, [images]);
const moveImage = (dragIndex, hoverIndex) => {
const draggedImage = images[dragIndex];
setImages(
update(images, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, draggedImage],
],
})
);
};
const imgList = images.map((img, index) => (
<Image image={img} moveImage={moveImage} index={index}/>
));
return (
<div className="container p-5 elements selecto-area">
<Selecto
dragContainer={".elements"}
selectableTargets={[".imageCube"]}
hitRate={100}
selectByClick={true}
selectFromInside={false}
toggleContinueSelect={["shift"]}
onSelect={(e) => {
e.added.forEach((el) => {
el.classList.add("selected");
console.log(el);
});
e.removed.forEach((el) => {
el.classList.remove("selected");
});
}}
></Selecto>
<div className="row">{imgList}</div>
</div>
);
};
export default Images;
image.js
import React, { useRef } from "react";
import { useDrag, useDrop } from "react-dnd";
import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
const type = "Image";
const Image = ( { image, moveImage, index } ) => {
const ref = useRef(null);
const [{ isDragging }, drag] = useDrag({
item: { type, id: image.id, index },
collect: monitor => ({
isDragging: monitor.isDragging()
})
});
const [, drop] = useDrop({
accept: type,
hover(item) {
if (!ref.current) {
return;
}
const dragIndex = item.index;
const hoverIndex = index;
if (dragIndex === hoverIndex) {
return;
}
moveImage(dragIndex, hoverIndex);
item.index = hoverIndex;
}
});
drag(drop(ref));
console.log(ref);
return(
<div className="col-sm-3 mt-3">
<div className="hvrbox imageCube" ref={ref} style={{ opacity: isDragging ? 0.7 : 1 }}>
<div className="card">
<div className="card-header">
<img src={image.photo.preview} alt="" width="210" height="150"/>
<div className="mt-1 text-center">
<strong>{image.photo.text}</strong>
</div>
</div>
</div>
<div className="hvrbox-layer_top hvrbox-layer_scale">
<div className="hvrbox-text">
{image.description ? image.description : image.photo.text}
</div>
</div>
</div>
</div>
);
};
export default Image;
Thanks in advance. Need help.