This question already has answers here:
How do I create a GUID / UUID?
(70 answers)
Closed 12 months ago.
I am mapping an array of data with props into a component. Then onClick I pull some of that data into redux/reducer from the rendered items, trying to render the same data - but in a different spot on the page.
My problem is (I assume?) that the ID's are the same - I render data with keys's/id's that were already taken - while React wants unique ones.
I am not sure, if that's what's causing the problem - but I keep getting a warning that react wants unique key props.
(it's a shop app - on click, i want to add the chosen item to a cart with redux... )
Thoughts?
here I am building the component to render
import { useDispatch, useSelector } from 'react-redux'
import { add } from '../features/addToCart'
export const ReduxshopProps = (props) => {
const dispatch = useDispatch()
const handleAddToCart = (props) => {
dispatch(add(props));
};
return (<>
<div key={props.id} className='shopitem'>
<img src={props.url} />
<h2>{props.title}</h2>
<p className='boldprice'>${props.price}</p>
<button onClick={() => handleAddToCart(props) }
>
ADD TO CART
</button>
</div>
</>
)
}
here I am passing data into the component
import React from "react"
import { ReduxshopProps } from "./ReduxshopProps"
import shopdata from "./shopdata"
export default function ReduxShop() {
const cards = shopdata.map(props => {
return (
<ReduxshopProps
key={props.id}
title={props.title}
price={props.price}
url={props.url}
/>
)
})
return (
<div className='shopwrapper'>
<h1>TradingView Indicators</h1>
<div className='itemWrapper'>
{cards}
</div>
</div>
)
}
here's the REDUX code that pulls data from above
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
cartItems: [],
cartTotalQuantity: 0,
cartTotalAmount: 0,
}
export const addToCartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
add(state, action ) {
const itemIndex = state.cartItems.findIndex(
(item) => item.id === action.payload.id
);
if(itemIndex >= 0){
state.cartItems[itemIndex].cartQuantity += 1
} else {
const tempProduct = {...action.payload, cartQuantity: 1}
state.cartItems.push(tempProduct);
}
},
},
});
export const {add} = addToCartSlice.actions;
export default addToCartSlice.reducer;
and here I'm trying to render the data when someone clicks on a button.. onClick it acts as all components have the same ID - also I'm getting the key prop error from here, below
import React from 'react'
import { useSelector } from 'react-redux'
function Cart() {
const cart = useSelector((state) => state.cart)
return (
<div>
<h1>Cart</h1>
{cart.cartItems.map(cartItem => (
<div key={cartItem.id}>
<p>product : {cartItem.title}</p>
<p>price {cartItem.price}</p>
<p>quantity : {cartItem.cartQuantity}</p>
<p>url : <img src={cartItem.url} /></p>
</div>
))}
</div>
)
}
export default Cart
What you are trying to do is, assign UUID
First in terminal:
npm install uuid
Then:
import { v4 as uuidv4 } from 'uuid';
uuidv4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
More on here, a sof thread: How to create a GUID / UUID
The library, on npm: https://www.npmjs.com/package/uuid
Related
I am facing a problem with re-rendering after a state change in my NextJS app.
The function sendMessageForm launches a redux action sendMessage which adds the message to the state.
The problem is unrelated to the returned state in the reducer as I am returning a new object(return {...state}) which should trigger the re-render!
Is there anything that might block the re-render ?
This is the file that calls & displays the state, so no other file should be responsible ! But if you believe the problem might lie somewhere else, please do mention !
import { AttachFile, InsertEmoticon, Mic, MoreVert } from '#mui/icons-material';
import { Avatar, CircularProgress, IconButton } from '#mui/material';
import InfiniteScroll from 'react-infinite-scroller';
import Head from 'next/head';
import { useState, useEffect } from 'react';
import Message from '../../components/Message.component';
import styles from '../../styles/Chat.module.css'
import { useRouter } from 'next/router'
import {useSelector, useDispatch} from "react-redux"
import {bindActionCreators} from "redux"
import * as chatActions from "../../state/action-creators/chatActions"
const Chat = () => {
const router = useRouter()
const { roomId } = router.query
const auth = useSelector((state)=> state.auth)
const messages = useSelector((state)=> state.chat[roomId].messages)
const dispatch = useDispatch()
const {getMessages, markAsRead, sendMessage} = bindActionCreators(chatActions, dispatch)
const [inputValue, setInputValue] = useState("")
const sendMessageForm = (e) => {
e.preventDefault()
console.log("***inputValue:", inputValue)
sendMessage(roomId, inputValue)
}
const loadMessages = (page) => {
if(roomId)
getMessages(roomId, page)
}
//user-read-message
useEffect(() => {
//user-read-message
markAsRead(roomId, auth.user._id)
}, [messages]);
return (
<div className={styles.container}>
<Head>
<title>Chat</title>
</Head>
<div className={styles.header}>
<Avatar/>
<div className={styles.headerInformation}>
<h3>Zabre el Ayr</h3>
<p>Last Seen ...</p>
</div>
<div className={styles.headerIcons}>
<IconButton>
<AttachFile/>
</IconButton>
<IconButton>
<MoreVert/>
</IconButton>
</div>
</div>
<div className={styles.chatContainer}>
<InfiniteScroll
isReverse={true}
pageStart={0}
loadMore={loadMessages}
hasMore={messages.hasNextPage || false}
loader={<div className={styles.loader} key={0}><CircularProgress /></div>}
>
{Object.keys(messages.docs).map((key, index)=>{
return<Message
key={index}
sentByMe={messages.docs[key].createdBy === auth.user._id}
message={messages.docs[key].msg}
/>})}
</InfiniteScroll>
<span className={styles.chatContainerEnd}></span>
</div>
<form className={styles.inputContainer}>
<InsertEmoticon/>
<input className={styles.chatInput} value={inputValue} onChange={(e)=>setInputValue(e.target.value)}/>
<button hidden disabled={!inputValue} type='submit' onClick={sendMessageForm}></button>
<Mic/>
</form>
</div>)
};
export default Chat;
useSelector requires a new object with a new reference from the object you are passing to it in order to trigger the re-render
What you're doing with return {...state} is just creating a new object for the parent object but not the nested one useSelector is using, which is in your case :
const messages = useSelector((state)=> state.chat[roomId].messages)
So, you should return the whole state as a new object WITH a new state.chat[roomId].messages object
In other words, the references for the root object & the one being used should be changed.
Can't render the noofcartItems in my react UI. I get only NaN value as output in my UI.
Anything wrong in the syntax ? The context I created also seems to be failing.
Please ignore the console logs as I used it for debug purposes.
import CartContext from '../../CartStore/cart-context.js';
import CartIcon from '../Cart/CartIcon.js';
import './CartButton.css';
import { useContext } from 'react';
const CartButton = (props) => {
const context = useContext(CartContext);
const noofcartItems = context.items.reduce((curNo, item) => {
console.log(curNo, item.amount,curNo + item.amount, 'curNo + item.amount');
return curNo + item.amount;
}, 0);
console.log(noofcartItems,'No of cart items');
return (<button className='button' onClick={props.onClick}>
<span className='icon'>
<CartIcon/>
</span>
<span>Cart</span>
<span className='badge'>{noofcartItems}</span>
</button>
)
};
export default CartButton;
import React from 'react'
const CartContext = React.createContext({
items:[],
totalAmount: 0,
addItem: (item) => {},
removeItem: (id) => {}
});
export default CartContext;
You should console log your context.items array and check for the values of amount variable. It seems that one of the amount values must be undefined.
I don't know, whether it is correct or wrong while rendering, the component renders the number of objects it has plus one extra time (6 objects + 1), 7 times the component renders, Please suggest to me is it correct or wrong with reason. If I type 5 letters in the 5th task, it renders 30 times, which means in the 5th index, it have 5 objects so 25+(1 extra time in each object *5)=30 times I think it's a huge performance issue.
Create-task.js
import React, {useEffect, useState} from "react";
import store from "../redux/store";
import actions from "../redux/actions";
import '../styles/create-task.scss';
import {useSelector} from "react-redux";
import TaskTemplate from "./task-template";
import SaveTemplate from "./save-template";
const CreateTask=()=> {
// const [templates, setTemplates]=useState([]);
const onTaskCreate = () => {
store.dispatch({type: actions.ON_CREATE_TASK, payload: {'taskCount':taskCount + 1,'save':false}})
addTemplate();
store.dispatch({type:actions.ON_ADD_TEMPLATES, payload:template});
}
const createTask = useSelector(({reducers}) => {
const {taskCount, taskDetails,save,templates} = reducers;
return {taskCount, taskDetails,save, templates};
});
const {taskCount, taskDetails,save,templates} = createTask;
const {userData}=taskDetails;
const globalTemplates=createTask.templates;
let template = globalTemplates;
const addTemplate = () => {
template.push(<div key={`template${taskCount}`} id={`task${taskCount+1}`}><TaskTemplate/></div>);
return template;
}
useEffect(()=>{
// setTemplates(globalTemplates)
},[])
return (
<div>
<div className={"create-task"}>
<div>TASKS <span className={"task-count"}> {taskCount} </span></div>
<div className={"add-task-button"} onClick={onTaskCreate}>
<button>+</button>
</div>
</div>
{templates && templates.map((template)=>template)}
{/*{save?<SaveTemplate/>:templates.map((template)=> template)}*/}
</div>
)
}
export default CreateTask;
Task.js
import React, {useEffect, useState} from "react";
import store from "../redux/store";
import actions from "../redux/actions";
import DatePicker from 'react-date-picker';
import {useSelector} from "react-redux";
import TimePicker from 'react-bootstrap-time-picker';
import {wrapMapToPropsConstant} from "react-redux/lib/connect/wrapMapToProps";
const TaskTemplate=(props)=>{
const [dateValue, dateChange] = useState(new Date());
const [time, timeChange] = useState('10:00');
const tasks = useSelector(({reducers}) => {
const { taskDetails,edit, createTask} = reducers;
return {taskDetails, edit, createTask};
});
const{edit,taskDetails,createTask}=tasks;
const onFieldChange=(e)=>{
let editingTaskId=e.target.parentNode.parentNode.getAttribute("id");
editingTaskId=editingTaskId.split("task")[1]-1;
createTask.map((data,index)=>{
if(index === editingTaskId){
createTask[editingTaskId]["userData"][e.target.name]=e.target.value;
}
})
}
const dateChangeFormat = (val) =>{
var splitDate=val.toLocaleString('hu-HU').substr(0,12);
splitDate=splitDate.replaceAll(" ","").replaceAll(".","-");
return splitDate;
}
return(
<>
<div>
<label>Task Description</label>
<input name={"task_msg"} defaultValue={ props.task && props.task.task_msg && props.task.task_msg } onChange={(e)=>{onFieldChange(e)}}/>
</div>
<div>
<label>Date</label>
<DatePicker
onChange={onDateChange}
value={props.task?props.task.original_date:dateValue}
format={"y-MM-dd"}
name={"task_date"}
clearIcon={""}
/>
</div>
<div>
<label>Time</label>
<TimePicker start="10:00" end="21:00" step={30} onChange={onTimeChange} value={props.task?props.task.task_time:time} />
</div>
<div>
<label>Assign User</label>
<input name={"assigned_user"} defaultValue={ props.task && props.task.task_msg && props.task.assigned_user} onChange={(e)=>{onFieldChange(e)}}/>
</div>
<div>
<button onClick={(e)=>onFormSave(e)}> Save </button>
</div>
</>
)
}
export default TaskTemplate;
Reducer. js
import actions from "./actions";
const stateInit={
taskCount:0,
taskDetails:[],
templates:[],
createTask:[],
userData: {
task_msg: '',
task_date: '',
task_time: 36000,
assigned_user: '',
time_zone: new Date().getTimezoneOffset(),
is_completed: 0,
}
};
export default function tasks(state=stateInit, action){
switch (action.type){
case actions.ON_CREATE_TASK:{
const {taskCount,save}=action.payload;
return {
...state,
taskCount: taskCount,
save:save,
createTask: [...state.createTask,{userData:{...state.userData}}],
}
}
case actions.ON_VALUE_CHANGE:{
return {
...state,
createTask: action.payload,
}
}
case actions.ON_ADD_TEMPLATES:{
return{
...state,
templates: action.payload,
}
}
default:{
return {
...state
}
}
}
}
Output :
The short answer is that you do not control when the framework decides to re render objects that are bound to elements in the dom tree. It sort of detects that for you.
The long answer is to understand the lifecycle-and-state of objects as well as two-way bindings and what triggers render-updates:
https://reactjs.org/docs/state-and-lifecycle.html
https://react-cn.github.io/react/docs/two-way-binding-helpers.html
This question already has answers here:
Why is my onClick being called on render? - React.js
(5 answers)
Closed 2 years ago.
Hi I am building a frontend in react and I am rendering a List of places that I get from google maps api. I want each place to fire an action on onClick. If I don't pass any value it works, if I pass the id of the place that I get from props than onClick is fired when the list item is rendered leading to an error.
Here is the list component
import {ListGroup} from "react-bootstrap";
import {useDispatch} from "react-redux";
import {selectedPlace} from "../../actions/searchActions";
const PlaceList=function (props) {
const dispatch=useDispatch()
const handleClick=function (id) {
console.log('ciao '+id)
}
return(
<ListGroup>
{props.places.map(item=>{
return (<ListGroup.Item variant="flush" onClick={handleClick(item['place_id'])}>{item['formatted_address']}</ListGroup.Item>)
})}
</ListGroup>
)
}
export default PlaceList
I want the onClick to be fired just when the list item is clicked. Any Idea on how to solve?
onClick functions should be called like this!
import { ListGroup } from "react-bootstrap";
import { useDispatch } from "react-redux";
import { selectedPlace } from "../../actions/searchActions";
const PlaceList = (props) => {
const dispatch = useDispatch();
const handleClick = (id) => {
console.log('ciao ' + id)
}
return (
<ListGroup>
{
props.places.map(item => {
return (<ListGroup.Item variant="flush" onClick={() => handleClick(item['place_id'])}>{item['formatted_address']}</ListGroup.Item>)
})
}
</ListGroup>
)
}
export default PlaceList
Your onClick should be calling the function like this:
import { ListGroup } from 'react-bootstrap';
import { useDispatch } from 'react-redux';
import { selectedPlace } from '../../actions/searchActions';
export default (props) => {
const dispatch = useDispatch();
const handleClick = (id) => {
console.log('ciao ' + id);
};
return (
<ListGroup>
{props.places.map((item) => (
<ListGroup.Item variant="flush" onClick={() => handleClick(item['place_id'])}>
{item['formatted_address']}
</ListGroup.Item>
))}
</ListGroup>
);
};
I am kind of struggling to get the state of book, when I log the props I get book: undefined.
Any tips?
import React from 'react';
import { connect } from 'react-redux';
import BookForm from './BookForm';
const EditBook = (props) => {
console.log(props)
return (
<div>
hello
</div>
);
};
const mapStateToProps = (state, props) => {
return {
book: state.books.find((book) => book.id === props.match.params.id)
};
};
export default connect(mapStateToProps)(EditBook);
Rest of the project is on my Github: https://github.com/bananafreak/bookshelf
Update the Link in the BookListItem. You don't need the : before ${id}. The : is causing the problem.
<Link to={`/edit/${id}`}><h2>{title}</h2></Link>
In the EditBook component return the following
<BookForm {...props}></BookForm>
In the BookForm constructor set the state from props.book
this.state = { ...props.book }
I've ran into issues with this before where === will fail because the types of book.id and props.match.params.id are different. The params values are always strings - maybe try parseInt(props.match.params.id) or == comparison (with type coercion).
I managed to found where was my mistake.
In the component BookListItem:
import React from 'react';
import { Link } from 'react-router-dom';
const BookListItem = ({ author, title, genre, text, id, pages }) => (
<div>
<Link to={`/edit/${id}`}><h2>{title}</h2></Link>
<h3>by: {author}</h3>
<p>{genre}</p>
{pages > 0 && <p>{pages} pages</p>}
<p>{text}</p>
<p>-------------------------------</p>
</div>
);
export default BookListItem;
Before the ${id} I had unfortunately colon {/edit/:${id}} so then book.id and props.match.params.id could not match