reactjs custom component input value doesnt update with state change - javascript

I am working on a list app, and I am having issues with the components not updating correctly. I pull the users list from a JSON file and save it in a state. I am using context to pass that state and other information around to my different compoents. The smallest component is the user items broken out into a list which is editable. It is here with the list of items that I am having issues with.
For example, I have two different JSON files:
[{"userId": 81944,
"listId": 1,
"title": "testa",
"items": [
{
"listItemId": 0,
"product": "walnuts",
"quantity": 1,
"category": "Bakery",
"unit": "Each",
"cart": false
},
{
"listItemId": 1,
"product": "syrup",
"quantity": 1,
"category": "Beverages",
"unit": "Each",
"cart": true
},
{
"listItemId": 2,
"product": "cinnamon",
"quantity": 6,
"category": "Bakery",
"unit": "Each",
"cart": false
},
{
"listItemId": 3,
"product": "gabonzo beans",
"quantity": 1,
"category": "Canned Goods",
"unit": "Each",
"cart": true
},
{
"listItemId": 4,
"product": "diced tomatos",
"quantity": 7,
"category": "Produce",
"unit": "Each",
"cart": false
},
{
"listItemId": 5,
"product": "milk",
"quantity": 1,
"category": "Dairy",
"unit": "Oz",
"cart": false
},
{
"listItemId": 6,
"product": "salmon",
"quantity": 3,
"category": "Meat",
"unit": "Lb",
"cart": false
}]},{
"userId": 78863,
"listId": 4,
"title": "testd",
"items": [
{
"listItemId": 0,
"product": "half and half",
"quantity": 1,
"category": "Dairy",
"unit": "Each",
"cart": false
},
{
"listItemId": 1,
"product": "Blue Cheese",
"quantity": 1,
"category": "Dairy",
"unit": "Each",
"cart": false
},
{
"listItemId": 2,
"product": "Garlic",
"quantity": 1,
"category": "Produce",
"unit": "Each",
"cart": false
},
{
"listItemId": 3,
"product": "Chestnuts",
"quantity": 1,
"category": "Other",
"unit": "Each",
"cart": false
},
{
"listItemId": 4,
"product": "Balsamic Vinegar",
"quantity": 1,
"category": "Other",
"unit": "Each",
"cart": false
},
{
"listItemId": 5,
"product": "Onions",
"quantity": 1,
"category": "Produce",
"unit": "Each",
"cart": false
},
{
"listItemId": 6,
"product": "Flax Seed",
"quantity": 1,
"category": "others",
"unit": "Each",
"cart": false
},
{
"listItemId": 7,
"product": "Plantains",
"quantity": 1,
"category": "Produce",
"unit": "Each",
"cart": false
}]}]
In my app I have a dialog box that allows me to switch between my lists. I then take list and pass it a custom component to be drawn on the screen.
import React, {useState,useEffect} from 'react';
const Card=(props)=>{
//console.log('prop');
const [cart, setCart] = useState(props.cart);
const [Product, setProduct] = useState(props.item);
const [Quantity, setQuantity] = useState(props.units);
// useEffect(()=>{
// setProduct(props.item)
// setQuantity(props.units)
// setCart(props.cart);
// },[])
console.log(props)
return (
<li key={props.value}>
<div>
<input type="checkbox" checked={cart} onChange={(e)=>{props.cartChange(e.target)}}/>
</div>
<div>
<input id={'product '+props.value} className='update'
type='text' value={Product}
onChange={(e)=>setProduct(e.target.value)}
/>
<br/>
<input id='quantityValue' className='update'
type='number' value={Quantity}
onChange={(e)=>setQuantity(e.target.value)}
/>
<span id='quantityType' className='update'>{props.unitType}</span>
</div>
<div>
<button id='save-button' type='button'
onClick={(e)=>{props.change(Product,Quantity,props.value)}}>✓ save</button>
<button id='delete-button' type='button'>✗ delete</button>
</div>
</li>
)
}
export default Card;
This is the code that calls the custom components. You will see that I am calling it from a array.map() those arrays are fine, and have the correct information in them.
import React, {useContext,useEffect} from 'react';
import {DataContext} from '../../../context/test/DataContext'
import Card from './ItemCard';
const update=(x)=>{
console.log(x)
}
const List = () =>{
const {listId} = useContext(DataContext);
const {userItemList} = useContext(DataContext);
const {GetItemList} = useContext(DataContext);
const {ListSplit} = useContext(DataContext);
const {foundList} = useContext(DataContext);
const {findList} = useContext(DataContext);
const {Updater} = useContext(DataContext);
const {cartUpdater} = useContext(DataContext);
useEffect(()=>{
GetItemList();
},[listId])
useEffect(()=>{
ListSplit();
},[userItemList])
// console.log(findList);
// console.log(foundList);
return(
<div>
<p>To find:</p>
<ul>
{findList.map((item,index)=><Card key={item.listItemId} index={index}
value={item.listItemId} cart={item.cart} item={item.product}
units={item.quantity} unitType={item.unit}
cartChange={cartUpdater} change={Updater} />)}
</ul>
<p>Found:</p>
<ul>
{foundList.map((item,index)=><Card key={item.listItemId} index={index}
value={item.listItemId} cart={item.cart} item={item.product}
units={item.quantity} unitType={item.unit}
cartChange={cartUpdater} change={Updater} />)}
</ul>
</div>
)
}
export default List;
Each time I switch this, the props that I console log out change correctly. Also, if I look at my compoents in dev tools (chrome) I see that the states should be correct, however what I see on the screen is not correct. For example the second item which is cinnamon, if I switch to the second list should be Blue Cheese. The prop changes, as does the state, but what I see on the screen is still cinnamon.
I know that I probably didnt explain it that clearly, but below is a screen shot of what I am talking about.

