How to update JSON data using input fields created using myData.map? - javascript

The JSON data is used to create dynamic Input fields for each item in the array. I would like the JSON data to be updated to match the quantity selected but am unsure the best way to go about this?
I plan on using Hooks to initially store the number of items selected then update the JSON file with a button press, although I am very open to the JSON file updating onChange. what is the best practise for this can you dynamically create react hooks?
here is my current code(I want the quantity to update in the JSON file).
JSON:
//Json data for the shopping ingredients
export default [
{
bread: {
Quantity: 0,
},
Name: 'Bread',
Price: "1.10",
},
{
milk: {
Quantity: 0,
},
Name: 'Milk',
Price: "0.50",
},
{
cheese: {
Quantity: 0,
},
Name: 'Cheese',
Price: "0.90",
},
{
soup: {
Quantity: 0,
},
Name: 'Soup',
Price: "0.60",
},
{
butter: {
Quantity: 0,
},
Name: 'Butter',
Price: "1.20",
}
]
React:
import React, { useState, useEffect } from "react";
import Data from '../shoppingData/Ingredients';
const ShoppingPageOne = (props) => {
//element displays
const [pageone_show, setPageone_show] = useState("pageOne");
//updates quatity of ingredients
const [bread_quantity, setBread_quantity] = useState(0);
const [milk_quantity, setMilk_quantity] = useState(0);
const [cheese_quantity, setCheese_quantity] = useState(0);
const [soup_quantity, setSoup_quantity] = useState(0);
const [butter_quantity, setButter_quantity] = useState(0);
useEffect(() => {
//sets info text using Json
if (props.showOne) {
setPageone_show("pageOne");
} else {
setPageone_show("pageOne hide");
}
}, [props.showOne]);
return (
<div className={"Shopping_Content " + pageone_show}>
{Data.map((Ingredients) => {
return <div className="Shopping_input" key={Ingredients.Name}>
<p>{Ingredients.Name} £{Ingredients.Price}</p>
<input onChange={} type="number"></input>
</div>
})}
<div className="Shopping_Buttons">
<p onClick={props.next_ClickHandler}>Buy Now!</p>
</div>
</div>
);
};
export default ShoppingPageOne;
Having input fields generated dynamically from a JSON file is great but using static hooks to update the JSON seems rather silly.

To work with a file system you need to have particular modules. You can do it using NodeJS using 'fs' module https://nodejs.org/dist/latest-v15.x/docs/api/fs.html.
You need a separate endpoint that will be responsible for data updating that will be on the server-side.

Related

Working with state on recursive components

