NextJS - Passing data via property to component for editing - reactjs

I have a page where it will list all books to the admin. And if admin clicks on the EDIT button of any particular book, my app will display a custom modal where it would display the input boxes with the values in it and lets the admin to edit it.
Minimal example of my component:
import styles from '../../styles/EditPopup.module.css'
export default function EditPopup({ show, setPopupVisibility, item_data }){
const onSaveEdit = () => {
//TODO
}
return (
<div className={ styles.popup } style={ show ? {} : { display: 'none' } }>
<input type="text" defaultValue={ item_data.title } />
<input type="text" defaultValue={ item_data.author } />
<textarea defaultValue={ item_data.desc}></textarea>
<button onClick={ () => onSaveEdit() } >SAVE</button>
<button onClick={ () => setPopupVisibility( false ) }>CANCEL</button>
</div>
)
}
Minimal example on my page where it list all books with the edit button:
const [selected_book, setSelectedBook] = useState({ id: '', title: '', author: '', desc: '' })
const [show_edit_popup, setShowEditPopup] = useState(false)
return (
<EditPopup
show={ show_edit_popup }
setPopupVisibility={ (visibility) => setShowEditPopup( visibility ) }
item_data={ selected_book }
/>
<div>
{ all_books.map( (item, i) => (
<div key={ item.id }>
Title: {item.title}
Author: {item.author}
Desc: {item.desc}
<a href="#"
onClick={ (e) => {
e.preventDefault()
setSelectedBook( prev => ({ ...prev, id: item.id, title: item.title, author: item.author, desc: item.desc}) )
setShowEditPopup( true )
} }
>EDIT</a>
</div>
))}
</div>
)
The problem is, it is not working properly in the popup. The for desc will always be empty in the popup. And if I type some new value in any of the inputs and I close the popup and clicks the EDIT button on any other item, the value I entered previously in the inputs will still be there. And secondly am being confused here regarding how to get the new user input. Should I go with the useRef or useState with onChange
I know this is something simple for all of you, but am overthinking I guess.

You are not handling states correctly, So go for it with useState and onChange. For your inputs set an onChange as a callback function that comes from the parent component, pass the event.target.value through it, and set them in the state initiated in the parent.
also, minimize your state's declaration by batching them in one state.
the code could be like this:
import styles from '../../styles/EditPopup.module.css'
export default function EditPopup({ data, handleClosePopUp, onSaveEdit, handleChangeInputs }){
return (
<div className={ styles.popup } style={ data.show ? {} : { display: 'none' } }>
<input type="text" onChange={e => handleChangeInputs(e.target.value, 'title')} value={ data.title } />
<input type="text" onChange={e => handleChangeInputs(e.target.value, 'author')} value={ data.author } />
<textarea onChange={e => handleChangeInputs(e.target.value, 'desc')} value={ data.desc}></textarea>
<button onClick={onSaveEdit} >SAVE</button>
<button onClick={handleClosePopUp}>CANCEL</button>
</div>
)
}
and your parent
const initialData = { show: false, id: '', title: '', author: '', desc: '' }
const [popUpData, setPopUpData] = useState({...initialData})
const handleChangeInputs = (text, type) => {
setPopUpData(data => {...data, data[type] = text})
}
const onSaveEdit = () => {
//do your API call or whatever it updates all_books
}
return (
<EditPopup
handleChangeInputs={handleChangeInputs}
onSaveEdit={onSaveEdit}
handleClosePopUp={ () => setPopUpData({...initialData}) }
data={ popUpData }
/>
<div>
{ all_books.map(item) => (
<div key={ item.id }>
Title: {item.title}
Author: {item.author}
Desc: {item.desc}
<a href="#"
onClick={ (e) => {
e.preventDefault()
setPopUpData({ show: true, ...item})
} }
>EDIT</a>
</div>
))}
</div>
)

Related

How to fix "cannot update during an existing state transition (such as within `render`)" problem?