You were close with the commented out code. Since you are setting your props to state (which is a bad idea and I will discuss at the bottom), your useState only sets the state initially. You want to watch these props and update when they do.
useEffect(() => {
setProduct(props.item)
setQuantity(props.units)
setCart(props.cart);
}, [props.item, props.units, props.cart]);
The items in the array are what useEffect watches to know if it should fire.
As a side note - assigning props to state is a bad idea and you've seen why - they don't automatically update. You should hoist up where these props are set to the parent and you can pass them down as props and use them directly. You can pass down the setters as props as well, which can update the parent.
This article, while referencing class based React component may provide more information if you'd care to read up on it.
const { useState } = React;
const initialItems = [
{
listItemId: 1,
product: 'syrup',
quantity: 1,
category: 'Beverages',
unit: 'Each',
cart: true,
},
{
listItemId: 2,
product: 'cinnamon',
quantity: 6,
category: 'Bakery',
unit: 'Each',
cart: false,
},
{
listItemId: 3,
product: 'garbanzo beans',
quantity: 1,
category: 'Canned Goods',
unit: 'Each',
cart: true,
},
];
const Parent = () => {
const [items, setItems] = useState(initialItems);
const updateProduct = listItemId => (e) => {
setItems(items.map((item) => {
if (item.listItemId === listItemId) {
return { ...item, product: e.target.value };
}
return item;
}));
};
const updateQuantity = listItemId => (e) => {
setItems(items.map((item) => {
if (item.listItemId === listItemId) {
return { ...item, quantity: e.target.value };
}
return item;
}));
};
return (
<div>
<div style={{ width: "50%" }}>
All Items - (this state lives inside the parent component)
</div>
<div>
{items.map(item => (
<div>
<div>
Product - {item.product}
</div>
<div>
Quantity - {item.quantity}
</div>
</div>
))}
</div>
<div style={{ width: "50%" }}>
{items.map(item => (
<Child item={item} updateQuantity={updateQuantity} updateProduct={updateProduct} />
))}
</div>
</div>
);
};
const Child = ({ item, updateQuantity, updateProduct }) => {
return (
<div>
<div>
<span>
Product -
</span>
<span>
<input value={item.product} onChange={updateProduct(item.listItemId)} />
</span>
</div>
<div>
<span>
Quantity -
</span>
<span>
<input value={item.quantity} onChange={updateQuantity(item.listItemId)} />
</span>
</div>
</div>
);
};
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
A little example above. The parent component holds the state of the items and maps over each to create a child component. This example is a little rough around the edges. You could do something like adding data-id and name to each input to simplify the updating functions, or use useReducer to hoist that logic a bit, but I think it gets you going in the right direction.

Related

How to Destructure JSON array>object>array