I'm writing a component that renders itself inside recursively and is data-driven
Attaching my sandbox snippet, as it will be easier to see there.
This is my data:
var builderStructureData = [
{
id: 123,
value: 3,
children: []
},
{
id: 345,
value: 5,
children: [
{
id: 4123,
value: 34,
children: [
{
id: 342342,
value: 33,
children: []
}
]
},
{
id: 340235,
value: 3431,
children: [
{
id: 342231342,
value: 3415,
children: []
}
]
}
]
}
];
and it renders like this:
This is my App.js:
import { useState } from "react";
import "./App.css";
import Group from "./components/group";
import builderStructureData from "./components/builderStructureData";
function App() {
const [builderStructure, setBuilderStructure] = useState(
builderStructureData
);
return (
<div className="App">
{builderStructure.map((x) => {
return <Group key={x.id} children={x.children} value={x.value} />;
})}
</div>
);
}
export default App;
And this is my recursive component:
import React from "react";
export default function Group(props) {
let childrenArray = [];
if (props.children) {
props.children.map((x) => childrenArray.push(x));
}
return (
<div className="group" draggable>
<p>this is value: </p>
<input value={props.value} readOnly={true}></input>
<button>Add Group</button>
{childrenArray.map((x) => {
return <Group key={x.id} children={x.children} value={x.value} />;
})}
</div>
);
}
I can render the components based on the data, and it seems to be handling recursion fine. I need to store the state on the App.js page and be able to change it from within child components. For example, if I update the "value" field of the component with ID = 342342, I want it to update that corresponding object in the state no matter how deeply nested it is, but not sure how to do that as it is not as simple as just passing a prop.
Am I taking the right approach with my code snippet? How can I do the state update?
I would advise the state normalization approach - here is an example for redux state - https://redux.js.org/usage/structuring-reducers/normalizing-state-shape - but you can use this approach with your state. So - your state will look like this:
state = {
items: {
[123]: {
id: 123,
value: 3,
childrenIds: []
},
[345]: {
id: 345,
value: 5,
childrenIds: [4123, 340235]
},
[4123]: {
id: 4123,
value: 34,
parentId: 345,
childrenIds: [342342]
},
[342342]: {
id: 342342,
value: 33,
parentId: 4123,
childrenIds: []
},
[340235]: {
id: 340235,
value: 3431,
parentId: 345,
childrenIds: [342231342]
},
[342231342]: {
id: 342231342,
value: 3415,
parentId: 340235
childrenIds: []
}
}
}
Here the field "childrenIds" is an optional denormalization for ease of use, if you want - you can do without this field. With this approach, there will be no problem updating the state.
You are thinking this in a wrong way, it should be very easy to do what you want.
The most imported thing is to make a little small changes in Group
Please have a look
import React from "react";
export default function Group(props) {
const [item, setItem] = React.useState(props.item);
let childrenArray = [];
if (item.children) {
item.children.map((x) => childrenArray.push(x));
}
const updateValue = ()=> {
// this will update the value of the current object
// no matter how deep its recrusive is and the update will also happen in APP.js
// now you should also use datacontext in app.js togather with state if you want to
// trigger somethings in app.js
item.value =props.item.value= 15254525;
setState({...item}) // update the state now
}
return (
<div className="group" draggable>
<p>this is value: </p>
<input value={item.value} readOnly={true}></input>
<button>Add Group</button>
{childrenArray.map((x) => {
return <Group item={x} />;
})}
</div>
);
}
The code above should make you understand how easy it is to think about this as an object instead of keys.
Hop this should make it easy for you to understand

Material-UI DataGrid/DataGridPro: How to persist state of columns through re-render, when you change their visibility with DataGrid Column Toolbar

We are using MUI DataGrid in our React based project.
At the moment I am trying to save/persist state of columns after toggling visibility of some columns with DataGrid's toolbar column menu, as currently after re-render it is back to default column setup.
I would like to know how could I access state of DataGrid/visibility state of columns in DataGrid so I can adjust/save it/reuse it later?
So far I meddled a bit with a apiRef, but all I got from apiRef.current was empty object. I am adding below some basic codeSandbox example to show how I tried to access it.
https://codesandbox.io/s/datagridprodemo-material-demo-forked-189j9?file=/demo.js
Maybe there is better/different approach, or I just need to create the state somehow. We would like to persist the state of the columns as user preference possibly in a future so this is vital for that to happen.
All suggestions are welcome and I thank you beforehand.
Fortunately, the DataGrid API provides the columnVisibilityModel and onColumnVisibilityChange props.
See this code sandbox for a simple example of controlling the columnVisibilityModel: https://codesandbox.io/s/mui-datagrid-oncolumnvisibilitychange-savestate-u1opzc?file=/src/App.tsx:1960-1984
Here is the code for a simple implementation. Your initial state may vary. Also, note that I could not figure out how to get DataGridPro to call onColumnVisibilityChange unless columnVisibilityModel was initially undefined. Bug, or my mistake, I am uncertain.
import "./styles.css";
import React from "react";
import {
DataGrid,
GridRowsProp,
GridColDef,
GridCallbackDetails,
MuiEvent,
GridColumnVisibilityModel,
GridColumnVisibilityChangeParams
} from "#mui/x-data-grid";
import { Button } from "#mui/material";
const rows: GridRowsProp = [
{ id: 1, col1: "Hello", col2: "World" },
{ id: 2, col1: "DataGridPro", col2: "is Awesome" },
{ id: 3, col1: "MUI", col2: "is Amazing" }
];
const columns: GridColDef[] = [
{ field: "col1", headerName: "Column 1", width: 150 },
{ field: "col2", headerName: "Column 2", width: 150 }
];
const initialVisibilityModel = { col1: true, col2: true };
export default function App() {
// it is strange, but in order for DataGridPro to call onColumnVisibilityChange, columnVisibilityModel must be undefined initially
const [
currentGridColumnVisibilityModel,
setCurrentGridColumnVisibilityModel
] = React.useState<GridColumnVisibilityModel | undefined>(undefined);
const [mySavedValue, setMySavedValue] = React.useState<
GridColumnVisibilityModel | undefined
>(undefined);
const onColumnVisibilityChange = React.useCallback(
(
params: GridColumnVisibilityChangeParams,
event: MuiEvent<{}>,
details: GridCallbackDetails
): void => {
console.log("params", params);
setCurrentGridColumnVisibilityModel((s) => ({
// per the DataGridPro strangeness, we must marry in initial state only the first update
...(s ? s : initialVisibilityModel),
[params.field]: params.isVisible
}));
},
[]
);
const saveACopyOfGridState = () => {
setMySavedValue(currentGridColumnVisibilityModel || initialVisibilityModel);
};
const loadSavedCopyOfGridState = () => {
setCurrentGridColumnVisibilityModel(mySavedValue || initialVisibilityModel);
};
const currentVisibilityAsText =
`${Object.keys(currentGridColumnVisibilityModel ?? {}).map(
(key) => `{${key}:${currentGridColumnVisibilityModel?.[key]}}`
)}` || "empty";
const savedVisibilityAsText =
`${Object.keys(mySavedValue ?? {}).map(
(key) => `{${key}:${mySavedValue?.[key]}}`
)}` || "empty";
return (
<div style={{ height: 300, width: "100%" }}>
<DataGrid
rows={rows}
columns={columns}
columnVisibilityModel={currentGridColumnVisibilityModel}
onColumnVisibilityChange={onColumnVisibilityChange}
/>
<div>
<Button onClick={saveACopyOfGridState} variant="contained">
SAVE CURRENT COLUMN VISIBILITY STATE
</Button>
<Button
onClick={loadSavedCopyOfGridState}
variant="contained"
color="warning"
>
LOAD SAVED COLUMN VISIBILITY STATE
</Button>
</div>
<p>Current filter state: {currentVisibilityAsText}</p>
<p>Saved filter state: {savedVisibilityAsText}</p>
</div>
);
}
I made a hook to persist column settings to localStorage. It uses callbacks on the API ref object. Usage:
function MyGrid() {
const apiRef = useGridApiRef()
usePersistColumnSettings(apiRef, 'customers-grid')
return <DataGrid apiRef={apiRef} />
}

