I created front-end for json and now I want to create CMS to add new things to database.
My problem start with update state because something not work proper.
const [object, setObject] = useState([{ name: "",
Description: "",
price: {
A: "",
B: "",
C: "", }}])
const changeIloscSztuk = (event, index) => {
const { name, value } = e.target;
const tempProducts = object.map((el, i) => {
if(i === index) {
return {
...el,
[name]: value
};
}
return el;
});
setObject(tempProducts)
}
const handleChange = (e, index) => {
const { name, value } = e.target;
const tempProducts = object.map((el, i) => {
if(i === index) {
return {
...el,
[name]: value
};
}
return el;
});
setObject(tempProducts);
};
return (
<>
<Container style={{backgroundColor: "black", color: "white"}}>
{object.map((name,i)=>{
return(<>
{name.name}
</>)
})}
</Container>
<form onSubmit={setDodatkowe} ref={formData}>
Dodaj nowy moduł
<input type="submit" value="dodaj" />
</form>
{object.length>0 ?
<form ref={formData}>
<Table responsive striped bordered hover >
<thead><tr><td>Nazwa usługi</td><td>usun</td></tr></thead>
<tbody>
{object.map((props, index) => (
<React.Fragment key={index}>
<tr>
<tr>
<td>Nazwa <input type="text" className="Nazwa" name="name" onChange={handleChange} /> </td>
<td> Opis<input type="text" className="Description" name="Description" onChange={handleChange}/> </td>
<td>Wielkość <input type="text" className="Wielkosc" name="Wielkosc" onChange={handleChange}/> </td></tr>
</React.Fragment>
))}
</tbody>
</Table>
</form> : ""}
</Container>
after click "dodaj" I see lots of inputs so it is correct. But when I write in field "name" something the state is not updating. Without this step building database is not possible.
The reason for that is that state variables in React are immutable. You cannot simply do object.description = "something". If you insist on having one big nested object, you need to make a copy of the entire object whenever a single property changes.
A common way to do so is
const newObject = JSON.parse(JSON.stringify(object);
// make modifications, for example:
newObject.Description = "something";
setObject(newObject);
React UseStates are immutable. Bit of a pain, but you can use Immer to get rid of this.
npm i immer
Example of immer:
import produce from "immer"
const nextState = produce(baseState, draft => {
draft[1].done = true
draft.push({title: "Tweet about it"})
//draft is muttable
})
Related
so i need to uplift state from SingleRowComponent to UpdateMessageBoxComponent, in which i want to update the payload, which looks like this
[
{
"id":80,
"title":"nowe",
"content": [
{
"id":159,
"checked":true,
"content":"cwelowe"
},
{
"id":160,
"checked":false,
"content":"guwno"
},
{
"id":161,
"checked":true,
"content":"jeabne"
}
]
}
]
i want to achieve that by adding a count prop in box.content.map()
export default function UpdateMessageBoxComponent(props) {
let [state, setState] = useState([])
useEffect(() => {
console.log(props.id);
RestService.getMessage(props.id).then(res => {
setState(res.data)
})
}, [props])
function handleRowState(i, data) {
let copy = state
copy[i] = data
setState(copy)
}
return (
state.map(box => (
<div key={box.id}>
<div>
<label htmlFor="title">Podaj nazwę</label>
<input type="text" name="title" id="title" value={box.title}
onChange={e => setState([{ title: e.target.value, id: box.id, content: box.content }])} />
</div>
<div className="additional">
{
----------------- box.content.map((row, idx) => ( // THIS ONE HERE -------------------------
<SingleRowComponent key={row.id} count={idx} onRowChange={handleRowState} state={row}/>
))
}
</div>
<div className="buttons">
<button onClick={() => {console.log(state); RestService.updateMessage(box.id, state) }}>add</button>
<button onClick={() => {
}}>finish</button>
</div>
</div>
))
)
}
the issue is that an box.content.map is not a function error is thrown, when i don't use the count prop, everything works fine (except the fact that i don't actually update the state in the payload). i don't understand what's the issue here
function SingleRowComponent(props) {
let payload;
if (typeof props.state !== 'undefined')
payload = {id:props.state.id, content:props.state.content, checked: props.state.checked}
else
payload = {content:'', checked: false}
let [values, setValues] = useState(payload)
useEffect(() => {
props.onRowChange(props.count, values)
})
//uplifting state
function changeCheckedHandler(e) {
setValues({content:values.content,checked:e.target.checked})
props.onRowChange(props.count, values)
}
function changeContentHandler(e) {
setValues({content:e.target.value, checked:values.checked})
props.onRowChange(props.count, values)
}
return (
<div key={props.count}>
<div>
<label htmlFor="content">Podaj treść</label>
<textarea name="content" id="content" cols="30" rows="10" value={values.content} onChange={changeContentHandler}></textarea>
</div>
<div>
<label htmlFor="checked">checked</label>
<input type="checkbox" name="checked" id="checked" checked={values.checked} onChange={changeCheckedHandler}/>
</div>
</div>
)
}
EDIT 1
i noticed that box after a few iterations becomes what row should be, hence the errors
the issue was the handleRowState function inside UpdateMessageBoxComponent wrongfully assigning the inner content from single row as the entire state
not working
function handleRowState(i, data) {
let copy = state
copy[i] = data
setState(copy)
}
solution
function handleRowState(i, data) {
let copy = state
copy[0][i] = data // <--------- here, the updated state is a 2-dimensional array
setState(copy)
}
You have your object wrapped in an Array, you need to call
box[0].content.map()
I have RecipeCreate component, and inside of that I want a user to be able to render as many IngredientDetailsInput components as needed to complete the recipe.
I do this by creating an empty object in an ingredients array within RecipeCreate, and then iterate over this array of empty objects, generating a corresponding IngredientDetailsInput for each empty object.
From within IngredientDetailsInput I want to update the empty corresponding empty object in RecipeCreate with data passed up from IngredientDetailsInput. Since IngredientDetailsInput has the index of where it's object lives in the ingredients array in it's parent component, I believe this is possible.
Here is working sandbox that demonstrates the issue
I'm close, but each time the handleChange runs it is creating a new object in the ingredients array and I'm not sure why, or what other options to use besides handleChange - I'd like there not to have to be a form submit if possiblee
And here is code for both components
import React, { useState } from "react";
const RecipeCreate = (props) => {
const [ingredients, setIngredients] = useState([]);
const [recipeTitle, setRecipeTitle] = useState("");
//if an ingredient object has been added to the ingredients array
//render an IngredientDetailsInput component, passing along the index position
//so we can update it later
const renderIngredientComponents = () => {
if (ingredients) {
return ingredients.map((_, index) => {
return (
<IngredientDetailsInput
key={index}
position={index}
updateIngredientArray={updateIngredientArray}
/>
);
});
}
};
//broken function that should find the object position in ingredients
//and copy it, and non-mutated ingredient objects to a new object, and set the state to this
//new object
const updateIngredientArray = (key, value, position) => {
return setIngredients((prevIngredients) => {
console.log(ingredients)
return [...prevIngredients, prevIngredients[position][key] = value]
});
};
//allows the user to add another "ingredient", rendering a new IngredientDetailsInput component
//does so by adding a new, empty object to the ingredients array
const addElementToArray = () => {
setIngredients((prevIngredients) => [...prevIngredients, {}]);
};
return (
<div>
<div>
<form>
<div>
<label>Recipe Title</label>
<input
type="text"
name="recipeTitle"
value={recipeTitle}
onChange={(e) => setRecipeTitle(e.target.value)}
/>
</div>
<div>
<p>Ingredients</p>
{renderIngredientComponents()}
<div>
<p onClick={() => addElementToArray()}>+ ingredient</p>
</div>
</div>
<div></div>
<button type="submit">Submit</button>
</form>
</div>
</div>
);
};
export default RecipeCreate;
//child component that should allow changes to bubble up to RecipeCreate
export function IngredientDetailsInput(props) {
return (
<div>
<input
type="number"
name="measurement"
id="measurement"
placeholder="1.25"
onChange={(e) =>
props.updateIngredientArray(
"measurement",
e.target.value,
props.position
)
}
/>
<div>
<label htmlFor="measurementType">type</label>
<select
id="unitType"
name="unitType"
onChange={(e) =>
props.updateIngredientArray(
"unitType",
e.target.value,
props.position
)
}
>
<option>tbsp</option>
<option>cup</option>
<option>tspn</option>
<option>pinch</option>
<option>ml</option>
<option>g</option>
<option>whole</option>
</select>
</div>
<input
type="text"
name="ingredientName"
id="ingredientName"
placeholder="ingredient name"
onChange={(e) =>
props.updateIngredientArray(
"ingredientName",
e.target.value,
props.position
)
}
/>
</div>
);
}
The assignment prevIngredients[position][key] = value returns value instead of prevIngredients[position][key]. Thus when you setting the state, it returns the previous stored ingredients as well as that value.
const updateIngredientArray = (key, value, position) => {
return setIngredients((prevIngredients) => {
console.log(ingredients)
return [...prevIngredients, prevIngredients[position][key] = value]
});
};
A quick fix would be to recopy a new array of the current ingredient, then changing the position and key that you want.
const updateIngredientArray = (key, value, position) => {
const tmp = ingredients.map((l) => Object.assign({}, l));
tmp[position][key] = value;
setIngredients(tmp);
};
May be you can try like this?
const {useState} = React;
const App = () => {
const [state, setState] = useState([
{
name: "",
amount: "",
type: ""
}
]);
const addMore = () => {
setState([
...state,
{
name: "",
amount: "",
type: ""
}
]);
};
return (
<div className="App">
<h1>Recipe</h1>
<h2>Start editing to see some magic happen!</h2>
<label>Recipe Title</label>
<input type="text" />
<br /> <br />
<div onClick={addMore}>Add More +</div>
{state && state.map((val, ikey) =>
<div>
<br />
<label>Ingredients</label>
<input type="text" placeholder="Name" />
<input type="text" placeholder="Amount" />
<select>
<option>tbsp</option>
<option>cup</option>
<option>tspn</option>
<option>pinch</option>
<option>ml</option>
<option>g</option>
<option>whole</option>
</select>
</div>
)}
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I have the following code in my React:
const [property, setProperty] = useState([]);
const [state, setState] = React.useState({ type: "", propertyName: "" });
const handleChange = (e, inputField) => {
setState((prevState) => ({
...prevState,
[inputField]: e.target.value,
}));
};
const handleSubmit = () => {
if (state.type !== "" && state.propertyName !== "") {
const newObject = { type: state.type, propertyName: state.propertyName };
property.push(newObject);
console.log(property);
setState({
type: "",
propertyName: "",
});
}
};
And html:
<div>
<label htmlFor='properties' className='properties-label'>
Properties
</label>
<div className='property-box'>
<input
type='text'
id='type'
value={state.type}
placeholder='Type'
className='type-element'
required
onChange={(e) => handleChange(e, "type")}
></input>
<input
type='text'
id='name'
value={state.propertyName}
className='type-element'
placeholder='Name'
required
onChange={(e) => handleChange(e, "propertyName")}
></input>
<button
className='buttonAccount'
type='submit'
onClick={handleSubmit}
>
Add Property
</button>
</div>
</div>
What I want is when I press the add Property button a new html tag will render on the page(a box or something like that containing the two fields that has been inputted). Can you help me find a way to do that?
You have to print the elements in your property array. For exmaple:
{
property.map((element) => (
<div key={element.propertyName}>
<span>
{element.type}
</span>
<span>
{element.propertyName}
</span>
</div>
)
}
You can use the Javascript array map method to map each item in your property state into an HTML element.
For example:
Make a function that returns the mapped property state into HTML elements.
const renderProperties = () => {
return property.map((item, index) => (
// `item` is a representation of each of your object in the property array
// In this case, item contains { type: string, propertyName: string }
<div key={index}> // React requires user to put a key in each of the mapped component
<p>{item.propertyName}</p>
<p>{item.type}</p>
</div>
))
}
Call this function inside the HTML part of your JSX.
...
<button
className='buttonAccount'
type='submit'
onClick={handleSubmit}
>
Add Property
</button>
</div>
{renderProperties()} // <-- Here
</div>
https://reactjs.org/docs/lists-and-keys.html
sorry bug again. im new to reactjs, i try to implement functional component and have problem with push a data inside object. i have 2 input tag and everytime i fill a value inside and switch to another the other show undefined. im not sure what is happening in here. help me explain what happen and how to solve it. please advise , thank you so much. below here i put a picture and my code.
my issue
function App() {
const [heldItems, setHeldItems] = useState({
salesno: '',
plu: '',
price: '',
dateandtime: '',
});
const [edit, setEdit] = useState({});
const [salesItemsTemp, setSalesItemsTemp] = useState([]);
const handlerOnEdit = (heldItemsData) => {
console.log(heldItemsData);
setHeldItems(heldItemsData);
setEdit(heldItemsData);
};
const handlerOnChange = (e, type) => {
setHeldItems({
[type]: e.target.value,
});
};
useEffect(() => console.log(heldItems));
const handlerOnSubmit = (e) => {
e.preventDefault();
const data = {
salesno: uniqid(),
plu: heldItems.plu,
price: heldItems.price,
dateandtime: new Date().toLocaleString(),
};
console.log(data);
};
const handlerRemove = (heldItemsSalesNo) => {
let filteredSalesItemsTemp = salesItemsTemp.filter(
(item) => {
return item.salesno !== heldItemsSalesNo;
},
);
setSalesItemsTemp(filteredSalesItemsTemp);
};
return (
<>
<form onSubmit={handlerOnSubmit} autoComplete="off">
<h1>GoGreen Point Of Sales</h1>
<input
type="text"
placeholder="Input item name"
name="plu"
onChange={(e) => handlerOnChange(e, 'plu')}
value={heldItems.plu}
/>
PLU
<input
type="number"
placeholder="Input item price"
name="price"
onChange={(e) => handlerOnChange(e, 'price')}
value={heldItems.price}
/>
Price
<button type="submit">
{edit.salesno ? 'Save Edit Item' : 'Save Item'}
</button>
<div>
<table>
<caption>Sales</caption>
<thead>
<tr>
<th>SalesNo</th>
<th>PLUName</th>
<th>Price</th>
<th>Date & Time</th>
<th>Void</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
{salesItemsTemp.map((sales) => {
const { salesno, plu, price, dateandtime } =
sales;
return (
<tr key={salesno}>
<td>{salesno}</td>
<td>{plu}</td>
<td>{price}</td>
<td>{dateandtime}</td>
<td>
<button
type="button"
onClick={() =>
handlerRemove(salesno)
}>
X
</button>
</td>
<td>
<button
type="button"
onClick={() =>
handlerOnEdit(sales)
}>
Edit
</button>
</td>
</tr>
);
})}
</tbody>
<tfoot>
<tr>
<td>brought to you by ...</td>
</tr>
</tfoot>
</table>
</div>
</form>
</>
);
You are replacing the complete object. The following may help:
const handlerOnChange = (e, type) => {
setHeldItems((prevValue)=>({...prevValue,
[type]: e.target.value,
}));
};
In your handlerOnChange you're replacing the previous state with a new object which has only one property, so you've lost your previous state. To fix it use handlerOnChange like this:
const handlerOnChange = (e, type) => {
setHeldItems(prevState => ({
...prevState,
[type]: e.target.value,
}));
};
The issue is that inside your handlerOnChnage method you replace the previous state, with new state and thus the previous state is lost!! Yes all those 4 types are lost and you are left with just one!!
You wanna preserve the previous state, sure? Then you can do that too..
Never Do this
You might find some solution like :
const handlerOnChange = (e, type) => {
setHeldItems({
...heldItems,
[type]: e.target.value,
});
};
Since the set state is asynchronous you can't expect the state to be updated just after setHelditems gets executed. Want more detail on it ? Visit
More clean solution
Do we have a proper solution? Yes and here it is : Use a callback function and update the state with the use of that
const handlerOnChange = (e, type) => {
setHeldItems(prevSnapshot=>({...prenSnapshot,
[type]: e.target.value,
}));
};
Helpful link
I am new to React, there are two input fields in the application, one is for ID and another for Name, There are two components I've used, in the parent component I've maintained all the state and form in separate another component. My aim is to check the id which is a input from the user, id should be unique every time, if it's same, an alert should popup and the focus turns to ID input field, and it should do the same until the ID is different from all the objects(state object)
My app.js file is,
import React, { Component } from "react";
import Form from "./Form";
export default class App extends Component {
state = {
names: [
/*
{id: 1,name: "Aashiq"}
*/
],
};
renderTable() {
return this.state.names.map((eachName) => {
const { id, name } = eachName;
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>
<input
type="button"
value="Delete"
onClick={() => this.deleteName(eachName.id)}
/>
</td>
</tr>
);
});
}
deleteName = (id) => {
console.log("ID object", id);
this.state.names &&
this.setState({
names: this.state.names.filter((name) => name.id !== id),
});
};
addName = (newName) => {
this.setState({
names: [newName, ...this.state.names],
});
};
render() {
return (
<>
<Form onSubmit={this.addName} names={this.state.names} />
{/* Table */}
<br />
<table id="details">
<tbody>
<tr>
<th>ID</th>
<th>Names</th>
<th>Operation</th>
</tr>
{/* Render dynamic rows
*/}
{this.renderTable()}
</tbody>
</table>
</>
);
}
}
You can see I try to render the data as table and we can delete the row data also
The form.js file is,
import React, { useState } from "react";
// import { uniqueId } from "lodash";
export default function Form(props) {
const [name, setName] = useState("");
const [id, setId] = useState();
const handleSubmit = (e) => {
e.preventDefault();
handleChangeandValidate();
};
const handleChangeandValidate = () => {
const { onSubmit, names } = props;
console.log("Object keys length", Object.keys(names).length);
if (Object.keys(names).length !== 0) {
names.map((name) => {
if (name.id === id) {
alert("Enter unique id");
setId("");
document.getElementById("ID").focus();
} else {
//if different id
onSubmit({ id: id, name: name });
setName("");
setId("");
}
return null;
});
} else {
onSubmit({ id: id, name: name }); // first time
setName("");
setId("");
}
};
return (
<form onSubmit={handleSubmit} id="myform">
<label style={{ fontSize: "20px", fontWeight: "bold" }}>
Name: {""}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</label>{" "}
<label style={{ fontSize: "20px", fontWeight: "bold" }}>
ID: {""}
<input
type="number"
onChange={(e) => setId(e.target.value)}
required
value={id}
id="ID"
/>
</label>
{""}
<input type="submit" value="Submit" />
</form>
);
}
You can see I've tried to get the state and onSubmit function from the parent component(app.js) and done some logic like comparing all the ID's, but this logic throws some error, please somebody come up with a good solution.
Thanks in advance!
I have modified your code a bit and here is a working example.
Here is what I did:
I used createRef() to create two references that refer to each input field named nameInputRef and idInputRef.
I added ref={nameInputRef} and ref={idInputRef} so that we can get their values on submit.
On submit, I get the values of the name + id using their refs.
to search for whether the ID exists or not, I used Array.find() which would return undefined if the same id doesn't exist in the list of names coming from the props.
in addName(), I used setState() but in the param I used a function to make sure I get the latest list of names as updating the state is asynchronous. Inside I also used ES6's destructuring feature to make a copy of the current list, push the new name to it and then update the state with the new list of names.