I'm trying to destructure a JSON file that looks like this:
[
{
"Bags": [
{
"id": 1,
"name": "Michael Kors Bag",
"price": 235,
"imgURL": "/imgs/03045643da82a42a4a5c86842f4b17f1.jpg"
},
{
"id": 2,
"name": "Ted Baker Bag",
"price": 495,
"imgURL": "/imgs/4c176b2fa86bdcddf74822c2501bbcac.jpg"
},
{
"id": 3,
"name": "Coach Bag",
"price": 238,
"imgURL": "/imgs/coach-jes-crossbody-signature-canvas-brown-black-59181.jpg"
},
{
"id": 4,
"name": "Kate Spade Bag",
"price": 35,
"imgURL": "/imgs/10.jpg"
}
]
},
{
"Shoes": [
{
"id": 1,
"name": "Michael Kors Bag",
"price": 235,
"imgURL": "/imgs/03045643da82a42a4a5c86842f4b17f1.jpg"
},
{
"id": 2,
"name": "Ted Baker Bag",
"price": 495,
"imgURL": "/imgs/4c176b2fa86bdcddf74822c2501bbcac.jpg"
},
{
"id": 3,
"name": "Coach Bag",
"price": 238,
"imgURL": "/imgs/coach-jes-crossbody-signature-canvas-brown-black-59181.jpg"
},
{
"id": 4,
"name": "Kate Spade Bag",
"price": 35,
"imgURL": "/imgs/10.jpg"
}
]
}
]
So that I get the name of the objects ("Bags" and "Shoes").
I'm trying to print out the results on a page based on which is which and I'm feeding in the names as strings to a Store component like so:
<Route path="/store" element={<Store merch="Bags" />} />
This is my Store.tsx file, it doesn't work at all but it's my attempt:
import storeItems from "../data/items.json";
import { Row, Col, Container } from "react-bootstrap";
import { StoreItem } from "../components/StoreItem";
import { useState } from "react";
type StoreProps = {
merch: string;
};
export function Store({ merch }: StoreProps) {
const [data, setData] = useState([]);
for (let i = 0; i < storeItems.length; i++) {
let a = Object.values(storeItems[i]);
console.log(a);
}
console.log(storeItems);
return (
<>
<Container className="mw-80 d-flex align-items-center justify-content-center p-0 flex-column mb-5">
<h1 className="m-5">Bags</h1>
<Row md={2} xs={1} lg={3} className="g-3">
{storeItems.map((item) => (
<Col>
<StoreItem key={item.id} {...item} />
</Col>
))}
</Row>
</Container>
</>
);
}
In order to get ["Bags", "Shoes"] from your storeItems you could:
const names = storeItems.flatMap(mi => Object.keys(mi));
This would get additional keys on the same object as well, so if you had:
const storeItems = [
{ "Bags": /*...*/{}, "Bags2": /*...*/{}, },
{ "Shoes": /*...*/{} },
];
then it would return [ "Bags", "Bags2", "Shoes" ]
I have to say, your data is in a pretty strange format, but I answered the question exactly as written
Also, if you want the names of all of the objects in a list, as in the name property of each object you could do something like:
const names = storeItems.flatMap(storeItem =>
Object
.values(storeItem)
.flatMap(itemList => itemList.map(item => item.name))
);
Also, if you want the names of all of the objects in the keys of an object by the name (like "Bags", or "Shoes") then you could:
const names = Object.fromEntries(storeItems.flatMap(storeItem =>
Object.entries(storeItem)
).map([k,v] => [k,v.map(i => i.name)]))
I'm not quite sure which one of these you wanted, so I included all of them (:
Edit
Looking at your code it seems as if you want to get a specific section of the data. This could be done by something like this:
const name = "Shoes";
const items = storeItems.flatMap(si => Object.entries(si))[name]
or if you know that your data is going to always have shoes first and be in the exact format then you could just do
const name = "Shoes";
const items = storeItems[1]["Shoes"];
Is this what you're trying to do?
const products = [
{
"Bags": [
{
"id": 1,
"name": "Michael Kors Bag",
"price": 235,
"imgURL": "/imgs/03045643da82a42a4a5c86842f4b17f1.jpg"
},
{
"id": 2,
"name": "Ted Baker Bag",
"price": 495,
"imgURL": "/imgs/4c176b2fa86bdcddf74822c2501bbcac.jpg"
},
{
"id": 3,
"name": "Coach Bag",
"price": 238,
"imgURL": "/imgs/coach-jes-crossbody-signature-canvas-brown-black-59181.jpg"
},
{
"id": 4,
"name": "Kate Spade Bag",
"price": 35,
"imgURL": "/imgs/10.jpg"
}
]
},
{
"Shoes": [
{
"id": 1,
"name": "Michael Kors Bag",
"price": 235,
"imgURL": "/imgs/03045643da82a42a4a5c86842f4b17f1.jpg"
},
{
"id": 2,
"name": "Ted Baker Bag",
"price": 495,
"imgURL": "/imgs/4c176b2fa86bdcddf74822c2501bbcac.jpg"
},
{
"id": 3,
"name": "Coach Bag",
"price": 238,
"imgURL": "/imgs/coach-jes-crossbody-signature-canvas-brown-black-59181.jpg"
},
{
"id": 4,
"name": "Kate Spade Bag",
"price": 35,
"imgURL": "/imgs/10.jpg"
}
]
}
]
const getProductsByKey = key => products
.filter(product => product.hasOwnProperty([key]))
.flatMap(obj => obj[key])
console.log(getProductsByKey('Shoes'))
The output of the above code would be:
[
{
id: 1,
name: 'Michael Kors Bag',
price: 235,
imgURL: '/imgs/03045643da82a42a4a5c86842f4b17f1.jpg'
},
{
id: 2,
name: 'Ted Baker Bag',
price: 495,
imgURL: '/imgs/4c176b2fa86bdcddf74822c2501bbcac.jpg'
},
{
id: 3,
name: 'Coach Bag',
price: 238,
imgURL: '/imgs/coach-jes-crossbody-signature-canvas-brown-black-59181.jpg'
},
{ id: 4, name: 'Kate Spade Bag', price: 35, imgURL: '/imgs/10.jpg' }
]
Please note that the supplied data is incorrect as the items under the key "Shoes" are apparently bags.
I'll explain the why and how of my code. First off, I wanted to make a function that could take any key as an argument. Today we have 'Bags' and 'Shoes', but tomorrow we may have more keys. Therefore, I didn't want to propose a solution that would involve "hard-coded" keys.
Once we have the key, we can use Array.prototype.filter to find the object containing the items we want. In the data we are provided with, 'Bags' and 'Shoes' are keys, not values. Hence why I used product.hasOwnProperty([key]) in the callbackFn. Note the use of the square brackets as we are searching for the value of a dynamic variable named key, not the actual string 'key'. Next we use Array.protoype.flatMap to get to the part of each object that we want, which is the array of items. We use .flapMap here to avoid the nested array that would normally result by chaining filter and map to the data.
For getting the shoes you can use this:
storeItems[1]["shoes"]
And for getting the bags you can use this:
storeItems[0]["bags"]
So, in the return expression in your attempt code, instead of this:
return (
<>
<Container className="mw-80 d-flex align-items-center justify-content-center p-0 flex-column mb-5">
<h1 className="m-5">Bags</h1>
<Row md={2} xs={1} lg={3} className="g-3">
{storeItems.map((item) => (
<Col>
<StoreItem key={item.id} {...item} />
</Col>
))}
</Row>
</Container>
</>);
use this (for bags):
return (
<>
<Container className="mw-80 d-flex align-items-center justify-content-center p-0 flex-column mb-5">
<h1 className="m-5">Bags</h1>
<Row md={2} xs={1} lg={3} className="g-3">
{storeItems[0]["bags"].map((item) => (
<Col>
<StoreItem key={item.id} {...item} />
</Col>
))}
</Row>
</Container>
</>);
You can use:
a combination of Array.map and Object.keys to get an array with available categories from storeItems object
Array.find to get the products for a given merch category.
const storeItems = [
{
"Bags": [
{
"id": 1,
"name": "Michael Kors Bag",
"price": 235,
"imgURL": "/imgs/03045643da82a42a4a5c86842f4b17f1.jpg"
},
{
"id": 2,
"name": "Ted Baker Bag",
"price": 495,
"imgURL": "/imgs/4c176b2fa86bdcddf74822c2501bbcac.jpg"
},
{
"id": 3,
"name": "Coach Bag",
"price": 238,
"imgURL": "/imgs/coach-jes-crossbody-signature-canvas-brown-black-59181.jpg"
},
{
"id": 4,
"name": "Kate Spade Bag",
"price": 35,
"imgURL": "/imgs/10.jpg"
}
]
},
{
"Shoes": [
{
"id": 1,
"name": "Michael Kors Bag",
"price": 235,
"imgURL": "/imgs/03045643da82a42a4a5c86842f4b17f1.jpg"
},
{
"id": 2,
"name": "Ted Baker Bag",
"price": 495,
"imgURL": "/imgs/4c176b2fa86bdcddf74822c2501bbcac.jpg"
},
{
"id": 3,
"name": "Coach Bag",
"price": 238,
"imgURL": "/imgs/coach-jes-crossbody-signature-canvas-brown-black-59181.jpg"
},
{
"id": 4,
"name": "Kate Spade Bag",
"price": 35,
"imgURL": "/imgs/10.jpg"
}
]
}
]
// Get the values for merch types
const categories = storeItems.map((entry) => Object.keys(entry)[0])
console.log(categories)
// Find the products for a given merch
const merch = 'Shoes'
const products = storeItems.find((entry) => entry[merch])[merch]
console.log(products)