How do I pass different values depending on the imported data in React?

I want to take data from js files classified as categories such as 'Delivery' and 'Cafe' and deliver different data to different pages.
I thought about how to import it using map(), but I keep getting errors such as 'products' is not defined.'
It must be done, but it is not implemented well with javascript and react weak. If you know how to do it, I'd appreciate it if you could let me know.
Products.js
export const Product = [
{
Delivery: [
{
id: '101',
productName: '허니랩',
summary: '밀랍으로 만든 친환경 식품포장랩 허니랩.',
description:
'~~',
images: ['3k7sH9F'],
companyName: '허니랩',
contact: '02-6082-2720',
email: 'lesslabs#naver.com',
url: 'https://honeywrap.co.kr/',
},
{
id: '102',
productName: '허니포켓',
summary: '밀랍으로 만든 친환경 식품포장랩 허니랩. 주머니형태.',
description:
"~~",
images: ['4zJEqwN'],
companyName: '허니랩',
contact: "02-6082-2720",
email: "lesslabs#naver.com",
url: "https://honeywrap.co.kr/",
},
],
},
{
HouseholdGoods: [
{
id: '201',
productName: '순둥이',
summary: '아기용 친환경 순한 물티슈',
description:
'~',
images: ['4QXJJaz'],
companyName: '수오미',
contact: '080-000-3706',
email: 'help#sumomi.co.kr',
url: 'https://www.suomi.co.kr/main/index.php',
},
{
id: '202',
category: ['HouseholdGoods'],
productName: '순둥이 데일리',
summary: '친환경 순한 물티슈',
description: '품질은 그대로이나 가격을 낮춘 경제적인 생활 물티슈',
images: ['OMplkd2'],
companyName: '수오미',
contact: '080-000-3706',
email: 'help#sumomi.co.kr',
url: 'https://www.suomi.co.kr/main/index.php',
},
],
},
];
Delivery.js
(The file was named temporarily because I did not know how to classify and deliver data without creating a js file separately.)
import React from "react";
function Delivery(
productName,
companyName,
contact,
email,
url,
summary,
description
) {
return (
<div className="Product">
<div className="Product__data">
<h3 className="Product__name">{productName}</h3>
<h4>{companyName}</h4>
<h5>Contact: {contact}</h5>
<h5>Email: {email}</h5>
<h5>URL: {url}</h5>
<p className="Product__summary">{summary}</p>
<p className="Proudct__descriptions">{description}</p>
</div>
</div>
);
}
export default Delivery;
Category.js
import React from "react";
import Delivery from "./Delivery";
import { Product } from "./Products";
class Category extends React.Component {
render() {
state = {
products: [],
};
this.setState(_renderProduct());
return <div>{products ? this._renderProduct() : "nothing"}</div>;
}
_renderProduct = () => {
const { products } = this.state;
const renderProducts = products.map((product, id) => {
return (
<Delivery
productName={Product.productName}
companyName={Product.companyName}
contact={Product.contact}
email={Product.email}
url={Product.url}
summary={Product.summary}
description={Product.description}
/>
);
});
};
}
export default Category;
Sorry and thank you for the long question.
There are quite a few different problems I've found.
First is that you call setState inside render in the Category component, this causes an infinite loop. Instead call setState inside a lifecycle method like componentDidMount or use the useEffect hook if using functional components.
Another problem is that state in Category is also defined inside render. In class components you would normally put this in a class constructor outside of render.
In your setState call you refer to _renderProduct(), this should be this._renderProduct() instead.
Now the main problem here is the structure of your data / how you render this structure.
Products is an array of objects where each object either has a Delivery or HouseholdGoods property which is an array of products. I would advise you to change this structure to something more like this:
export const Product = {
Delivery: [
{
id: "101",
},
{
id: "102",
},
],
HouseholdGoods: [
{
id: "201",
},
{
id: "202",
},
],
};
or this:
export const Product = [
{ id: "101", productType: "Delivery" },
{ id: "102", productType: "Delivery" },
{ id: "201", productType: "HouseholdGoods" },
{ id: "202", productType: "HouseholdGoods" },
];
I personally prefer the second structure, but I've implemented the first as this seems to be what you were going for:
class Category extends React.Component {
constructor(props) {
super(props);
this.state = {
products: null,
};
}
componentDidMount() {
this.setState({ products: Product });
}
render() {
const { products } = this.state;
return (
<div>
{products
? Object.keys(products).map((productKey) => {
return (
<div key={productKey}>
{products[productKey].map((product) => {
return (
<Delivery
key={product.id}
productName={product.productName}
companyName={product.companyName}
contact={product.contact}
email={product.email}
url={product.url}
summary={product.summary}
description={product.description}
/>
);
})}
</div>
);
})
: "no products"}
</div>
);
}
}
We need a nested loop here, because we need to map over each property key and over the array of objects inside each property. If you use the other structure for Product I've shown, you can simply map over Product without needing two loops.
Now the last important problem was that you weren't destructuring the props inside your Delivery component, instead you should do something like this:
function Delivery({
productName,
companyName,
contact,
email,
url,
summary,
description,
}) {
return (
<div className="Product">
<div className="Product__data">
<h3 className="Product__name">{productName}</h3>
<h4>{companyName}</h4>
<h5>Contact: {contact}</h5>
<h5>Email: {email}</h5>
<h5>URL: {url}</h5>
<p className="Product__summary">{summary}</p>
<p className="Proudct__descriptions">{description}</p>
</div>
</div>
);
}
Example Sandbox