I get this problem even though my code doesn't update the state directly.
Here's my code:
export class Login extends React.Component {
static contextType = getContext();
_usernameValue = "";
_passwordValue = "";
// adding value to notification variable activates notification
state = { mode: "signin", notification: "" };
constructor(props) {
super(props);
this.showNotificationDeb = debounceOnce(this.showNotification, 200);
this.closeNotificationDeb = debounceOnce(this.closeNotification, 200);
}
signinClicked = () => {
this.context.dispatch({
type: "LOGIN",
payload: {
username: this._usernameValue,
password: this._passwordValue,
callback: this.incorrectCredentials,
},
});
};
forgotPasswordClicked = () => {
this.setState({ mode: "password" });
};
sendPasswordRequest = () => {
this.context.dispatch({
type: "FORGOT_PASSWORD",
payload: {
username: this._usernameValue,
password: this._passwordValue,
callback: this.passwordRequestSent,
},
});
};
incorrectCredentials = (errorMessage) => {
this.showNotificationDeb(errorMessage);
};
passwordRequestSent = (message) => {
this.showNotificationDeb(message);
};
restoreSigninWindow = () => {
this.setState({ mode: "signin" });
};
showNotification = (message) => {
console.log("aa");
this.setState({ notification: message });
};
closeNotification = () => {
if (this.state.notification) this.setState({ notification: "" });
};
render() {
return (
<div className={styles.container}>
<div className={styles.loginContainer}>
<Icon
rotate={90}
path={mdiCog}
size={2}
color="black"
className={styles.loginIcon}
/>
<p className={styles.loginTitle}>Sign in to RAS</p>
<div
className={`${styles.notificationContainer} ${
this.state.notification ? "" : styles.hideNotification
}`}
>
<p className={styles.notificationMessage}>
{this.state.notification}
</p>
<p
className={styles.notificationCloseButton}
onClick={() => this.closeNotification()}
>
x
</p>
</div>
<div className={styles.loginWindow}>
{this.state.mode === "signin" ? (
<React.Fragment>
<label className={styles.inputLabel}>Username</label>
<input
id="usernameInput"
className={styles.input}
onChange={(event) => {
this._usernameValue = event.target.value;
}}
></input>
<div className={styles.passwordLabelContainer}>
<label className={styles.inputLabel}>Password</label>
<p
className={styles.forgotPasswordLabel}
onClick={() => this.forgotPasswordClicked()}
>
Forgot password?
</p>
</div>
<input
id="passwordInput"
type="password"
className={styles.input}
onChange={(event) => {
this._passwordValue = event.target.value;
}}
></input>
<Button
variant="contained"
className={styles.button}
onClick={() => this.signinClicked()}
>
Sign in
</Button>
</React.Fragment>
) : (
<React.Fragment>
<div className={styles.backButtonContainer}>
<div onClick={() => this.restoreSigninWindow()}>
<Icon
path={mdiKeyboardBackspace}
size={0.85}
color="black"
className={styles.backIcon}
/>
<p>Back</p>
</div>
</div>
<label className={`${styles.inputLabel}`}>
Enter your email address. Password reset link will be send to
your email address.
</label>
<input
id="usernameInput"
className={styles.input}
placeholder="Enter your email address"
></input>
<Button
variant="contained"
className={styles.button}
onClick={() => this.sendPasswordRequest()}
>
Send
</Button>
</React.Fragment>
)}
</div>
</div>
</div>
);
}
}
As you see, I don't change the state directly. I do it always by calling the functions above. However, according to the React I am changing the state from inside render() function.
I do use lambda functions but I still see the problem.

How to a make dynamic form in React