"TypeError: Cannot read property 'filter' of undefined" and similarly for other array functions like map using ReactJS and redux

I am following a tutorial and when at this point I'm getting stuck. It gives me TypeError: Cannot read property 'filter' of undefined when I try to filter through the Product array. I was implementing the same functionality using Axios according to the tutorial and it was working. The instructor then changed to redux and I followed the same steps and that's when I got the error. I'm pretty new to React and completely new to Axios and Redux and I've been trying to find out what's wrong but I'm getting nowhere.
Here's my ProductScreens.jsx where the error occurs:
import React, { useEffect } from "react";
import Rating from "../Components/rating.jsx";
import { useDispatch, useSelector } from "react-redux";
import { listProducts } from "../actions/productActions.js"
function Sale({product}){
if("salePrice" in product){
return <li>Sale Price: ₹{product.salePrice}</li>;
}
else
return null;
}
export default function ProductsScreen(props){
const dispatch = useDispatch();
const productList = useSelector( state => state.productList);
const {loading,error,products} = productList;
useEffect(() =>{
dispatch(listProducts());
},[dispatch]);
const product = products.filter(prod => prod.category === props.match.params.category && prod.subcategory === props.match.params.subcategory);
return (
<div className="container-fluid main-cards">
<div className="row">
{
product.map(product => (
<div key={product._id} className="col-12 col-md-6 col-lg-4 main-card-item">
<div className="card">
</img>
<div className="card-body product-card list-group">
<h5 className="card-title">{product.name}</h5>
<p className="card-text">{product.description}</p>
<ul className="list-unstyled mt-3 mb-4">
<li>Price: ₹{product.price}</li>
<Sale product={product}/>
<li>Buy Now</li>
<li>
<Rating rating={product.rating} numReview={product.numReview}/>
</li>
</ul>
</div>
</div>
</div>
))};
</div>
</div>
)
}
Here is my productActions.js
import Axios from "axios";
import { PRODUCT_LIST_REQUEST, PRODUCT_LIST_SUCCESS, PRODUCT_LIST_FAIL } from "../constants/productConstants"
export const listProducts = () => async (dispatch) =>{
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const {data} = await Axios.get('/api/products');
dispatch({type: PRODUCT_LIST_SUCCESS, payload : data});
}catch(error){
dispatch({type: PRODUCT_LIST_FAIL, payload: error.message});
}
}
Here's my productReducers.js
export const productListReducer = (state = {loading: true, products: [] }, action) =>{
switch(action.type){
case PRODUCT_LIST_REQUEST :
return {loading: true};
case PRODUCT_LIST_SUCCESS:
return {loading: false, products: action.payload};
case PRODUCT_LIST_FAIL :
return {loading: false, error: action.payload};
default:
return state;
}
}
And my productConstants.js
export const PRODUCT_LIST_REQUEST = "PRODUCT_LIST_REQUEST";
export const PRODUCT_LIST_SUCCESS = "PRODUCT_LIST_SUCCESS";
export const PRODUCT_LIST_FAIL = "PRODUCT_LIST_FAIL";
I've tried solving it but I can't find out what's wrong. I did console.log(products) instead of const product = products.filter(prod => prod.category === props.match.params.category && prod.subcategory === props.match.params.subcategory); in ProductScreens.jsx and also replaced the contents of return with <h1></h1> and got back the following object:
[
{
"_id": "1",
"name": "example1",
"category": "example category1",
"subcategory": "example subcategory1",
"image": [
{
"_id": "image1",
"name": "/images/example1.jpg"
}
],
"mainImage": "/images/example1.jpg",
"price": "19000",
"brand": "brand1",
"rating": 4.5,
"numReview": 10,
"description": "some description for example1."
},
{
"_id": "2",
"name": "example2",
"category": "example category1",
"subcategory": "example subcategory1",
"image": [
{
"_id": "image2",
"name": "/images/example2.jpg"
}
],
"mainImage": "/images/example2.jpg",
"price": "16791",
"salePrice": "15500",
"brand": "brand2",
"rating": 4.7,
"numReview": 10,
"description": "some description for example2."
},
{
"_id": "3",
"name": "example",
"category": "example category2",
"subcategory": "example subcategory3",
"image": [
{
"_id": "image3",
"name": "/images/example3-1.jpg"
},
{
"_id": "image4",
"name": "/images/example3-2.jpg"
},
{
"_id": "image5",
"name": "/images/example3-3.jpg"
},
{
"_id": "image6",
"name": "/images/example3-4.jpg"
}
],
"mainImage": "/images/example3-1.jpg",
"price": "8549",
"salePrice": "7200",
"brand": "brand3",
"rating": 3,
"numReview": 10,
"description": "some description for example3."
},
{
"_id": "4",
"name": "example4",
"category": "example category3",
"subcategory": "example subcategory4",
"image": [
{
"_id": "image7",
"name": "/images/example4.jpg"
}
],
"mainImage": "/images/example4.jpg",
"price": "450",
"brand": "brand4",
"rating": 4.5,
"numReview": 10,
"description": "some description for example4."
},
{
"_id": "5",
"name": "example5",
"category": "example category1",
"subcategory": "example subcategory2",
"image": [
{
"_id": "image8",
"name": "/images/example5.jpg"
}
],
"mainImage": "/images/example5.jpg",
"price": "30000",
"salePrice": "27000",
"brand": "brand5",
"rating": 4.5,
"numReview": 10,
"description": "some description for example5"
}
]
Looking at the object above, filter() should work but it's not. Moreover, the above object was the same object I got when I used axios and it worked fine then. So I have no idea why it's not working anymore.
Also when I try to console.log(products._id) or any other property, I get the same TypeError. I tried console.log(products[0]) thinking maybe I have to do that to get the first object since its an array of objects, I got TypeError: Cannot read property '0' of undefined.
So I figured it out. I found two solutions. First, Turns out I was getting two objects before the PRODUCT_LIST_SUCCESS action occured. In the first object, the products was an empty array from the initial state and in the second object which was receieved from PRODUCT_LIST_REQUEST action, there was only a loading property so products was undefined. So I changed the switch case statement in productsReducers.js for PRODUCT_LIST_REQUEST to return {loading: true, products: []}; and that did the trick.
My second solution is to check if loading is true or false and only execute my code and render the page if loading is false.
I haven't been an avid programmer so I don't know best programming practices but I'm using the second solution.