ReactJS - LocalStorage, how do I format my localstorage

So I have an app that saves items in a list to another list that is then passed, using JSON, to the localstorage and then that localstorage information has to be retrieved from another page, the thing is that the information comes like this
{"name":"Red Dress","image":"/static/media/Dress.1c414114.png","Price":120,"id":1}
while I want it to come in a format that is like this
Name: "Red Dress",
Image: "/static/media/Dress.1c414114.png",
Price: 120,
id: 1
And the reason I want it to come like this is so that, later on, I could point to this data as {product.Name} and it would give me "Red Dress", {product.Price} and it would give me 120 etc.
Here's the corresponding code, that is where I set the localstorage and declare the useState that would keep the items and add them to a differnet list that is added to the localStorage:
const addItem=(item)=>
{
setProduct([...product, item])
localStorage.setItem('products', JSON.parse(product))
}
const[product, setProduct] = useState([])
const [item]=useState([
{
name: 'Blue Dress',
image: '/static/media/Dress.1c414114.png',
Price: 200,
id: 0
},
{
name: 'Red Dress',
image: '/static/media/Dress.1c414114.png',
Price: 120,
id: 1
},
])
And this is the corresponding code on the page that retrieves the data
const [product, getProduct]= useState([])
const getProducts=()=>
{
let X = JSON.stringify(localStorage.getItem('products'))
getProduct([...product, X])
}
Since I have forgotten to add my map function that shows the objects in question, here it is:
{product.map(item=>
<div>
<h3 className='productName'>{item.name}</h3>
<div className='productImageContainer'>
<img className='productImage'src={item.image}></img>
</div>
<span className='productPrice'><strong>${item.Price}.00</strong></span>
<Button className='removeProductButton' variant='danger'><MdCancel></MdCancel> Remove</Button>
<br></br>
</div>)}

