Rendering a box when submitting the data - javascript

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

Related

react - map is not a function

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()

How to update state of object

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
})

How can I update the state of an object nested in an array from a child component?

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>

How to re-render a list after deleting an element

I am trying to write a delete method in order to delete an element from a list, first of all I am not being able to write it in a setState function so I have it as a direct function call, How can I manage to signal a re-render after the direct function or manage to place the function in the setState method for automatic re-render?
class TASKMANAGER extends Component {
constructor(props){
super(props);
this.state= {
name: "",
description:"",
priority: "urgent",
tasklist: [],
}
this.handleTitleChange= this.handleTitleChange.bind(this);
//this.handleDescriptionChange= this.handleDescriptionChange.bind(this);
//this.handlePriorityChange= this.handleDescriptionChange.bind(this);
this.handleClick= this.handleClick.bind(this);
}
handleTitleChange = event => {
this.setState( {
name: event.target.value
})
};
handleDescriptionChange = event => {
this.setState({
description: event.target.value
})
};
handlePriorityChange = event => {
this.setState({
priority: event.target.value
})
};
handleClick = event => {
this.setState((state) => {
const tasklist = [
...state.tasklist,
[
state.name,
state.description,
state.priority
]
];
return {
tasklist
};
});
//console.log(this.state.tasklist);
};
handleDelete = index => {
this.setState(() => {
this.state.tasklist.splice(index, 1)
});
console.log(this.state.tasklist)
} THIS ONE IS THE FUNCTION I CANNOT SET TO WORK TO TRIGGER THE AUTO RE-RENDER
render() {
const task_item = this.state.tasklist.map((arr, index) => (
<li
key= {index}
className= 'task'>
Task: {arr[0]} <br />
Description: {arr[1]} <br />
Priority: {arr[2]} <br />
<div className='delete-button' onClick={
/*() => {this.state.tasklist.splice(index, 1);}*/ THIS ONE IS THE DIRECT FUNCTION THAT WORKS, BUT DOESN'T TRIGGER THE RE-RENDER, IT SHOWS WHEN I TYPE AGAIN ON THE INPUTS
this.handleDelete
}>delete</div>
</li>
))
return (
<div>
<div className= 'task-form'>
<form>
<div>
<label>Name your task!</label>
<input type= 'text' id='task-title' value={this.state.name} onChange={this.handleTitleChange} />
</div>
<div>
<label>Description?</label>
<textarea id='description' value={this.state.description} onChange={this.handleDescriptionChange}/>
</div>
<div>
<label>Priority?</label>
<select value={this.state.priority} onChange={this.handlePriorityChange}>
<option value='urgent'>Urgent</option>
<option value='regular'>Regular</option>
<option value='Can wait'>Can wait</option>
</select>
</div>
</form>
<button onClick={this.handleClick}>PRESS</button>
</div>
<div className='list-items'>
<ul className='list-render'>
{task_item}
</ul>
</div>
</div>
)
}}
export default TASKMANAGER
You shouldn't be making any mutations to the current state, but instead build a new state from the existing state, generating a new, filtered array along the way
handleDelete = index => {
this.setState((state) => ({
...state,
tasklist: state.taskList.filter((_,i) => i != index)
}));
}
When you map your taskList to JSX below, you will need to avoid using the index of the item as key, because the optimizations react makes using the key value will be operating under broken assumptions. Use a key value that remains constant and unique per item. Perhaps its name, or an identifier that is attached to it when created.
There is no need to assign the list. Just splice it. Use something like this to change the state:
delete={()=>{this.setState({phase:1-this.state.phase});
this.state.list.splice(index,1)}}

How to remove key from my input upon input reset?

I have a search form with multiple inputs.
The form has a reset button to start a new search. Currently I have it working so that the value gets cleared from the state. The problem is that the key value is not being removed and so the input is being included in the new search with just an empty key. This is causing the search to include empty keys as part of the query string.
For example. This is a query string with the added empty key:
http://api/ixmasterdocument?filter=IDXT002|&filter=IDXT001|1111
As you can see the filter=IDXT002| is empty and being included in query string with the correct key value pair filter=IDXT001|1111
Here is my reset method that clears the key value from state.
clear = () => {
let emptyValues = JSON.parse(JSON.stringify(this.state.formValues))
Object.keys(emptyValues).forEach(key => emptyValues[key] = "")
this.setState({
formValues: emptyValues,
contracts:[],
})
}
Here is my inputchange method..
handleInputChange = (e) => {
console.log("==handleInputChange==")
let newValues = JSON.parse(JSON.stringify(this.state.formValues))
newValues[e.target.name] = e.target.value
this.setState({
formValues: newValues
})
console.log("newFormValues is: " + JSON.stringify(newValues))
}
Here is the submit method..
handleFormSubmit = event => {
event.preventDefault();
const formData = this.state.formValues
let query = '';
let keys = Object.keys(formData);
keys.forEach(k => {
if (query !== "")
query += `&`;
query += `filter=`
query += `${k}|${formData[k]}`
})
return this.loadContracts(query);
};
Here is the input component with reset button..
<form className="form-inline col-md-12" onReset={this.props.handleFormReset}>
{this.props.labels.map(label => (
<div className="card border-0 mx-auto" style={styles} key={label.Id}>
<ul className="list-inline ">
<span>
<li>
<Labels htmlFor={label.DisplayName} >{label.DisplayName}:</Labels>
</li>
<li >
<div>
<Input
key={label.Id}
onChange={this.props.handleInputChange}
value={this.props.formValues[label.DataField] ||""}
type="search"
maxLength="999"
style={{height:34}}
name={label.DataField ||""}
className={"form-control mb-2 mr-sm-2"}
id={label.DataField}
/>
State: {this.props.formValues[label.DataField]}
</div>
</li>
</span>
</ul>
</div>
))}
<div className=" col-sm-12">
<Button
style={{ float: "left", marginBottom: 10 }}
className="btn btn-success"
type="submit"
onClick={this.props.handleFormSubmit}
>
Search
</Button>
<Help />
<Button
style={{ float: "left", marginBottom: 10 }}
className="btn btn-secondary"
// type="reset"
onClick={this.props.clear}
>
Reset
</Button>
</div>
</form>
Since You want to ignore/skip the key-value pairs in api call where value='', so put the check in handleSubmit function and include only non-empty values.
Like this:
handleFormSubmit = event => {
event.preventDefault();
const formData = this.state.formValues
let query = '';
let keys = Object.keys(formData);
keys.forEach(k => {
// here
if(formData[k]) {
if (query !== "")
query += `&`;
query += `filter=`
query += `${k}|${formData[k]}`
}
})
return this.loadContracts(query);
};
Or another possible way can be, setting formValues as {} in clear method. You are only clearing values not keys from that object, if you reset the variable then only new key-value will be available.
Like this:
clear = () => {
this.setState({
formValues: {},
contracts:[],
})
}

Categories

Resources