React: trying to check/uncheck checkbox

I'm trying to make an obligatory Todo app to help with my React learning . The behavior I'm going for is multiple Todo lists, where you select a todo name and the list of todo items for that show. Select a different todo name and it's list todo items show, etc (like wunderlist/msft todo). For now it's using static json where each item has a child array.
I'm trying to check/uncheck a checkbox in order to mark the todo as done. My problem is that when a click the checkbox it doesn't update until a click away and then click back. What do I need to do to get it to update immediately?
code sandbox: https://codesandbox.io/s/github/cdubone/todo-react-bootstrap?file=/src/App.js
Here's the relevant code:
The function:
const updateChecked = todo => {
todo.IsChecked = !todo.IsChecked;
};
Properties on the component:
onChange={() => updateChecked(todo)}
isChecked={todo.IsChecked}
The input in the component:
<input
type="checkbox"
id={props.id}
name={props.id}
value={props.title}
checked={props.isChecked}
onChange={props.onChange}
/>
Here's the data -
JSON:
const TodoData = [
{
"Id": 1,
"Title": "Groceries",
"TodoList": [
{
"Id": 1,
"Title": "Apples",
"IsChecked": false
},
{
"Id": 2,
"Title": "Oranges",
"IsChecked": false
},
{
"Id": 3,
"Title": "Bananas",
"IsChecked": true
}
]
},
{
"Id": 2,
"Title": "Daily Tasks",
"TodoList": [{
"Id": 11,
"Title": "Clean Kitchen",
"IsChecked": false
},
{
"Id": 12,
"Title": "Feed Pets",
"IsChecked": false
},
{
"Id": 13,
"Title": "Do Stuff",
"IsChecked": false
}]
},
{
"Id": 3,
"Title": "Hardware Store",
"TodoList": []
},
{
"Id": 4,
"Title": "Costco",
"TodoList": [{
"Id": 21,
"Title": "Diapers",
"IsChecked": false
},
{
"Id": 22,
"Title": "Cat Food",
"IsChecked": false
},
{
"Id": 23,
"Title": "Apples",
"IsChecked": false
},
{
"Id": 24,
"Title": "Bananas",
"IsChecked": false
}]
},
{
"Id": 5,
"Title": "Work",
"TodoList": [
{
"Id": 34,
"Title": "TPS Reports",
"IsChecked": true
}
]
}
]
export default TodoData;
You're mutating state directly rather than using setTodoData to update (a lot of your functions are doing this). Also, you're mapping the the todo items of off ActiveTodoList rather than the todoData which creates a big issue. In react there should only be 1 source of truth, so it would be better to instead store the index of the active list and map "todoData[activeIndex].TodoList.", than to store an instance of the active list itself. Finally, because the data is so nested, you need the list index and the todo item index passed into your update function to reference its location in todoData. Map lets you pass index as a second parameter.
Something along the lines of the below:
todoData[activeIndex].TodoList.map((todo, todoIndex) => {
return (
<div onClick={()=> yourFunction(todo, todoIndex, activeIndex)}> </div>
)
}
yourFunction = (todo, todoIndex, listIndex) => {
let newTodo = {...todo, IsChecked: !todo.IsCheck};
setTodoData((todoData) => [
...todoData.slice(0,listIndex),
{
...todoData[listIndex],
TodoList: [
...todoData[listIndex].Todolist.slice(0,todoIndex)
,newTodo,
...todoData[listIndex].Todolist.slice(todoIndex + 1)
]
},
...todoData.slice(listIndex + 1)
]
}
The goal is not to change things directly, but make copies to send to state. Unfortunately with a nested object it can get confusing very fast.

How to get complete response data based on text field entry Reactjs

I am facing problem in getting values from rendered data in component that is already outputted on page render. What I need to do is, when someone types in data into text field, it should send it to database but taking that fields data from runtime data.
Currently, when I type something, it says undefined field etc.
This is not form data but a data that need to be update from text field.
So, if user write some xyz in text field, we need to update that data according to the id associated to that field.
I am not able to get data into: console.log(Id, projectId, userId, date, e.target.value)
I have used reduce method that serves the purpose but now I have another use case.
I dont want to set hidden fields as its not the right approach.
The problem is that when someone type data in text field, I need to get that text field data and associated id and respective data from it and pass it ti ajax call.
I need to send that data with ajax but as soon as I type something, it says undefined. I can easily get data from projects array but its of no use to me. I think array reduce method is not good for my use case.
Here is project array:
data = [
{
"id": 27,
"projectno": "007823",
"projectname": "non-A Project 2",
"dailyproof": 1,
"probability": "1.0",
"toleranceregistering": 2,
"customer_name": "Peter",
"user_id": "4",
"days_allocated": "231.0",
"days_real": "5.0",
"hours_real": "6.0",
"project_times": [
{
"id": 11,
"activity": "\"yht\"",
"date": "2020-04-28",
"hours": "2.0",
"project_id": 27,
"token": "\"trr\"",
"user_id": 4,
"created_at": "2020-04-22T12:36:55.479Z",
"updated_at": "2020-04-22T12:36:55.479Z"
},
{
"id": 12,
"activity": "\"yht\"",
"date": "2020-04-03",
"hours": "2.0",
"project_id": 27,
"token": "\"trr\"",
"user_id": 4,
"created_at": "2020-04-22T12:37:08.763Z",
"updated_at": "2020-04-22T12:37:08.763Z"
},
{
"id": 13,
"activity": "\"yht\"",
"date": "2020-04-14",
"hours": "2.0",
"project_id": 27,
"token": "\"dfg\"",
"user_id": 4,
"created_at": "2020-04-22T12:37:19.177Z",
"updated_at": "2020-04-22T12:37:19.177Z"
}
]
},
{
"id": 28,
"projectno": "007333",
"projectname": "non-A Project 2",
"dailyproof": 0,
"probability": "1.0",
"toleranceregistering": 2,
"customer_name": "Peter",
"user_id": "4",
"days_allocated": "231.0",
"days_real": "3.333333333333333333333333334",
"hours_real": "4.0",
"project_times": [
{
"id": 18,
"activity": "\"tgr\"",
"date": "2020-04-16",
"hours": "2.0",
"project_id": 28,
"token": "\"ujy\"",
"user_id": 4,
"created_at": "2020-04-22T12:39:41.465Z",
"updated_at": "2020-04-22T12:39:41.465Z"
},
{
"id": 19,
"activity": "\"ddd\"",
"date": "2020-04-11",
"hours": "2.0",
"project_id": 28,
"token": "\"fff\"",
"user_id": 4,
"created_at": "2020-04-22T12:39:55.020Z",
"updated_at": "2020-04-22T12:39:55.020Z"
}
]
},
{
"id": 29,
"projectno": "00721",
"projectname": "non-A Project 2",
"dailyproof": 1,
"probability": "1.0",
"toleranceregistering": 2,
"customer_name": "Peter",
"user_id": "4",
"days_allocated": "231.0",
"days_real": "5.0",
"hours_real": "6.0",
"project_times": [
{
"id": 22,
"activity": "\"cdf\"",
"date": "2020-04-11",
"hours": "2.0",
"project_id": 29,
"token": "\"fgff\"",
"user_id": 4,
"created_at": "2020-04-22T12:41:26.392Z",
"updated_at": "2020-04-22T12:41:26.392Z"
},
{
"id": 23,
"activity": "\"tg\"",
"date": "2020-04-15",
"hours": "2.0",
"project_id": 29,
"token": "\"ad\"",
"user_id": 4,
"created_at": "2020-04-22T12:41:38.747Z",
"updated_at": "2020-04-22T12:41:38.747Z"
},
{
"id": 24,
"activity": "\"ff\"",
"date": "2020-04-19",
"hours": "2.0",
"project_id": 29,
"token": "\"bbb\"",
"user_id": 4,
"created_at": "2020-04-22T12:41:47.500Z",
"updated_at": "2020-04-22T12:41:47.500Z"
}
]
},
{
"id": 30,
"projectno": "0074",
"projectname": "non-A Project 2",
"dailyproof": 1,
"probability": "1.0",
"toleranceregistering": 2,
"customer_name": "Peter",
"user_id": "4",
"days_allocated": "231.0",
"days_real": "3.333333333333333333333333334",
"hours_real": "4.0",
"project_times": [
{
"id": 25,
"activity": "\"ff\"",
"date": "2020-04-12",
"hours": "2.0",
"project_id": 30,
"token": "\"bbb\"",
"user_id": 4,
"created_at": "2020-04-22T12:42:09.385Z",
"updated_at": "2020-04-22T12:42:09.385Z"
},
{
"id": 26,
"activity": "\"rter\"",
"date": "2020-04-19",
"hours": "2.0",
"project_id": 30,
"token": "\"gfdg\"",
"user_id": 4,
"created_at": "2020-04-22T12:42:19.861Z",
"updated_at": "2020-04-22T12:42:19.861Z"
}
]
}
]
getDaysNumber('2020', '04') {
const dayNums = [];
const daysInMonth = new Date(year, month, 0).getDate();
for (let i = 1; i <= daysInMonth; i++) {
dayNums.push(i);
// console.log(i, ' xxx ');
}
return dayNums;
}
{
data.map((h, index) => (
<TableRow key={`mi-${index}`}>
<TableCell align="right">{h.projectno}</TableCell>
<TableCell align="right">{h.projectname}</TableCell>
<TableCell align="right">{h.customer_name}</TableCell>
<TableCell align="right">{h.days_allocated}</TableCell>
<TableCell align="right">{h.days_real}</TableCell>
<TableCell align="right">{h.hours_real}</TableCell>
{daysNumber.reduce((acc, number, index) => {
const found = h.project_times.find(item => number == item["date"].split('-')[2].replace(/^0+/, ''))
const Id = found && found["id"];
const projectId = found && found["project_id"];
const userId = found && found["user_id"];
const date = found && found["date"];
const hours = found && found["hours"];
found && console.log(Id, projectId, userId, date);
return [...acc,
h.dailyproof == 1 && hours > 0.0 ?
<TableCell align="right" key={`mi-${index}`}
onClick={this.launchCreateContactDialog}>{hours}</TableCell>
:
<TableCell align="right" key={`mi-${index}`}>
<TextField required fullWidth size="small"
variant="outlined"
onKeyUp={(e) => console.log(Id, projectId, userId, date, e.target.value)}/>
</TableCell>
]
}, [])
}
</TableRow>
))
}
This find call may sometimes return undefined.
const found = h.project_times.find(item => number == item["date"]
.split('-')[2]
.replace(/^0+/, '')
)
This is expected when no matches are found. And since it's undefined, all of these will also end up undefined:
const Id = found && found["id"];
const projectId = found && found["project_id"];
const userId = found && found["user_id"];
const date = found && found["date"];
const hours = found && found["hours"];
Therefore, it's not unusual that your console.log statement will log out the value undefined.
It sounds like you're needing to do a few things:
Maintain this data as state in your component.
Add a function to mutate this state.
Add a function to store the state (calling an API)
I don't have enough context to answer #3 for you, but here's the type of pattern you want to go for:
import { useEffect, useState } from 'react';
function HoursEntry() {
const [state, setState] = useState();
useEffect(() => {
// Do your data fetching here; for now will use your constant
setState(data);
}, []);
function updateHours({ userId, projectId, hourEntryId, date, hours }) {
// Build newData based on the changes...
setState(newData);
}
// All the rendering stuff. Rendered components should be mappings of what's in
// state...
<TextField
required fullWidth
size="small"
variant="outlined"
value={hours}
onChange={(e) => updateHours({
userId,
projectId,
hourEntryId: Id,
date,
hours: parseFloat(e.target.value)
})}/>
// ...
}
Inside of your updateHours function, you'll create a new copy of your data with the expected modifications. For example, if you're updating an existing object, you'll update its hours property. If you're updating something for which there is no record, you'll create a new one, etc. The key is your call to setState to update the data in your component.
Then, once you have your submit button or whatever method you've got to store, you'll reference this state for the latest copy of your data.
That's the general pattern for any kind of form entry component; it's an exercise in updating state.

reactjs with axios “Cannot read property 'map' of undefined`”

I was messing around using React, and I get this error while reading json from a local file localhost. I have checked other questions but have not gotten any result. i have install react dev tools from chrome app store and check error so i do not get cross origin error.
Here's the code:
class Content extends React.Component {
constructor(){
super();
this.state={
json: {
categories: []
}
};
}
componentWillMount(){
var _this = this;
var loc = window.location.pathname;
var dir = loc.substring(0, loc.lastIndexOf('/'));
this.serverRequest =
axios
.get(dir+"/data.json")
.then(function(result) {
// we got it!
_this.setState({
json:result
});
});
}
render() {
var title = <a>{this.state.json.title}</a>;
return (
<div>
<h2>Content</h2>
<h3>{title}</h3>
{this.state.json.categories.map(function(item) {
return (
<div key={item.categoryID} className="category">
<div> {item.categoryName} </div>
<div> {item.categoryDescridivtion} </div>
<div> {item.videosCount} </div>
</div>
);
})}
</div>
);
}
}
Here's the JSON:
{
"categories": [{
"categoryID": "294",
"parentID": "304",
"subjectID": "7",
"categoryName": "Apps and Side Dishes (Laura)",
"categoryDescription": "Learn to make amazing appetizers and side dishes with Laura in the Kitchen.",
"videosCount": "101",
"forumCategoryID": "163"
}, {
"categoryID": "285",
"parentID": "304",
"subjectID": "7",
"categoryName": "Side Dishes",
"categoryDescription": "Side dish recipes for salads, vegetables, sauces with Hilah cooking.",
"videosCount": "38",
"forumCategoryID": "163"
}, {
"categoryID": "337",
"parentID": "304",
"subjectID": "7",
"categoryName": "Side Dishes (bt)",
"categoryDescription": "Side dish recipes with Byron Talbott.",
"videosCount": "5",
"forumCategoryID": "163"
}, {
"categoryID": "301",
"parentID": "304",
"subjectID": "7",
"categoryName": "Side Dishes for Barbecue",
"categoryDescription": "Barbecue side dish recipes done on the grill by the BBQ Pit Boys!",
"videosCount": "43",
"forumCategoryID": "163"
}, {
"categoryID": "297",
"parentID": "304",
"subjectID": "7",
"categoryName": "Soups and Salads (Laura)",
"categoryDescription": "Looking for the perfect recipe to start your meal? Or are you looking to eat something on the lighter side? These are sure to have you covered!",
"videosCount": "70",
"forumCategoryID": "163"
}],
"title": "Title page"
}
here is the ouput from debug console regarding result from axios debug console:
Your screenshot from the console makes it clear why it isn't working: result doesn't have a categories property. It's result.data that has the categories, axios wraps the result in an envelope of sorts giving you information about the request (config, headers, status, etc.) and provides the actual data as data. So:
this.serverRequest =
axios
.get(dir+"/data.json")
.then(function(result) {
// we got it!
_this.setState({
json:result.data // ***
});
});
Example:
class Content extends React.Component {
constructor(){
super();
this.state={
json: {
categories: []
}
};
}
componentWillMount(){
var _this = this;
var loc = window.location.pathname;
var dir = loc.substring(0, loc.lastIndexOf('/'));
this.serverRequest =
axios
.get(dir+"/data.json")
.then(function(result) {
// we got it!
console.log(result); // So you can check it against your image
_this.setState({
json:result.data
});
});
}
render() {
var title = <a>{this.state.json.title}</a>;
return (
<div>
<h2>Content</h2>
<h3>{title}</h3>
{this.state.json.categories.map(function(item) {
return (
<div key={item.categoryID} className="category">
<div> {item.categoryName} </div>
<div> {item.categoryDescridivtion} </div>
<div> {item.videosCount} </div>
</div>
);
})}
</div>
);
}
}
const data = {
"config": {
"some": "stuff"
},
data: {
"categories": [{
"categoryID": "294",
"parentID": "304",
"subjectID": "7",
"categoryName": "Apps and Side Dishes (Laura)",
"categoryDescription": "Learn to make amazing appetizers and side dishes with Laura in the Kitchen.",
"videosCount": "101",
"forumCategoryID": "163"
}, {
"categoryID": "285",
"parentID": "304",
"subjectID": "7",
"categoryName": "Side Dishes",
"categoryDescription": "Side dish recipes for salads, vegetables, sauces with Hilah cooking.",
"videosCount": "38",
"forumCategoryID": "163"
}, {
"categoryID": "337",
"parentID": "304",
"subjectID": "7",
"categoryName": "Side Dishes (bt)",
"categoryDescription": "Side dish recipes with Byron Talbott.",
"videosCount": "5",
"forumCategoryID": "163"
}, {
"categoryID": "301",
"parentID": "304",
"subjectID": "7",
"categoryName": "Side Dishes for Barbecue",
"categoryDescription": "Barbecue side dish recipes done on the grill by the BBQ Pit Boys!",
"videosCount": "43",
"forumCategoryID": "163"
}, {
"categoryID": "297",
"parentID": "304",
"subjectID": "7",
"categoryName": "Soups and Salads (Laura)",
"categoryDescription": "Looking for the perfect recipe to start your meal? Or are you looking to eat something on the lighter side? These are sure to have you covered!",
"videosCount": "70",
"forumCategoryID": "163"
}],
"title": "Title page"
},
"headers": {
"some": "stuff"
}
};
const axios = {
get() {
return new Promise(resolve => {
setTimeout(resolve, 100, data);
});
}
};
ReactDOM.render(
<Content />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Categories