I am trying to learn how to sort some data using react and TailwindCss, but upon trying I found a problem which I can't put my finger on, after sorting the data array it's not data.map is not re-rendering and I don't know why, can someone help me understand how to fix it, and why this happens.
import { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [data, setData] = useState([]);
// this is the sorting function
const sortData = () => {
setData(data.sort((a, b) => a.firstName.localeCompare(b.firstName)));
console.log("sort",data);
};
useEffect(() => {
axios
.get("https://dummyapi.io/data/v1/user?limit=50", {
headers: {
"app-id": "6200ff0858dcad3e84d67c55",
},
})
.then((res) => {
setData(res.data.data);
})
.catch((err) => {
console.log(err);
});
}, []);
useEffect(() => {
console.log(data);
}, [data])
return (
<div className="flex justify-center min-h-screen App bg-background min-w-screen">
<div className="container">
<div className="flex flex-col items-center gap-4 py-5">
<h1 className="text-3xl">Filter & sorting app</h1>
<div className="relative w-full">
<div className="right-0 ">
<select className="flex content-end px-3 py-2 bg-white shadow-sm rounded-xl">
<option
value="volvo"
className="text-gray-100"
selected
disabled
hidden
>
Sort by
</option>
<option value="saab" onClick={sortData}>
Ascending
</option>
<option value="opel">Descending</option>
</select>
</div>
</div>
<div className="w-full flex justify-center gap-8 flex-wrap bg-white p-8 min-h-[300px] border-2 rounded-xl">
{/* here i map on the data state but it does nor re-render after data has changed */}
{data?.length > 0 ? (
data.map((user) => (
<div
className="bg-blue-300 sm:w-[200px] sm:h-[200px] w-[150px] h-[150px] rounded-3xl p-4 flex flex-col items-center mt-8"
key={user.id}
>
<img
src={user.picture}
alt={`"user_"${user.id}`}
className="relative w-20 h-20 rounded-full -top-12"
/>
<div className="flex flex-row -mt-6 text-center sm:mt-0">
<h1 className="text-xl capitalize sm:text-3xl">
{user.title}.
<p className="text-sm sm:text-xl">
{user.firstName} {user.lastName}
</p>
</h1>
</div>
</div>
))
) : (
<div className="flex items-center justify-center">
<h1 className="text-3xl">Loading...</h1>
</div>
)}
</div>
</div>
</div>
</div>
);
}
export default App;
EDIT: if you have a better implementation of this please let me know.
This sort function does not return any array. so instead to this:
const sortData = () => {
setData(data.sort((a, b) => a.firstName.localeCompare(b.firstName)));
};
Try this
const sortData = () => {
var newArr = [...data];
newArr.sort((a, b) => a.firstName.localeCompare(b.firstName))
setData(newArr);
};
Related
it is the initial state of the redux toolkit, cartSlice and it is an object and products is an array having oneProduct as an object containing product information like image, price, description, etc.
This is cart slice
import { createSlice } from "#reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: {
products: [],
quantity: 0,
total: 0,
},
reducers: {
addProduct: (state, action) => {
state.products.push(action.payload);
state.quantity += 1;
state.total += action.payload.price * action.payload.quantity;
},
removeProduct: (state, action) => {
let index = state.products.indexOf(action.payload)
state.quantity -= action.payload
state.products.splice(index, 1)
}
},
});
export const { addProduct ,removeProduct} = cartSlice.actions;
export default cartSlice.reducer;
THIS OBJECT IS initilaState of redux. In CONSOLE OF BROWSER
Object
products
:
Array(2)
0
:
{status: 'Specific products is here', oneProduct: {…}}
1
:
{status: 'Specific products is here', oneProduct: {…}}
length
:
2
[[Prototype]]
:
Array(0)
quantity
:
2
total
:
NaN
[[Prototype]]
:
Object
The problem is I am unable to map products array console above . Below is the code for the orders page and kindly Look at map Method
import React, { useState } from 'react'
import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
const Order = () => {
const cart = useSelector((state) => state.cart)
console.log(cart)
const quantity = useSelector((state) => state.cart.quantity)
const [counter, setCounter] = useState(1);
const counterNumber = (type) => {
if (type === "inc") {
setCounter((prev) => prev + 1);
} else {
counter > 1 && setCounter((prev) => prev - 1);
}
};
return (
<>
<h1 className='font-bold text-xl text-center my-5'>CART PAGE</h1>
<div className='flex justify-around flex-wrap my-5 space-x-2'>
<div className='flex flex-col shadow-md p-2'>
{cart.products.oneProduct?.map((item)=>(
<div className="Product_info flex align-middle m-auto space-x-2 ">
<div className="product_image my-auto" key={item._id}>
<img src={item.img} alt="" className='w-16 h-16 ' />
</div>
<div className="details">
<h1 className='font-bold text-lg m-3'>{item.title}</h1>
</div>
<div className="price my-auto ">
<h1 className='font-bold text-2xl cursor-pointer'>$ {item.price}</h1>
</div>
<div className="ince_dec flex align-middle justify-center space-x-2 mr-5">
<div className="dec font-extrabold text-3xl my-auto" onClick={() => counterNumber("dec")}>-</div>
<h1 className='font-bold text-2xl my-auto'> {counter} </h1>
<div className="inc font-extrabold text-3xl my-auto cursor-pointer" onClick={() => counterNumber("inc")}>+</div>
</div>
<hr />
</div>
))}
</div>
<div className=" sideCart ml-1 text-white bg-blue-400 p-10 ">
<h1 className="text-center text-black font-extrabold">Product Quantity is: </h1>
<div className="text-white text-center font-bold my-2">{cart.products.length<1 ?"Cart is empty": quantity }</div>
<ul >
<div className="items flex align-middle items-center ">
</div>
</ul>
<div className="bton flex justify-center align-middle">
<Link to="/checkout">
<button className=" cart flex ml-4 text-white bg-black border-0 py-2 px-4 focus:outline-none lg:w-32 hover:bg-indigo-600 rounded md:w-4 md:px-6 text-sm">Checkout Now</button>
</Link>
</div>
</div>
</div>
</>
)
}
export default Order
import React from "react";
import { useParams } from "react-router-dom";
import Spinner from "../Spinner/Spinner";
import { useState } from "react";
import { useEffect } from "react";
const InventoryItemDetail = () => {
const [spinner, setSpinner] = useState(false);
const [itemDetail, setItemDetail] = useState({});
**const [newQuantity, setNewQuantity] = useState(itemDetail.quantity)**;
let { itemId } = useParams();
useEffect(() => {
setSpinner(true);
fetch(`http://localhost:5000/item/${itemId}`)
.then((res) => res.json())
.then((data) => setItemDetail(data));
setSpinner(false);
}, [itemId]);
if (spinner) {
return <Spinner />;
}
const handleRestock = (e) => {
e.preventDefault();
console.log(e.target.restock.value);
};
const handleDeleveredByOne = () => {
setNewQuantity(itemDetail.quantity - 1);
};
return (
<div className="bg-[#F0ECE3] lg:h-screen ">
<div className="w-11/12 mx-auto grid md:grid-cols-2 lg:grid-cols-3 justify-items-center items-center pt-32">
<div className="lg:w-[500px] ">
<img src={itemDetail.img} alt="" />
</div>
<div className="px-2 font-poppins mb-16 md:col-span-1 lg:col-span-2">
<div className="lg:w-3/6 border-2 border-red-500 mx-auto px-12 py-16">
<p className="text-xl py-2 font-bold">{itemDetail.name}</p>
<p className="">{itemDetail.shortDescripton}</p>
<p>
{" "}
Quantity: <span className="font-bold">{newQuantity}</span>
</p>
<p>
Price:{" "}
<span className="text-red-500 text-xl font-blod">
{" "}
${itemDetail.price}
</span>
</p>
<p className="font-light text-sm">
Supplier: {itemDetail.supplierName}
</p>
<button
onClick={handleDeleveredByOne}
className="mt-8 border-2 border-red-500 py-2 lg:px-6 w-full lg:w-fit hover:bg-red-500 hover:text-white"
>
Delivered
</button>
<form
onSubmit={handleRestock}
className="flex justify-end mt-8 lg:mt-0"
>
<div className="grid grid-cols-2 border-2 border-red-500 py-2 px-4 hover:bg-red-500 ">
<input
className="lg:w-20 text-lg order-2 outline-none "
type="number"
name="restock"
id=""
/>
<input
className="hover:text-white cursor-pointer"
type="submit"
value="Restock"
/>
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default InventoryItemDetail;
I want to display quantity that I have received from backend server and by clicking on delivered button, everytime it will reduce by one and save it to backend server. I have tried but it does not display quantity information and after clicking button it only works for first time.
How can I display quantity information and that I received from backend server and how to reduce it on every click and save it to backend server.
I am loading data from firebase, so when I refresh, it takes time to load it. And added data are not present in the cart that,s why I am not getting any product in the cart. But I did follow the same process to store user data, and it,s working fine, which means I am getting a registered profile in console. Can anyone suggest me a solution for getting data in the cart?
sending data:
import { motion } from "framer-motion";
import React, { useEffect, useRef, useState } from "react";
import { MdShoppingBasket } from "react-icons/md";
import { actionType } from "../context/reducer";
import { useGlobalState } from "../context/stateProvider";
import NotFound from "../img/NotFound.svg";
const RowContainer = ({ flag, data, scrollValue }) => {
const [{ cartItems }, dispatch] = useGlobalState();
const [items, setItems] = useState([]);
const rowContainer = useRef();
const addToCart = () => {
dispatch({
type: actionType.SET_CARTITEMS,
cartItems: items,
});
localStorage.setItem("cartItems", JSON.stringify(items));
};
useEffect(() => {
addToCart();
}, [items]);
useEffect(() => {
rowContainer.current.scrollLeft += scrollValue;
}, [scrollValue]);
return (
<div
ref={rowContainer}
className={`w-full my-12 flex items-center ${
flag
? "overflow-x-scroll scrollbar-none scroll-smooth"
: "overflow-x-hidden flex-wrap justify-center"
}`}
>
{data && data.length > 0 ? (
data.map((item) => {
const { id, price, imageURL, calories, title } = item;
return (
<div
key={id}
className="w-350 min-w-[300px] md:min-w-[380px] md:my-10 backdrop-blur-lg mx-1 my-2 lg:mx-2 "
>
<div className="w-full flex flex-row items-center justify-between bg-white rounded-lg drop-shadow-lg py-2 px-4 hover:bg-whiteAlpha min-h-[150px]">
<motion.img
whileHover={{ scale: 1.2 }}
src={imageURL}
alt="img"
className="w-30 max-h-40 -mt-8 duration-100 drop-shadow-2xl"
/>
<div className="flex flex-col items-end justify-end gap-4">
<motion.div
whileTap={{ scale: 0.75 }}
className="w-8 h-8 rounded-md bg-orange-500 hover:bg-orange-600 "
onClick={() => setItems([...new Set(cartItems), item])}
>
<MdShoppingBasket className="text-white text-2xl m-1" />
</motion.div>
<div className=" w-full">
<p className="text-gray-800 md:text-lg text-base text-right">
{title}
</p>
<p className="text-sm text-gray-500 text-right">
<span className="text-orange-600">$</span> {price}
</p>
</div>
</div>
</div>
</div>
);
})
) : (
<div className="w-full flex flex-col items-center justify-center">
<img src={NotFound} className="h-340" alt="" />
<p className="text-center my-2 text-xl text-red-500">No data found</p>
</div>
)}
</div>
);
};
export default RowContainer;
fetching data
export const fetchCart = () => {
const cartInfo =
localStorage.getItem("cartItems") !== "undefined"
? JSON.parse(localStorage.getItem("cartItems"))
: localStorage.clear();
return cartInfo ? cartInfo : [];
};
You are clearing your data for no reason
localStorage.getItem("cartItems") !== "undefined"
should be
localStorage.getItem("cartItems") !== undefined
I need to call CutomerDashboard.js file's "toggleIsTrucated" function and "isTruncated" to CustomerNotice.js files button onClick and text change places, How can I call that?
(In this customer dashboard file I'm creating a Read function to show some extent of notice text)
import React, {useState,useEffect} from 'react';
import { Input, Row, Col, Button } from 'antd';
import {fetchDashboardMetrics} from "./DashboardApi";
import {items} from "./DashboardItems";
import axios from 'axios';
import CustomerNotice from "./CustomerNotice";
function Read ({children}) {
const text = children;
const [isTruncated, setIsTrucated] = useState(true);
const result = isTruncated ? text.slice(0,90) : text;
function toggleIsTrucated(){
setIsTrucated(!isTruncated);
}
return (
<div>
{result}....
</div>
);
}
const CustomerDashboard = () => {
const [features, setFeatures] = useState(items);
const source = axios.CancelToken.source()
const [notice, setNotice] = useState(<Read>Customer Notice: Optimism Is Invaluable For The Meaningful Life. With A Firm Belief In A Positive Future You Can Throw Yourself Into The Service Of That Which Is Larger Than You Are. -Martin Seligman-</Read>);
const [noticeVisibility, setNoticeVisibility] = useState(true);
useEffect(() => {
fetchDashboardMetrics(features, setFeatures,source.token)
return (() => {
source.cancel();
})
}, []);
return (
<>
<div className='md:pl-8 sm:pl-0'>
<div className='my-5 '>
<p className='mb-8'>My Account - Dashboard Overview</p>
{noticeVisibility && <CustomerNotice notice={notice} setNoticeVisibility={setNoticeVisibility}/>}
</div>
<ul role="list" className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => (
<li key={feature.name} className="col-span-1 bg-white rounded-lg shadow divide-y divide-gray-200 relative">
<div className="w-full flex items-center justify-between p-6 space-x-6">
<div className="flex-1 truncate">
<div className="flex items-center space-x-3 justify-between">
<h3 className="text-gray-900 text-lg truncate">{feature.name}</h3>
{feature.isNew && (
<div className="absolute -top-2 -right-2 p-1 px-4 text-white text-sm bg-red-500">
New
</div>
)}
</div>
</div>
</div>
<div>
<div className={'mx-4 mt-2 mb-3 '}>
{feature.details.map((singleDetail) => {
return (
<div className={'flex justify-between text-base'}>
<span>{singleDetail.name}</span>
<span>{singleDetail.value}</span>
</div>
)
})}
</div>
</div>
</li>
))}
</ul>
</div>
</>
)
}
export default CustomerDashboard;
import React, {useState,useEffect} from 'react';
import {XIcon} from "#heroicons/react/solid";
const CustomerNotice = ({notice, setNoticeVisibility}) => {
return (
<div>
<div className="mt-8 pb-2 sm:pb-5">
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div className="p-2 rounded-lg bg-orange-600 shadow-lg sm:p-3">
<div className="flex items-center justify-between flex-wrap">
<div className="w-0 flex-1 flex items-center">
<p className="ml-3 font-medium text-white truncate">
<span className="md:inline">{notice}</span>
</p>
</div>
<div className="order-3 mt-2 flex-shrink-0 w-full sm:order-2 sm:mt-0 sm:w-auto">
<a
href="#"
className="flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-orange-600 bg-white hover:bg-orange-50"
>
<button onClick={toggleIsTrucated}>{isTruncated ? "Read More" : "Read Less"}</button>
</a>
</div>
<div className="order-2 flex-shrink-0 sm:order-3 sm:ml-2">
<button
onClick={() => setNoticeVisibility(false)}
type="button"
className="-mr-1 flex p-2 rounded-md hover:bg-orange-500 focus:outline-none focus:ring-2 focus:ring-white"
>
<span className="sr-only">Dismiss</span>
<XIcon className="h-6 w-6 text-white" aria-hidden="true"/>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default CustomerNotice;
If this is not possible please suggest me a possible way.
Instead of doing a bunch of hacks, I would recommend simplifying the structure of your components.
import { useState } from 'react'
export default function CustomerDashboard() {
// I am not sure why you want to keep notice in state,
// because in your example you did not call setNotice
const [notice, setNotice] = useState(`
Customer Notice: Optimism Is Invaluable For The Meaningful Life.
With A Firm Belief In A Positive Future You Can Throw Yourself Into The Service
Of That Which Is Larger Than You Are. -Martin Seligman
`)
const [isNoticeVisible, setIsNoticeVisible] = useState(true)
return (
<div>
<h1>My Account - Dashboard Overview</h1>
{isNoticeVisible && (
<CustomerNotice
notice={notice}
setIsNoticeVisible={setIsNoticeVisible}
/>
)}
</div>
)
}
function CustomerNotice(props) {
const { notice, setIsNoticeVisible } = props
const [isTruncated, setIsTruncated] = useState(true)
function toggleIsTruncated() {
setIsTruncated(!isTruncated)
}
return (
<div>
<Read text={notice} isTruncated={isTruncated} />
<button onClick={toggleIsTruncated}>
{isTruncated ? 'Read More' : 'Read Less'}
</button>
<button onClick={() => setIsNoticeVisible(false)}>Dismiss</button>
</div>
)
}
function Read(props) {
const { text, isTruncated } = props
const result = isTruncated ? text.slice(0, 90) : text
return <div>{result}....</div>
}
List of the things that were bad in your code.
Keeping the component instance in the state. It is hard to manage. Even your simple case proves that.
Keeping the toggleIsTruncated function inside the Read component. I think we should keep it outside and pass only 2 props to the Read component. I enable exposed only two things
const { text, isTruncated } = props
As you can see it is easy to maintain and allow us to do whatever we want.
PS. If my review and example were helpful please leave the thumbs up.
From firebase I fetch data and map this data to be shown in cards, every card has edit component and CardComponent(edit component parent) which use ref provided from useHandleOpen custom hook
Error message:
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Component where ref is in use
export default function EditCard(id) {
const { ref, isOpen, setIsOpen } = useHandleOpen(false);
return (
<div>
<GoKebabVertical
ref={ref}
className="cursor-pointer "
onClick={() => setIsOpen(true)}
/>
{isOpen && (
<div
ref={ref}
className="w-20 h-15 bg-white z-30 rounded-md absolute text-center
top-0 right-0
"
>
<p className="hover:bg-blue-300 hover:text-white cursor-pointer">
Edytuj
</p>
<p
className="bg-red-400 text-white font-semibold hover:bg-red-600 cursor-pointer"
onClick={() => {}}
>
Usuń
</p>
</div>
)}
</div>
);
}
export const Edit = memo(EditCard);
Card component which use ref tho and its Edit parent
const Card = ({ data, id }) => {
const editRef = useRef(null);
const { ref, isOpen, setIsOpen } = useHandleOpen(editRef);
return (
<div
className="bg-gradient-to-r from-blue-200 to-purple-500
w-64 h-28 rounded-md relative
"
>
<div className="relative top-0 left-0">
<p className="px-2 pt-1 font-bold text-gray-800 text-lg">
{data.subject_name}
</p>
<p className="px-2 text-xs text-gray-800">Sala 101</p>
</div>
<div
className="absolute top-0 right-0 rounded-r-md
rounded-b-none
bg-white w-6 h-7
grid place-items-center
"
>
<Edit id={id} />
</div>
<div className="absolute bottom-9 left-0 mb-2.5">
<p className="px-2 text-xs text-gray-800">{data.teacher}</p>
</div>
<div className=" flex direction-row mt-7 text-center w-full justify-end align-bottom pr-2 ">
<div className="rounded-lg w-14 h-7 mx-3 bg-purple-300">
<a
href={data.link_subject}
className="text-gray-800
"
target="_blank"
rel="noreferrer"
>
Lekcja
</a>
</div>
<div className="rounded-lg w-14 h-7 bg-gray-800 ">
<a
href={data.link_online_lesson}
target="_blank"
rel="noreferrer"
className="text-white"
>
Online
</a>
</div>
</div>
<div
className=" absolute bottom-0 left-0 rounded-l-md
bg-white w-7 h-6 grid place-items-center devide-solid"
>
{isOpen ? (
<AiOutlineUp
className="cursor-pointer"
ref={ref}
onClick={() => setIsOpen(true)}
/>
) : (
<AiOutlineDown
className="cursor-pointer"
ref={ref}
onClick={() => setIsOpen(true)}
/>
)}
</div>
{isOpen && (
<div
className="bg-gradient-to-r from-blue-200 to-purple-500 w-full text-left rounded-b-md p-4 "
ref={ref}
>
<p className="font-bold text-gray-800 text-sm ">Lekcje w:</p>
<p className="text-gray-800 text-sm">PN: 8-12</p>
</div>
)}
</div>
);
};
export const CardSubject = memo(Card);
Custom hook with ref:
export default function useHandleOpen() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(!isOpen);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside, !isOpen);
return () => {
document.removeEventListener("click", handleClickOutside, !isOpen);
};
});
return { ref, isOpen, setIsOpen };
}
Edit: Tried change it this way, but this displays warning too.
export default function useHandleOpen(ref) {
const [isOpen, setIsOpen] = useState(false);
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(!isOpen);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside, !isOpen);
return () => {
document.removeEventListener("click", handleClickOutside, !isOpen);
};
});
return { ref, isOpen, setIsOpen };
}
And use hook like this:
const editRef = useRef(null);
const { ref, isOpen, setIsOpen } = useHandleOpen(editRef);
Ok i fixed it by removing ref from places where i use onClick method for changing the state