I hope anyone can help me! I cant render my object in the right way.
My JSON:
{"status":"Entregado","_id":"5f490dd9b3f5192230a8f536","products":[{"_id":"5f44eaa1cf215b305449e216","name":"Reto Gratuito Medita Healing","price":0,"createdAt":"2020-08-25T10:40:33.845Z","updatedAt":"2020-08-25T10:40:33.845Z","__v":0,"count":1}],"details":"dimebag_666","client_email":"jobroman83#gmail.com","state":"CDMX","address":"acueducto","zip":"16200","client_id":"5f2ac527543c3835d028337c","amount":0,"createdAt":"2020-08-28T13:59:53.129Z","updatedAt":"2020-09-07T09:03:39.117Z","__v":0}
I rendered almost all the component with this code:
const SingleOrder = (props) => {
const token = getCookie('token') //// <-- right one
const Id = getCookie('token')
const [order, setOrder] = useState({});
const [error, setError] = useState(false);
const [statusValues, setStatusValues] = useState([])
const loadSingleOrder = orderId => {
read(orderId,token).then( data => {
if (data.error){
setError(data.error)
} else {
setOrder(data)
}
})
}
const loadStatusValues = () => {
getStatusValues(Id, token).then(data => {
if (data.error){
console.log(data.error)
} else{
setStatusValues(data)
}
})
}
const handleStatusChange = (e, orderId) => {
// console.log('update order status')
updateOrderStatus(Id, token ,orderId, e.target.value).then(
data => {
if (data.error){
console.log('status update failed')
} else {
// setRun(!run)
loadSingleOrder(orderId)
console.log('changed')
alert('Has Cambiado el estatus')
}
}
)
}
const showStatus = (order) => {
return (
<div className='form-group'>
<h5 className='mark mb-4'>Estatus: {order.status}</h5>
<select className='form-control'
onChange={(e) => handleStatusChange(e, order._id)}>
<option>Actualizar estado de orden</option>
{statusValues.map((status, index) => (
<option key={index} value={status}>{status}</option>
) )}
</select>
</div>
)
}
const showInput = (key, value) => {
return (
<div className="input-group mb-2 mr-sm-2">
<div className="input-group-prepend">
<div className="input-group-text">{key}</div>
</div>
<input type="text" value={value} className="form-control" readOnly/>
</div>
)
}
useEffect (() => {
const orderId = props.match.params.orderId
loadSingleOrder(orderId)
loadStatusValues()
},[props])
const showOrderList = () => {
return (
<div className="card mr-2 mt-2 mb-5">
<h5 className="mb-1 mt-2 text-center bg-primary" style={{color:'red'}}>Numero de Orden: {order._id}</h5>
<ul className="list-group">
<li className='list-group-item'>
{/* {o.status} */}
{showStatus(order)}
</li>
<li className='list-group-item'>ID cliente: {order.client_id} </li>
<li className="list-group-item">Télefono: {order.client_phone}</li>
<li className="list-group-item" style={{fontWeight:'bold'}}> Cuenta de <i class="fab fa-instagram"/> vinculada al Taller: {order.details}</li>
<li className="list-group-item">E-mail: {order.client_email}</li>
<li className='list-group-item'>Total de la orden: ${order.amount}</li>
<li className='list-group-item' style={{fontWeight:'bold'}}>Comprado el dia:{" "}
{moment(order.createdAt).locale('es').format('LL')}
</li>
<li className='list-group-item'>Dirección : {order.address}</li>
<li className='list-group-item'>Estado : {order.state}</li>
<li className='list-group-item'>Código Postal: {order.zip}</li>
if a put this code the problem is --> "TypeError: order.products is undefined" {order.products.map((p, pIndex) => (
<div className='' key={pIndex} style={{padding:'20px', border:'1px solid #e6c4ba'}}
>
{showInput('Nombre del producto:', p.name)}
{/* {showInput('Precio del producto $:', p.price)}
{showInput('Cantidad pedida del producto:', p.count)}
{showInput('ID del producto:', p._id)} */}
</div>
))}
if i only put this i recived --> {order.products}
Error: Objects are not valid as a React child (found: object with keys {_id, name, price, createdAt, updatedAt, __v, count}). If you meant to render a collection of children, use an array instead.
</ul>
</div>
)
}
return(
<Layout>
<div className='container'>
{/* {showOrderList()} */}
{JSON.stringify(order)}
</div>
</Layout>
)
}
But when i want to render the products inside of my object i cant Render them, its because of that i receiving the message :
Error: Objects are not valid as a React child (found: object with keys
{_id, name, price, createdAt, updatedAt, __v, count}). If you meant to
render a collection of children, use an array instead.
{order.products}
I have to render this in the right way, I tried with map etc.. I'm always getting this error.
You can use map() like this:
<ul>
{order?.products.map(item => (
<>
<li>{item.name}</li>
<li>{item.price}</li>
<li>{new Date(item.createdAt)}</li>
<li>{new Date(item.updatedAt)}</li>
</>
)
}
</ul>
you can also include the rest of the object items like id or count if necessary.
using <> </> tags are a short way of <React.Fragment> </React.Fragment> respectively.
Related
I have a list of dynamically generated items with information about real estate objects. Every item has a button. When clicked a contact form pops up. However when a button is clicked the event fires on all of the items in the list instead of only on that single item? I solved it with javascript event delegation but that's not the react-way to handle this. What is the best way to do this in react? I'm using React v18, React-Hooks and Redux-Toolkit. Data is fetched from a mongoDB database using Express.
Thanks you!
// Items list with pagination and map component
// Map component also contains the button!
const Content = () => {
const [show, setShow] = useState(false)
const [currentNumber, setCurrentNumber] = useState(1)
const [newList, setNewList] = useState([])
const [errorMessage, setErrorMessage] = useState(false)
const realestates = useSelector(state => state.realestate)
const { loading, realestate, error } = realestates
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchData())
if (realestate) {
setShow(true)
}
if (error) {
setErrorMessage(true)
}
}, [dispatch, error, realestate])
const pageNumbers = []
const resultsPerPage = 4
const pages = Math.ceil(realestate.length / resultsPerPage)
for (let i = 1; i <= pages; i++) {
pageNumbers.push(i)
}
const pagination = (number) => {
setCurrentNumber(number)
}
const slicedList = useCallback(() => {
const data2 = realestate.slice(((currentNumber - 1) * resultsPerPage), (currentNumber * resultsPerPage))
setNewList(data2)
}, [currentNumber, realestate])
useEffect(() => {
slicedList()
}, [slicedList])
return (
<div className="content2">
{errorMessage ? <div>{error}</div> : ""}
//List item
{show ? newList.map(item => {
const { _id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price } = item;
return (
<div className="content2_property" key={_id}>
<div className="content2_castleImageBox"><img src={img} alt="" className="content2_castleImage" /></div>
<div className="content2_info">
<div className="title"><h5>{name}</h5></div>
<div className="location">
<div><span>Region:</span> {region}</div>
<div><span>Departement:</span> {departement}</div>
<div><span>City:</span> {city}</div>
</div>
<div className="icons">
<div className="icon">{bedrooms}<img src={bedroomsIcon} alt="" /></div>
<div className="icon">{bathrooms} <img src={bathroomsIcon} alt="" /></div>
<div className="icon">{livingspace}<img src={livingspaceIcon} alt="" /></div>
<div className="icon">{area}ha<img src={areaIcon} alt="" /></div>
</div>
<div className="price"><span>Price:</span> {item.price === 'Not for Sale' ? price : `$${price},-`}</div>
</div>
<Map region={region} map={map} />
</div>
)
}) : <Loader />}
// Pagination buttons
<div className="btns">
{pageNumbers.map((number, index) => {
return (number === currentNumber) ? <button className="paginationBtn active" onClick={() => pagination(number)} key={index} >{number}</button> :
<button className="paginationBtn" onClick={() => pagination(number)} key={index} >{number}</button>
})}
</div>
</div>
)
}
export default Content
Map component with button
const Map = ({ region, map }) => {
const [showRegionName, setShowRegionName] = useState(false)
const handleMouseOver = () => {
setShowRegionName((prev) => !prev);
}
const makeEnquiry = () => {
//show contact form
}
return (
<div className="mapEnquiryBox">
<div className="map" onMouseEnter={handleMouseOver} onMouseLeave={handleMouseOver}>
<img src={map} alt="map" />
<div className={showRegionName ? "regionName" : "regionName hide"} >{region}</div>
</div>
<button className="enquiry" onClick={makeEnquiry}>Make enquiry</button>
</div>
)
}
export default Map
I think, the issue is with the key in the component.
This is how React differentiated between the components.
This is how I recently made my pagination:
Parent
{pageNumbersArray.map(pageNumber => (
<PaginationButton
key={pageNumber}
active={pageNumber === currentPage}
disabled={false}
onClick={() => handlePageChange(pageNumber)}
title={pageNumber}
/>
))}
Pagination Button
export default function PaginationButton({title, onClick, active, disabled}) {
return (
<button onClick={disabled ? null : onClick}>
<span>
{title}
</span>
</button>
);
}
This might come in handy for you.
Problem solved. I had to split the items list into separate components. One for the list container , one for the list items and one for every single list item. In the single item component I managed the state of that particular item, so when clicking a button the event only fires on that particular item.
Like so:
The list container
<div className="content2">
{errorMessage ? <div>{error}</div> : ""}
<ListItems newList={newList} show={show}/>
<div className="btns">
{pageNumbers.map((number, index) => {
return (number === currentNumber) ? <button className="paginationBtn active" onClick={() => pagination(number)} key={index} >{number}</button> :
<button className="paginationBtn" onClick={() => pagination(number)} key={index} >{number}</button>
})}
</div>
</div>
The list items
const ListItems = ({ newList, show }) => {
return (
<>
{show ? newList.map(item => {
const { _id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price } = item;
return (
<SingleProperty id={_id} area={area} bathrooms={bathrooms} bedrooms={bedrooms} city={city}
departement={departement} region={region} img={img} livingspace={livingspace} map={map} name={name} price={price}/>
)
}) : < Loader />}
</>
)
}
An the single item component
const SingleProperty = ({ id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price}) => {
const [ showForm, setShowForm ] = useState(false)
return (
<div className={!showForm ? "content2_property" : "content2_property enlarge"} key={id}>
<div className="content2_property_castleImageBox"><img src={img} alt="castle" className="content2_property_castleImage" /></div>
<div className="content2_property_info">
<div className="title"><h5>{name}</h5></div>
<div className="location">
<div><span>Region:</span> {region}</div>
<div><span>Departement:</span> {departement}</div>
<div><span>City:</span> {city}</div>
</div>
<div className="icons">
<div className="icon">{bedrooms}<img src={bedroomsIcon} alt="" /></div>
<div className="icon">{bathrooms} <img src={bathroomsIcon} alt="" /></div>
<div className="icon">{livingspace}<img src={livingspaceIcon} alt="" /></div>
<div className="icon">{area}ha<img src={areaIcon} alt="" /></div>
</div>
<div className="price"><span>Price:</span> {price === 'Not for Sale' ? price : `$${price},-`}</div>
</div>
<Map region={region} map={map} setShowForm={setShowForm}/>
</div>
)
}
I use prop drilling to pass down the id value of the document, but every time I click on a document to update it using updateDoc, the same document gets updated(always the latest one added), not the one I clicked on. I don't understand why the unique IDs don't get passed down correctly to the function, or whether that's the problem. I use deleteDoc this way and it's working perfectly. Any help will be appreciated.
This is where I get the id value from
const getPosts = useCallback(async (id) => {
const data = await getDocs(postCollectionRef);
setPosts(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
});
useEffect(() => {
getPosts();
}, [deletePost]);
return (
<div className={classes.home}>
<ul className={classes.list}>
{posts.map((post) => (
<BlogCard
key={post.id}
id={post.id}
title={post.title}
image={post.image}
post={post.post}
date={post.date}
showModal={showModal}
setShowModal={setShowModal}
deletePost={() => {
deletePost(post.id);
}}
showUpdateModal={showUpdateModal}
setShowUpdateModal={setShowUpdateModal}
/>
))}
</ul>
</div>
);
This is where I pass through the id value to the update modal component for each document:
function BlogCard(props) {
const [postIsOpen, setPostIsOpen] = useState(false);
const modalHandler = () => {
props.setShowModal((prevState) => {
return (prevState = !prevState);
});
};
const updateModalHandler = () => {
props.setShowUpdateModal((prevState) => {
return (prevState = !prevState);
});
};
const handleView = () => {
setPostIsOpen((prevState) => {
return (prevState = !prevState);
});
};
return (
<>
{props.showUpdateModal && (
<UpdateModal
showUpdateModal={props.showUpdateModal}
setShowUpdateModal={props.setShowUpdateModal}
id={props.id}
title={props.title}
image={props.image}
post={props.post}
/>
)}
{props.showModal && (
<DeleteModal
showModal={props.showModal}
setShowModal={props.setShowModal}
deletePost={props.deletePost}
/>
)}
<div className={classes.blogCard} id={props.id}>
<div className={classes.head}>
<p className={classes.title}> {props.title}</p>
<div className={classes.buttons}>
<button className={classes.editButton} onClick={updateModalHandler}>
Edit
</button>
<button className={classes.removeButton} onClick={modalHandler}>
Delete
</button>
</div>
</div>
<p className={classes.date}>{props.date}</p>
<img src={props.image} alt="image" />
{!postIsOpen ? (
<p className={classes.viewHandler} onClick={handleView}>
Show More
</p>
) : (
<p className={classes.viewHandler} onClick={handleView}>
Show Less
</p>
)}
{postIsOpen && <p className={classes.article}>{props.post}</p>}
</div>
</>
);
}
export default BlogCard;
Here I create the function to update and add the onclick listener
function UpdateModal(props) {
const [title, setTitle] = useState(props.title);
const [image, setImage] = useState(props.image);
const [post, setPost] = useState(props.post);
const updateModalHandler = (prevState) => {
props.setShowUpdateModal((prevState = !prevState));
};
const updatePost = async (id) => {
const postDocRef = doc(db, "posts", id);
props.setShowUpdateModal(false);
try {
await updateDoc(postDocRef, {
title: title,
image: image,
post: post,
});
} catch (err) {
alert(err);
}
};
return (
<div onClick={updateModalHandler} className={classes.backdrop}>
<form onClick={(e) => e.stopPropagation()} className={classes.form}>
<label htmlFor="title">Title</label>
<input
id="title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<label htmlFor="image">Image(URL)</label>
<input
id="image"
type="text"
value={image}
onChange={(e) => setImage(e.target.value)}
/>
<label htmlFor="post">Post</label>
<textarea
id="post"
cols="30"
rows="30"
value={post}
onChange={(e) => setPost(e.target.value)}
/>
<div className={classes.buttons}>
<button className={classes.cancel} onClick={updateModalHandler}>Cancel</button>
<button className={classes.update} onClick={() => updatePost(props.id)}>Update</button>
</div>
</form>
</div>
);
}
export default UpdateModal;
This is the way my data is structured
firebase
i am following a mern stack tutorial of creating a full stack web application using react and i got an error in cartItems.map-- Uncaught (in promise) TypeError: cartItems.map is not a function. The above error occurred in the component: , i guess it's because of an array and i have tried to make it an array but it was also not working for me
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { addToCart } from '../actions/cartActions';
import MessageBox from '../components/MessageBox';
export default function CartScreen(props) {
const params = useParams();
const productId = params.id;
const {search} =useLocation();
const qtyInUrl = new URLSearchParams(search).get('qty');
const qty = qtyInUrl?Number(qtyInUrl):1;
const cart = useSelector(state => state.cart);
const {cartItems } = cart;
const{ error} = cart;
const dispatch = useDispatch();
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty));
}
}, [dispatch, productId, qty]);
const removeFromCartHandler = () => {
// delete action
// dispatch(removeFromCart(id));
};
const navigate=useNavigate()
const checkoutHandler = () => {
navigate('/signin?redirect=shipping');
};
return (<div className="row top">
<div className="col-2">
<h1>Shopping Cart</h1>
{error && <MessageBox variant="danger">{error}</MessageBox>}
{cartItems.length === 0 ? (
<MessageBox>
Cart is empty. <Link to="/">Go Shopping</Link>
</MessageBox>
) : (
<ul>
{cartItems.map((item) => (
<li key={item.product}>
<div className="row">
<div>
<img
src={item.image}
alt={item.name}
className="small"
></img>
</div>
<div className="min-30">
<Link to={`/product/${item.product}`}>{item.name}</Link>
</div>
<div>
<select
value={item.qty}
onChange={(e) =>
dispatch(
addToCart(item.product, Number(e.target.value))
)
}
>
{[...Array(item.countInStock).keys()].map((x) => (
<option key={x + 1} value={x + 1}>
{x + 1}
</option>
))}
</select>
</div>
<div>${item.price}</div>
<div>
<button
type="button"
onClick={() => removeFromCartHandler(item.product)}
>
Delete
</button>
</div>
</div>
</li>
))}
</ul>
)}
</div>
<div className="col-1">
<div className="card card-body">
<ul>
<li>
<h2>
Subtotal ({cartItems.reduce((a, c) => a + c.qty, 0)} items) : $
{cartItems.reduce((a, c) => a + c.price * c.qty, 0)}
</h2>
</li>
<li>
<button
type="button"
onClick={checkoutHandler}
className="primary block"
disabled={cartItems.length === 0}
>
Proceed to Checkout
</button>
</li>
</ul>
</div>
</div>
</div>
);
}
This error occurs because your data is not an array. The .map() function only works with arrays. First, you'll need to confirm your data type. Second, you'll need to transform your data into an array.
You should check your cartItems type and change your state to be sure to have an array.
source : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Map
I would like to ask for some help with this, i don't know if its normal or not.
Have This components, one is a container that fetch the data and the second one receive the data and display it in a div. Nothing fancy.
const ProjectContainer = () => { // component
const projects = useSelector((state) => state.projectReducer.projects);
const count = useSelector((state) => state.projectReducer.count);
const isDarkMode = useSelector((state) => state.themeReducer.isDarkMode);
const [isLoading, setIsLoading] = useState(false);
const limit = 5;
const dispatch = useDispatch();
useEffect(() => {
console.log("INSIDE USEFFECT");
if (projects.length > 0) return; // avoid fetching data if the state has data already
async function getData() {
setIsLoading(true);
try {
const projectsCollectionRef = db.collection("project-collection");
const projectsCountRef = db
.collection("aggregation")
.doc("project-collection");
console.log("FETCHING DATA");
const responseCount = await projectsCountRef.get();
const count = await responseCount.data().count;
//dispatch
dispatch({ type: "SET_PROJECTS_COUNT", payload: count });
const response = await projectsCollectionRef
.orderBy("createdAt")
.limit(limit)
.get();
let dataSend = [];
response.forEach((document) => {
dataSend.push({ ...document.data(), uid: document.id });
});
//dispatch
dispatch({ type: "SET_PROJECTS", payload: dataSend });
setIsLoading(false);
} catch (error) {
console.error(error);
}
}
getData();
}, [dispatch, projects.length]);
return (
<div className="container mx-auto text-center">
<div>
Proyectos
</div>
{isLoading && projects.length === 0 ? (
<div >
<div id="title">
<p>
Cargando....
</p>
</div>
</div>
) : (
<>
{projects.length === 0 ? (
<div >
<div id="title" >
<p>
No hay proyectos que mostrar....
</p>
</div>
</div>
) : (
<>
<div >
{projects.map((project, index) => {
return (
<Project data={project} index={index} key={project.uid} />
);
})}
</div>
{count !== projects.length && (
<button>
Cargar más
</button>
)}
</>
)}
</>
)}
</div>
);
};
export default ProjectContainer;
The component that shows the data is something like this
import React from "react";
import { useSelector } from "react-redux";
const Project = (props) => {
const { data, index } = props;
console.log({ index });
const isDarkMode = useSelector((state) => state.themeReducer.isDarkMode);
return (
<div>
<div id="image">
<div>
<img
src={data.imageURL}
alt=""
/>
</div>
</div>
<div id="textblock">
<h1 >
{data.name}
</h1>
<div id="description">
<span >{data.description}</span>
<div >
<p>
Tecnologías
</p>
{data.technologies.map((technology) => {
return (
<span key={technology}>
{technology}
</span>
);
})}
</div>
<div >
<div >
<span>
Api Utilizada:
</span>
</div>
<div >
<span>
{data.usedAPI}
</span>
</div>
</div>
</div>
</div>
</div>
);
};
export default Project;
I mean, it works, it does its job, but I don't know if it's correct, in a more realistic company work it should work like this ?
I read that Strict mode can force to do some re renders, but i checked and don't have it.
At the end console looks like this ..
thanks in advance everyone :)
React will re-render once for each dispatch, even if multiple dispatch functions are called in the same cycle. This article has some good info on it:
https://medium.com/unsplash/react-redux-performance-considerations-when-dispatching-multiple-actions-5162047bf8a6
Fortunately, there is a solution: the batch function provided by Redux:
https://react-redux.js.org/api/batch
Just call both of your dispatch calls from within a batch, and you should see it only re-render once.
I am trying to build a todo list and when I console log it deletes the item I am attempting to delete onclick but the item stays on the screen. what I am trying to do is delete the item from the list onclick.
import React, { useState } from "react";
export function Todos() {
const [tasks, setTasks] = useState([]);
const deleteLabel = ind => {
tasks.splice(ind, 1);
console.log(tasks);
};
return (
<div className="container d-flex justify-content-center">
<div className="row">
<div className="col">
<input
onKeyUp={e =>
e.keyCode === 13 &&
setTasks(
tasks.concat({
label: e.target.value
})
)
}
/>
<div className="list-group">
{tasks === null
? "Loading..."
: tasks.map((t, index, myarr) => (
<a
href="#"
className="list-group-item list-group-item-action"
key={index}
onClick={() => {
deleteLabel(index);
}}>
{t.label}
</a>
))}
</div>
</div>
</div>
</div>
);
}
It's because you're not updating your state after splicing the item from your array. The component only re-renders when there is any change in the state.
You can use the update your deleteLabel function as suggested in the previous answer:
const deleteLabel = ind => {
const newTasks = [...tasks]
newTasks.splice(ind, 1);
setTasks(newTasks)
console.log(newTasks);
};
i think is better to change your deleteLabel function to
const deleteLabel = ind => {
const newTasks = [...tasks]
newTasks.splice(ind, 1);
setTasks(newTasks)
console.log(newTasks);
};