I'm trying to make an comment input from map,
but since I use the same useState all the input fields get changed.
How can I target a specific input?
return (
<div>
{posts.map(post => (
<div key={post.id}>
<img
src={`https://localhost:1111/api/posts/uploads/images/${post.content}`}
alt={`${post.id}`}
/>
<p>{post.description}</p>
<span>{post.likes ? post.likes : 0}</span>
<button onClick={() => like(post.id)}>Like</button>
<Link to={`/post/${post.id}`}>Edit</Link>
<button onClick={() => deletePost(post.id)}>Delete</button>
<form onSubmit={uploadComment}>
<input
type="text"
onChange={handleComment}
value={comment}
placeholder="Comment"
/>
</form>
</div>
))}
</div>
)
You have an own state per rendered post, which means that it is a use case for an own component:
function Post(post, deletePost) {
const [comment, setComment] = useState('');
const uploadComment = () => {}; // your code is missing
return (
<div key={post.id}>
<img
src={`https://localhost:1111/api/posts/uploads/images/${post.content}`}
alt={`${post.id}`}
/>
<p>{post.description}</p>
<span>{post.likes ? post.likes : 0}</span>
<button onClick={() => like(post.id)}>Like</button>
<Link to={`/post/${post.id}`}>Edit</Link>
<button onClick={() => deletePost(post.id)}>Delete</button>
<form onSubmit={uploadComment}>
<input
type="text"
onChange={e => setComment(e.target.value)}
value={comment}
placeholder="Comment"
/>
</form>
</div>
)
}
Then your render function would look like this:
return (
<div>
{posts.map(post => <Post post={post} deletePost={deletePost} />)}
</div>
)
Consider using react useState hook per input.
Related
I am stuck on editing user details and updating it.
What I'm trying to accomplish is when I click on "update" button, I can edit their name and email. And I can also delete the entry by clicking the "delete" button.
I have added the ability to add new User which I am able to code.
This is my code: https://codesandbox.io/s/zen-browser-5ifrel?file=/src/User.js
How do I add a if-else statement in my render so that IF i click on "update" button on one of the entry (e.g., id === selected.id), it will transform the "name" and "email" to textboxes to allow for edit. And 2 buttons for them to "confirm" their update or "cancel" their update.
render() {
return (
<div className="App">
{
this.state.users.map((u) => {
return (
<React.Fragment key={u._id}>
<div className="box">
<h3>{u.name}</h3>
<h4>{u.email}</h4>
<button onClick={() => {
this.beginEdit(u);
}}
>Update
</button>
<button onClick={() => {
this.deleteUser(u);
}}
>Delete
</button>
</div>
</React.Fragment>
);
})}
{this.renderAddUser()}
</div>
);
}
Use conditional rendering.
render() {
return (
<div className="App">
{
this.state.users.map((u) => {
return (
<React.Fragment key={u._id}>
<div className="box">
{u.isEditing ? (
<React.Fragment>
<input type="text" placeholder="name" />
<input type="text" placeholder="email" />
</React.Fragment>
) : (
<React.Fragment>
<h3>{u.name}</h3>
<h4>{u.email}</h4>
</React.Fragment>
)}
<button onClick={() => {
this.beginEdit(u);
}}
>Update
</button>
<button onClick={() => {
this.deleteUser(u);
}}
>Delete
</button>
</div>
</React.Fragment>
);
})}
{this.renderAddUser()}
</div>
);
}
You can probably figure out the rest.
you can use this that render output depends on the condition of (id === selectedUser.id)
state = {
selected = null;
}
render() {
return (
<div className="App">
{this.state.users.map((user) => {
return (
<div className="box" >
{selected?.id === user?._id ? (
<>
<input type="text" placeholder="name" defaultValue={user.name} />
<input type="text" placeholder="email" defaultValue={user.email} />
<button onClick={() => { this.handleCancel(user);}}>
Cancel
</button>
<button onClick={() => { this.handleConfirm(user);}}>
Confirm
</button>
</>
) : (
<>
<h3>{user.name}</h3>
<h4>{user.email}</h4>
<button onClick={() => { this.beginEdit(user);}}>
Update
</button>
<button onClick={() => { this.deleteUser(user);}}>
Delete
</button>
</>
)}
</div>
);
})}
{this.renderAddUser()}
</div>
);
}
Consider the code below:
function Item({name, _key})
{
console.log('rendering Item')
const [updatingName, setUpdatingName] = useState(false);
const nameInputElement = useRef();
useEffect(() => {
if (updatingName) {
nameInputElement.current.focus();
}
}, [updatingName]);
function onUpdateClick() {
setUpdatingName(true);
}
function onCancelClick() {
setUpdatingName(false);
}
return (
<div>
<input ref={nameInputElement} type="text" defaultValue={name} name="name"
disabled={!updatingName} />
{!updatingName
? <>
<button key={1} type="button" onClick={onUpdateClick}>Update</button>
<button key={2} type="submit" name="delete" value={_key}>Remove</button>
</>
: <>
<button key={3} type="submit" name="update" onClick={(e) => {setUpdatingName(false)}}>Save</button>
<button key={4} type="button" onClick={onCancelClick}>Cancel</button>
</>}
</div>
)
}
function ItemList({title})
{
return <>
<h1>{title}</h1>
<form method="post" onSubmit={(e) => {console.log('submitting');e.preventDefault()}}>
<Item name={'small'} _key={0} />
</form>
</>
}
export default ItemList;
The problem that I am facing is the click of the Save button. When it's clicked, as you can see, I trigger a state change. But at the same time, I also want the button to cause the underlying <form>'s submission.
(To check whether the form is submitted, I've prevented its default submit mechanism and instead gone with a simple log.)
However, it seems to be the case that when the state change is performed from within the onClick handler of the Save button, it ceases to submit the form. If I remove the state change from within the handler, it then does submit the form.
Why is this happening?
Live CodeSandbox demo
When you call setUpdatingName(false) in save button's click handler, the button is removed from the DOM before submitting. You can add the logic for showing the buttons in ItemList, like below:
function ItemList({ title }) {
const [updatingName, setUpdatingName] = useState(false);
return (
<>
<h1>{title}</h1>
<form
method="post"
onSubmit={(e) => {
e.preventDefault();
setUpdatingName(false);
console.log("submitting");
}}
>
<Item
name={"small"}
_key={0}
updatingName={updatingName}
setUpdatingName={setUpdatingName}
/>
</form>
</>
);
}
export default ItemList;
function Item({ name, _key, updatingName, setUpdatingName }) {
console.log("rendering Item");
const nameInputElement = useRef();
useEffect(() => {
if (updatingName) {
nameInputElement.current.focus();
}
}, [updatingName]);
function onUpdateClick() {
setUpdatingName(true);
}
function onCancelClick() {
setUpdatingName(false);
}
return (
<div>
<input
ref={nameInputElement}
type="text"
defaultValue={name}
name="name"
disabled={!updatingName}
/>
{!updatingName ? (
<>
<button key={1} type="button" onClick={onUpdateClick}>
Update
</button>
<button key={2} type="submit" name="delete" value={_key}>
Remove
</button>
</>
) : (
<>
<button key={3} type="submit" name="update">
Save
</button>
<button key={4} type="button" onClick={onCancelClick}>
Cancel
</button>
</>
)}
</div>
);
}
Also, you could use useTransition to ask React to delay the state update, so the submission happens first:
function Item({ name, _key }) {
console.log("rendering Item");
const [isPending, startTransition] = useTransition();
const [updatingName, setUpdatingName] = useState(false);
const nameInputElement = useRef();
useEffect(() => {
if (updatingName) {
nameInputElement.current.focus();
}
}, [updatingName]);
function onUpdateClick() {
setUpdatingName(true);
}
function onCancelClick() {
setUpdatingName(false);
}
return (
<div>
<input
ref={nameInputElement}
type="text"
defaultValue={name}
name="name"
disabled={!updatingName}
/>
{!updatingName ? (
<>
<button key={1} type="button" onClick={onUpdateClick}>
Update
</button>
<button key={2} type="submit" name="delete" value={_key}>
Remove
</button>
</>
) : (
<>
<button
key={3}
type="submit"
name="update"
onClick={(e) => {
startTransition(() => setUpdatingName(false));
}}
>
Save
</button>
<button key={4} type="button" onClick={onCancelClick}>
Cancel
</button>
</>
)}
</div>
);
}
i try make my first App in react, everything works, but i try take amoun of bill and number of people and divide te bill for person, and add Tip. I know for somebody probably its 5 min solution but i start working with that yesterday and still can't find solution.
I wanna take bill amount and divide by personAmount + tip if somebody choose tip. What i do wrong
import React, { useState } from 'react'
import style from './BillSplitter.module.css'
const tips = [5, 10, 15, 25]
const Input = ({ label, id, handleChange, name, type, placeholder }) => (
<>
<label className={`${style.label}`} htmlFor={id}>{label}</label>
<input className={`${style.input}`} type={type || "number"} id={id} name={name || id} placeholder={placeholder} onChange={handleChange}></input>
<br />
</>
);
const SelectTip = ({ tip }) => {
<>
<button className='tipButton' value={tip}></button>
</>
}
function BillSplitter() {
const [tipAmount, setTipAmount] = useState(0)
const [billAmount, setBillAmount] = useState(0)
const [personAmount, setPersonAmount] = useState(0)
function handleChange(e) {
console.log(e.target.value)
}
return (
<div className={`${style.wrapper}`}>
<div className={`${style.box}`}>
<div className={`${style.top}`}>
<h2 className={`${style.title}`}>Bill Splitter</h2>
<p className={`${style.personBillAmount}`}>Person Bill Amount</p>
<p onChange={handleChange} className={`${style.totalPersonAmount}`} >$ {tipAmount}</p>
</div>
<div className={`${style.bottom}`}>
<Input handleChange={handleChange} className={`${style.clases}`} placeholder='Amount of bill' label="Bill value">{billAmount}</Input>
<Input handleChange={handleChange} className={`${style.clases}`} placeholder='Number of people' label="Number of people">{personAmount}</Input>
<div className={`${style.tipBox}`}>
<p>Select tip</p>
{tips.map((tip) => {
return <button className={`${style.tipButton}`}>{tip} %</button>
})}
</div>
</div>
</div>
</div>
)
}
export default BillSplitter
Added onChange handler to the Input component and pass the different input setter functions. Then do the calculation when the tip is selected.
Try like below:
const tips = [5, 10, 15, 25];
const Input = ({ label, id, handleChange, name, type, placeholder }) => (
<React.Fragment>
<label htmlFor={id}>{label}</label>
<input
type={type || "number"}
id={id}
name={name || id}
placeholder={placeholder}
onChange={(e) => handleChange(e.target.value)}
></input>
<br />
</React.Fragment>
);
function BillSplitter() {
const [billAmount, setBillAmount] = React.useState(0);
const [personAmount, setPersonAmount] = React.useState(0);
const [perCost, setPerCost] = React.useState(0);
const calculatePerPeronAmount = (tip) => {
if (personAmount > 0) {
setPerCost(((1 + 0.01 * tip) * billAmount) / personAmount);
}
};
return (
<div>
<div>
<div>
<h2>Bill Splitter</h2>
<p>Person Bill Amount</p>
<p>$ {perCost}</p>
</div>
<div>
<Input
handleChange={setBillAmount}
placeholder="Amount of bill"
label="Bill value"
>
{billAmount}
</Input>
<Input
handleChange={setPersonAmount}
placeholder="Number of people"
label="Number of people"
>
{personAmount}
</Input>
<div>
<p>Select tip</p>
{tips.map((tip) => {
return (
<button onClick={() => calculatePerPeronAmount(tip)} key={tip}>
{tip} %
</button>
);
})}
</div>
</div>
</div>
</div>
);
}
ReactDOM.render(<BillSplitter />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
I need to do a form in react.js but when I try to type something in the form field, It doesn't let me type anything. I create a function for the input e my form. what am i doing wrong? please help me. thanks.
renderInput(title, value, onChange, validateField, placeholder) {
return (
<div className="col-12 col-md-4">
<div className="form-groud">
<label>{title}</label>
<input type="text" className="form-control"
name={title}
value={value}
onChange={e => onChange(e)}
onBlur={validateField}
placeholder={placeholder} />
</div>
</div>
)
}
renderForm() {
return (
<div className="form">
<div className="row">
{this.renderInput("Avatar", this.state.member.avatar, e => this.updateField(e), this.validateField, "profile picture" )}
{this.renderInput("Name", this.state.member.name, e => this.updateField(e), this.validateField, "name" )}
{this.renderInput("Email", this.state.member.email, e => this.updateField(e), this.validateField, "email" )}
{this.renderInput("Project", this.state.member.project, e => this.updateField(e), this.validateField, "project" )}
{this.renderInput("Devices", this.state.member.devices, e => this.updateField(e), this.validateField, "devices" )}
{this.renderInput("MainStack", this.state.member.mainstack, e => this.updateField(e), this.validateField,"main stack" )}
</div>
<hr />
<div className="row">
<div className="col-12 d-flex justify-content-end">
<button className="btn btn-primary"
onClick={e => this.save(e)}>
Save
</button>
<button className="btn btn-secondary ml-2"
onClick={e => this.clear(e)}>
Cancel
</button>
</div>
</div>
</div>
)
}
My updateField method:
updateField (event) {
const member = {...this.state.member}
member[event.target.avatar] = event.target.value
member[event.target.name] = event.target.value
member[event.target.project] = event.target.value
member[event.target.devices] = event.target.value
member[event.target.mainstack] = event.target.value
this.setState({ member })
}
you should controll input form value's using react state, in React is called a “controlled component”. So check your updateField function and make sure that it change the state value.
https://reactjs.org/docs/forms.html
I have a FieldArray in Redux Form that I push objects inside this array and right after that I have a callback to trigger a function to make a POST request.
When I submit the form I get the old values, because the push() method of the Redux Form is an async dispach from redux.
// Parent component
<FieldArray
name="myObjects"
component={ChildComponent}
onSubmitObjects={this.onSubmit} />
// Function
onSubmit = async () => {
const { getFormValues } = this.props;
const data = {
myObjects: getFormValues.myObjects
}
try {
// const contact = await Service.updateUser(data);
} catch (e) {
console.log(e)
}
}
I need to submit the form with the new values added in the array right after the push method.
// Function inside ChildComponent
addNewObject = () => {
const { fields, onSubmitObjects} = this.props;
fields.push({
number: 1,
name: 'Foo',
});
if (onSubmitObjects) {
onSubmitObjects(); // cb() to trigger a function in the parent component
}
}
Is there a way to call the callback with the new values right after the push method?
You should use a form with redux-form handleSubmit to wrap your FieldArray. You can optionally pass your custom submit function (to make API requests, submit validation, etc.) to handleSubmit so it'd look like this <form onSubmit={handleSubmit(this.onSubmit)}> ...
See this example from redux-form official docs:
FieldArraysForm.js
import React from 'react'
import {Field, FieldArray, reduxForm} from 'redux-form'
import validate from './validate'
const renderField = ({input, label, type, meta: {touched, error}}) => (
<div>
<label>{label}</label>
<div>
<input {...input} type={type} placeholder={label} />
{touched && error && <span>{error}</span>}
</div>
</div>
)
const renderHobbies = ({fields, meta: {error}}) => (
<ul>
<li>
<button type="button" onClick={() => fields.push()}>Add Hobby</button>
</li>
{fields.map((hobby, index) => (
<li key={index}>
<button
type="button"
title="Remove Hobby"
onClick={() => fields.remove(index)}
/>
<Field
name={hobby}
type="text"
component={renderField}
label={`Hobby #${index + 1}`}
/>
</li>
))}
{error && <li className="error">{error}</li>}
</ul>
)
const renderMembers = ({fields, meta: {error, submitFailed}}) => (
<ul>
<li>
<button type="button" onClick={() => fields.push({})}>Add Member</button>
{submitFailed && error && <span>{error}</span>}
</li>
{fields.map((member, index) => (
<li key={index}>
<button
type="button"
title="Remove Member"
onClick={() => fields.remove(index)}
/>
<h4>Member #{index + 1}</h4>
<Field
name={`${member}.firstName`}
type="text"
component={renderField}
label="First Name"
/>
<Field
name={`${member}.lastName`}
type="text"
component={renderField}
label="Last Name"
/>
<FieldArray name={`${member}.hobbies`} component={renderHobbies} />
</li>
))}
</ul>
)
const FieldArraysForm = props => {
const {handleSubmit, pristine, reset, submitting} = props
return (
<form onSubmit={handleSubmit}>
<Field
name="clubName"
type="text"
component={renderField}
label="Club Name"
/>
<FieldArray name="members" component={renderMembers} />
<div>
<button type="submit" disabled={submitting}>Submit</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
)
}
export default reduxForm({
form: 'fieldArrays', // a unique identifier for this form
})(FieldArraysForm)