I'm learning React. My target is to make this object:
"Phones": [
{
"Type": "Mobile",
"Phone_Number": "6546543"
},
{
"Type": "Home",
"Phone_Number": "6546543"
}
]
I did some research and I followed this YouTube video ReactJS - Dynamically Adding Multiple Input Fields.
These values I'll fetch from a form. Th user can keep on adding as many numbers as he wants. My code is:
render() {
return (
<div>
<h1>The Form</h1>
<label>Contact</label>
{this.state.phones.map((phone, index) => {
return (
<div key={index}>
<input onChange={(e) => this.handleChange(e)} value={phone} />
<select>
{this.phoneTypes.map((phoneType, index) => {
return (
<option key={index} value={phoneType}>
{phoneType}
</option>
);
})}
</select>
<button onClick={(e) => this.handleRemove(e)}>Remove </button>
</div>
);
})}
<hr />
<button onClick={(e) => this.addContact(e)}>Add contact</button>
<hr />
<button onClick={(e) => this.handleSubmit(e)}>Submit</button>
</div>
);
}
I'm not pasting the entire code here as I've already created a stackblitz. My code is not working as expected. Please pitch in.
You need to pass index in the handleChange function for input field. I guess you missed that part from video.This will make the input field editable.
<input
onChange={(e) => this.handleChange(e, index)}
value={phone}
/>
For the second part to get the expected object array I have updated some code, please check:
import React, { Component } from 'react';
class Questionnaire extends Component {
state = {
phones: [{ type: '', number: '' }],
};
phoneTypes = ['Mobile', 'Home', 'Office'];
addContact() {
this.setState({ phones: [...this.state.phones, { type: '', number: '' }] });
}
handleChange(e, index) {
this.state.phones[index]['number'] = e.target.value;
this.setState({ phones: this.state.phones });
}
handleRemove(index) {
this.state.phones.splice(index, 1);
console.log(this.state.phones, '$$$');
this.setState({ phones: this.state.phones });
}
handleSubmit(e) {
console.log(this.state, '$$$');
}
render() {
return (
<div>
<h1>The Form</h1>
<label>Contact</label>
{this.state.phones.map((phone, index) => {
return (
<div key={index}>
<input
onChange={(e) => this.handleChange(e, index)}
value={phone.number}
name="number"
/>
<select>
{this.phoneTypes.map((phoneType, index) => {
return (
<option key={index} value={phoneType} name="type">
{phoneType}
</option>
);
})}
</select>
<button onClick={(e) => this.handleRemove(e)}>Remove </button>
</div>
);
})}
<hr />
<button onClick={(e) => this.addContact(e)}>Add contact</button>
<hr />
<button onClick={(e) => this.handleSubmit(e)}>Submit</button>
</div>
);
}
}
export default Questionnaire;
You can use input objects like formik
import { useState } from 'react';
const Component = () => {
const [inputs, setInputs] = useState({ // Initial inputs
email: "",
phoneNumber: "",
});
const handleSubmit = (e) => {
e.preventDefault();
// Do
};
const handleChange = (e) => {
setInputs({...inputs, [e.target.id]: e.target.value});
};
const addInput = () => {
const name = window.prompt("Type a name", "");
setInputs({...inputs, [name]: ""});
};
return (
<form onSubmit={handleSubmit}>
{object.keys(inputs).map((name, i) => {
<input type="text" onChange={handleChange} name={name} value={inputs[name]} />
})}
<button
type="button"
onClick={addInput}
>
Add Input
</button>
</form>
);
}

my form is not working. when i click on save button, my table is blank, without the field input.and my cancel button is not clearing the fields