How to continuously add objects to nested collection in Firestore

I am writing a React/Redux app that uses Firebase Auth/Firestore to keep track of a user's gym exercises. I have Redux Form to handle data submission and I have the below example data structure I want to achieve in Firestore:
users {
id {
name: 'John Smith'
uid: 'k1s7fxo9oe2ls9u' (comes from Firebase Auth)
exercises: {
{
name: 'Bench Press',
sets: 3,
reps: 10,
lbs: 100,
}
}
}
}
However, I can't figure out how to keep adding new exercise objects to the exercises subcollection (in Firestore I guess it would be a field type of map). What I want to do is have new objects in "exercises" as the user submits new forms. So for example, if the user wanted to add a Deadlift exercise, it would look like the below:
users {
id {
name: 'John Smith'
uid: 'k1s7fxo9oe2ls9u' (comes from Firebase Auth)
exercises: {
{
name: 'Bench Press',
sets: 3,
reps: 10,
lbs: 100,
},
{
name: 'Deadlift',
sets: 3,
reps: 12,
lbs: 120,
}
}
}
}
Calling db.collection('users').doc(doc.id).add({"exercises": values});
just updates the Bench Press object that's there already rather than adding a new one on submission of the form.
But calling db.collection('users').doc(doc.id).add({"exercises": values}); gives me this error: Uncaught (in promise) TypeError: _firebase__WEBPACK_IMPORTED_MODULE_3__.default.collection(...).doc(...).add is not a function.
I've been struggling with this for quite a while, any help is hugely appreciated.
This is my component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import db from '../../../firebase';
import '#firebase/firestore';
import { store } from '../../../App';
const formSubmit = (values)=> {
const currentUserId = store.getState().auth.uid;
db.collection("users").get().then((usersSnapshot) => {
usersSnapshot.forEach((doc) => {
// looking for the current user and then updating their data
if(doc.data().uid === currentUserId) {
db.collection('users').doc(doc.id).add({
"exercises": values,
});
}
});
});
}
let ExercisesForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit(formSubmit)}>
<div>
<Field name="name" component="input" type="text" placeholder="Exercise Name" />
</div>
<div>
<Field name="sets" component="input" type="number" />
<label htmlFor="sets"> sets</label>
</div>
<div>
<Field name="reps" component="input" type="number" />
<label htmlFor="reps"> reps</label>
</div>
<div>
<Field name="lbs" component="input" type="number" />
<label htmlFor="lbs"> lbs</label>
</div>
<button type="submit">Submit</button>
</form>
)
ExercisesForm = reduxForm({
form: 'exercise'
})(ExercisesForm)
const mapStateToProps = state => ({
uid: state.auth.uid,
});
export default connect(
mapStateToProps,
undefined
)(ExercisesForm);
You should be able to say:
db
.collection('users')
.doc(doc.id)
.collection('exercises')
.add(values);
Where values contains all the fields of the document you want to add. It will create a new document with a random id in the exercises subcollection.
So this won't work in the latest version of Firebase V9.
So below is how I added a subcollection to a document in the latest version:
const docRef = doc(database, "userDocs", session.user.email);
const colRef = collection(docRef, "docs");
await addDoc(colRef, {
fileName: input,
timestamp: serverTimestamp(),
})
.then(() => {
console.log("CREATED");
})
.catch((err) => {
console.error("Error creating document", err);
});
This will created a structure like
userDocs
-> userEmail
-> docs
-> uid
-> data

Categories

Resources