I'm using React, Formik and Yup to make a form.
my form is not working. when i click on save button, my input is not saving the information I typed on the fields. it's saving blank information on the table.
and my cancel button is not clearing the fields. when i click on cancel button, nothing happens. need your help, guys! thanks.
import React, { Component } from 'react';
import Main from '../template/Main';
import axios from 'axios';
import { Helmet } from 'react-helmet';
import { confirmAlert } from 'react-confirm-alert';
import 'react-confirm-alert/src/react-confirm-alert.css';
import { BASE_URL } from './Url.json';
import { Formik, ErrorMessage, Form, Field } from 'formik';
import * as Yup from 'yup';
const initialState = {
member: { avatar: '', name: '', email: '', project: '', devices: '', mainstack: '' },
list: [],
};
export default class MemberCrud extends Component {
constructor(props) {
super(props);
this.state = { ...initialState };
//this.validateField = this.validateField.bind(this)
}
componentWillMount() {
axios(BASE_URL).then((resp) => {
this.setState({ list: resp.data });
});
}
clear() {
this.setState({ member: initialState.member });
}
save() {
const member = this.state.member;
const method = member.id ? 'put' : 'post';
const url = member.id ? `${BASE_URL}/${member.id}` : BASE_URL;
axios[method](url, member).then((resp) => {
const list = this.getUpdatedList(resp.data);
this.setState({ member: initialState.member, list });
});
}
getUpdatedList(member) {
const list = this.state.list.filter((u) => u.id !== member.id);
list.unshift(member);
return list;
}
updateField(event, field) {
const member = this.state.member;
member[field] = event.target.value;
this.setState({ member });
}
renderInput(title, value, onChange, /*validateField,*/ placeholder, formik = {}) {
return (
<div className="col-12 col-md-4">
<div className="form-groud">
<label>{title}</label>
</div>
<div className="form-row">
<Field
type={placeholder}
name={placeholder}
id={placeholder}
placeholder={placeholder}
// className={formik.errors[placeholder] && formik.touched[placeholder] ? "input-error" : null}
/>
<ErrorMessage name={placeholder} component="span" className="error" />
</div>
</div>
);
}
renderForm() {
const formSchema = Yup.object().shape({
avatar: Yup.string().required('Required'),
name: Yup.string().required('Required'),
email: Yup.string().required('Required'),
project: Yup.string().required('Required'),
devices: Yup.string().required('Required'),
mainstack: Yup.string().required('Required'),
});
const initialState = {
member: { avatar: '', name: '', email: '', project: '', devices: '', mainstack: '' },
list: [],
};
return (
<Formik
initialValues={initialState.member}
validationSchema={formSchema}
onSubmit={(values) => {
console.log(values);
}}
>
{(formik) => {
return (
<Form>
<div className="row" updateField={this.updateField}>
{this.renderInput(
'Avatar',
this.state.member.avatar,
(e) => this.updateField(e, 'avatar'),
/*this.validateField,*/ 'avatar',
)}
{this.renderInput(
'Name',
this.state.member.name,
(e) => this.updateField(e, 'name'),
/*this.validateField,*/ 'name',
)}
{this.renderInput(
'Email',
this.state.member.email,
(e) => this.updateField(e, 'email'),
/*this.validateField,*/ 'email',
)}
{this.renderInput(
'Project',
this.state.member.project,
(e) => this.updateField(e, 'project'),
/*this.validateField,*/ 'project',
)}
{this.renderInput(
'Devices',
this.state.member.devices,
(e) => this.updateField(e, 'devices'),
/*this.validateField,*/ 'devices',
)}
{this.renderInput(
'MainStack',
this.state.member.mainstack,
(e) => this.updateField(e, 'mainstack'),
/*this.validateField,*/ 'mainstack',
)}
</div>
<hr />
<div className="row">
<div className="col-12 d-flex justify-content-end">
<button
type="submit"
className={!(formik.dirty && formik.isValid) ? 'disabled-btn' : 'btn btn-primary'}
disabled={!(formik.dirty && formik.isValid)}
onClick={(e) => this.save(e)}
>
Save
</button>
<button
type="button"
className="btn btn-secondary ml-2"
onClick={(e) => this.clear(e)}
/*disabled={!(formik.dirty && formik.isValid)}*/
>
Cancel
</button>
</div>
</div>
</Form>
);
}}
</Formik>
);
}
load(member) {
this.setState({ member });
}
remove(member) {
axios.delete(`${BASE_URL}/${member.id}`).then((resp) => {
const list = this.state.list.filter((u) => u !== member);
this.setState({ list });
});
}
confirmRemove(member) {
confirmAlert({
message: 'Are you sure ?',
buttons: [
{
label: 'Yes',
onClick: () => this.remove(member),
},
{
label: 'No',
},
],
});
}
renderTable() {
return (
<table className="table">
<thead>
<tr>
<th>Profile Pictute</th>
<th>Name</th>
<th>Email</th>
<th>Project</th>
<th>Devices</th>
<th>Main Stack</th>
<th>Actions</th>
</tr>
</thead>
<tbody>{this.renderRows()}</tbody>
</table>
);
}
renderRows() {
return this.state.list.map((member) => {
return (
<tr key={member.id}>
<td>
<div className="avatar">
<img src={member.avatar} alt={member.name} />
</div>
</td>
<td>{member.name}</td>
<td>{member.email}</td>
<td>{member.project}</td>
<td>{member.devices}</td>
<td>{member.mainstack}</td>
<td>
<button className="btn btn-warning" onClick={() => this.load(member)}>
<i className="fa fa-pencil"></i>
</button>
<button
className="btn btn-danger ml-2"
onClick={() => {
this.confirmRemove(member);
}}
>
<i className="fa fa-trash"></i>
</button>
</td>
</tr>
);
});
}
render() {
return (
<Main>
{this.renderForm()}
{this.renderTable()}
<Helmet>
<title>Add Member</title>
</Helmet>
</Main>
);
}
}
You should use formik state, not ur component state. also for the reset field, you should use onSubmit={(e, { resetForm }) like this :
<Formik
initialValues={initialState.member}
validationSchema={formSchema}
onSubmit={(e, { resetForm }) => {
//Your submit code
...
//Then reset with this function
resetForm({
values: {
...e,
},
});
}}
>
{({ handleChange, values, touched, errors }) => ( // <-- use this state
<Form>
// Your form code .jsx, you can seperate them aswell just pass state from above!
</Form>
)}
</Formik>
Formik Basic

How to add data into an array using inputs in reactjs?

I am new to react and I am trying to build a todo app which basically can execute the CRUD operation. I am able to update and delete as of now. But not sure how to create an object and save it into the array.
When i click on edit or add task button a modal pop up and i am trying to enter the title and description value there.
This is my Index.js file
import React, {Component} from 'react';
import { Button, Modal } from 'reactstrap';
import Modals from './Modals'
const todoItems = [
{
id: 1,
title: "Go to Market",
description: "Buy ingredients to prepare dinner",
completed: true
},
{
id: 2,
title: "Study",
description: "Read Algebra and History textbook for upcoming test",
completed: false
},
{
id: 3,
title: "Sally's books",
description: "Go to library to rent sally's books",
completed: true
},
{
id: 4,
title: "Article",
description: "Write article on how to use django with react",
completed: false
}
];
class Index extends Component {
state = {
modal: false,
items: todoItems,
selectedItem: null,
selectedIndex: -1,
}
toggle = (item, index) => {
if (item) {
this.setState({ selectedItem: item, selectedIndex: index })
}
this.setState({ modal: !this.state.modal });
};
handleChange = () => {
let oldState = this.state.items;
this.setState({ items: oldState })
}
onDelete = (item) => {
const newData = this.state.items.filter(i => i.title !== item.title)
this.setState({ items: newData })
}
render() {
return (
<>
<h1 className="p-3">TODO APP</h1>
<div style={{ backgroundColor: "white", padding: "50px", color: "black"}} className="container">
<div className="row">
<button className="btn btn-primary" onClick={() => this.toggle()}>Add task</button>
</div>
<div className="row my-5">
<Button color="danger mr-5">Incomplete</Button>
<Button color="success">Complete</Button>
<Modals index={this.state.selectedIndex} onDelete={this.onDelete} item={this.state.selectedItem} handleChange={this.handleChange} modal={this.state.modal} toggle={this.toggle} />
</div>
<hr/>
{this.state.items.map((item, index) => {
return(
<div key={item.id}>
<div className="row">
<p style={{ textAlign: "left" }}>{item.title}</p>
<Button onClick={() => this.toggle(item, index)} className="mr-0 ml-auto" color="info">Edit</Button>
<Button onClick={() => this.onDelete(item)} className="ml-5" color="warning">Delete</Button>
</div>
<hr/>
</div>
)
})}
</div>
</>
);
}
}
export default Index;
This is my modal.js file
import React, { useState, useEffect } from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
function Modals(props) {
const {
className,
buttonLabel,
handleChange,
item,
index,
toggle,
} = props;
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
useEffect(() => {
if (item && item.title){
setTitle(item.title)
setDescription(item.description)
}
}, [item])
const setValues = () => {
handleChange({ title: title, description: description });
props.toggle();
push({
})
}
return (
<div>
<Modal isOpen={props.modal} className={className} style={{ color: "black"}}>
<ModalHeader >Todo Item</ModalHeader>
<ModalBody>
<div className="container-fluid">
<div className="row mb-3">
<div className="col-12">
<p className="mb-0">Title</p>
</div>
<div className="col-12">
<input onChange={(e) => setTitle(e.target.value)} value={title} className="w-100" type="text" placeholder="Enter title"/>
</div>
</div>
<div className="row mb-3">
<div className="col-12">
<p className="mb-0">Description</p>
</div>
<div className="col-12">
<input type="text" onChange={(e) => setDescription(e.target.value)} value={description} className="w-100" placeholder="Enter Description"/>
</div>
</div>
<div className="row">
<div className="col-12">
<input type="checkbox"/>
<span className="ml-2"> Completed </span>
</div>
</div>
</div>
</ModalBody>
<ModalFooter>
<Button onClick={() => setValues()} color="success">Submit</Button>
<Button onClick={props.toggle} color="secondary">Cancel</Button>
</ModalFooter>
</Modal>
</div>
)
}
export default Modals;
Thanks in advance!!
One way would be to just create a method in index.js
addItem = (item) =>{
this.setState({items: [...this.state.items, item]})
}
and then just pass it as a prop to your Modal and call it in setValues,
const setValues = () => {
handleChange({ title: title, description: description });
props.toggle();
props.addItem({ title: title, description: description, //etc})
}

How to maintain controlled input which are added dynamically on user click with React Hooks

I've got a form component, where you can add multiple inputs on click. I am using useState to store data from all of the inputs, but the problem is I am receiving a message that my inputs are uncontrolled
State data structure
const [formData, setFormData] = useState({
title: '',
titleNum: '',
meta: [
{
content: '',
contentNum: '',
details: '',
tags: ''
}
]
});
The idea is to dynamically add multiple objects via form to meta array
onChange events added to inputs
const { title, titleNum, meta } = formData;
const handleMainChange = e => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubChange = (e, index) => {
const { name, value } = e.target;
let metaContent = [...meta];
metaContent[index] = { ...metaContent[index], [name]: value };
setFormData({ ...formData, meta: metaContent });
};
Adding and removing inputs
const handleAddInput = () => {
setFormData(prevState => ({
meta: [
...prevState.meta,
{ content: '', contentNum: '', details: '', tags: '' }
]
}));
};
const handleRemove = index => {
let metaContent = [...meta];
metaContent.splice(index, 1);
setFormData({ meta: metaContent });
};
Submit and looping through all META
const onFormSubmit = e => {
e.preventDefault();
createAccount(formData, history);
};
const renderList = meta.map((item, index) => {
console.log(item);
return (
<AddMeta
key={index}
index={index}
meta={item}
handleChange={e => handleSubChange(e, index)}
handleRemove={() => handleRemove(index)}
/>
);
});
AddMeta Component
const AddMeta = ({ index, meta, handleChange, handleRemove }) => {
return (
<Fragment>
<div className='row valign-wrapper'>
<div className='col s3'>
<h5 className='indigo-text text-lighten-1'>Add Meta - {index + 1}</h5>
</div>
<div className='col s9'>
<button onClick={() => handleRemove(index)} className='btn red'>
Remove Meta Content
</button>
</div>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Content Text'
name='content'
value={meta.content || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Content Number'
name='contentNum'
value={meta.contentNum || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<textarea
className='materialize-textarea'
type='text'
placeholder='Details'
name='details'
value={meta.details || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
<div className='form-group'>
<input
type='text'
placeholder='Tags'
name='tags'
value={meta.tags || ''}
onChange={e => handleChange(e, index)}
autoComplete='off'
/>
</div>
</Fragment>
);
};
I appreciate any attempt to solve this problem.
Thanks!
Try removing the || '' on your inputs value. Their value should always be tied to your state (which should be '' when you add a new meta).

Resources