How do I validate textfields in a dynamic array - javascript

I have one mother component and one child component. In the child component there are two textfields (name and email) and an add button. When the user presses on add a new set of the same textfields will be rendered. I save those textfields in my mother component in an array. I want to have a validate function that checks of the values are valid. If not i want to give the error props a true value so that the user can see that field is wrong. The only problem i have is figuring out how to give right textfield in the array the error value. Because now every textfield will get the error value.
Child Component:
import React from 'react';
import TextField from '#material-ui/core/TextField';
import AddIcon from '#material-ui/icons/Add';
import Minus from '#material-ui/icons/Delete';
import Button from '#material-ui/core/Button';
class People extends React.Component {
constructor(props){
super(props);
}
render(){
const {classes} = this.props;
return (
<div>
{this.props.people.map((person, index) => (
<div key={person}>
<div className="container">
<div className="row">
<div className="col s2">
<Button
mini
variant="fab"
color="primary"
aria-label="minus"
onClick={e =>
this.props.removeRow(index)}
data-toggle="tooltip"
>
<Minus/>
</Button>
</div>
<div>
<div className="col s5">
<TextField
name="name"
id="standard-dense"
label="Teamlid naam"
margin="dense"
value={person.name}
error={e =>
this.props.validate(e, index)}
onChange={e =>
this.props.onChange(e, index)}
/>
</div>
<div className="col s5">
<TextField
name="email"
id="standard-dense"
label="Teamlid email"
margin="dense"
value={person.email}
error={e =>
this.props.validate(e, index)}
onChange={e =>
this.props.onChange(e, index)}
/>
</div>
</div>
</div>
</div>
</div>
))}
<div className="container">
<div className="row">
<div className="col s2">
<Button
mini
variant="fab"
color="primary"
aria-label="Add"
onClick={this.props.addRow}
data-toggle="tooltip"
className="btn btn-xs btn-primary"
data-original-title="">
<AddIcon/>
</Button>
</div>
<div className="col s5">
</div>
<div className="col s5">
</div>
</div>
</div>
</div>
);
}
};
export default People;
Mother Component:
import React, {Component} from 'react';
import People from './People';
class Form extends React.Component{
constructor(props){
super(props);
this.state = {
people: []
}
}
addRow = () => {
this.setState(previousState => {
return {
people: [...previousState.people, {name: "", email: "", error:false}]
};
});
};
removeRow = index => {
this.setState(previousState => {
const people = [...previousState.people];
people.splice(index, 1);
return { people };
});
};
//onChange functie van de teamleden rijen
onChange = (event, index) => {
const { name, value } = event.target;
this.setState(previousState => {
const people = [...previousState.people];
people[index] = { ...people[index], [name]: value };
return { people };
});
};
validate = (event,index) => {
event.preventDefault();
if(!event.target.value){
return true;
}else{
return false;
}
};
render(){
const {classes} = this.props;
return(
<form>
<People
addRow={this.addRow}
removeRow={this.removeRow}
onChange={this.onChange}
people={this.state.people}
validate={this.validate}
/>
<button onClick={this.validate}>Validate</button>
</form>
);
}
}
export default Form;
I know that the validate function probably needs the index of the wrong textfield. But I don't know how to properly implement it.

Related

How to create "Selected tab" in Next.Js?

I am trying to create selected tab in Next.Js.
The User will have the option to search for data it can be Users or Posts, what the user will search for will be selected by clicking on one of the buttons.
Once the user clicks on the button the button will change background to blue.
However I can't make it to work properly, when the User clicks on the button the .Selected class gets added to the button but the button doesn't render the CSS.
import React, { MouseEventHandler, ReactElement, useState } from 'react'
import { PageWithLayout } from '../components/Layouts/LayoutConfig'
import MainLayout from '../components/Layouts/MainLayout'
import style from '../styles/Search.module.css'
const Search: PageWithLayout = () => {
const [searchPosts, setPostsSearch] = useState < String > ();
const setSearchOption = (searchFor: String) => {
let searchOption = '';
if (searchFor == 'POSTS') {
searchOption = 'POSTS';
} else {
searchOption = 'USERS';
let button = document.getElementById('usersOption') as HTMLElement;
button.className += style.Selected;
}
console.log(searchOption);
setPostsSearch(searchOption);
}
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...' />
</div>
<div className={style.SearchOptions}>
<button id='usersOption' onClick={() => setSearchOption('USERS')}>Users</button>
<button id='postsOption' onClick={() => setSearchOption('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)
}
Search.getLayout = function getLayout(page: ReactElement) {
return (
<MainLayout>
{page}
</MainLayout>
)
}
export default Search
you can use searchOption data for className style
import React, { MouseEventHandler, ReactElement, useState } from 'react'
import { PageWithLayout } from '../components/Layouts/LayoutConfig'
import MainLayout from '../components/Layouts/MainLayout'
import style from '../styles/Search.module.css'
const Search: PageWithLayout = () => {
const [searchPosts, setPostsSearch] = useState<String>();
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...'/>
</div>
<div className={style.SearchOptions}>
<button id='usersOption' className={searchPosts === 'USERS' ? style.Selected : undefined } onClick={() => setPostsSearch('USERS')}>Users</button>
<button id='postsOption' className={searchPosts === 'POSTS' ? style.Selected : undefined } onClick={() => setPostsSearch('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)
}
Search.getLayout = function getLayout(page: ReactElement){
return(
<MainLayout>
{page}
</MainLayout>
)
}
export default Search
Just have a state for active searchOption and apply the class conditionally directly into the JSX.
const [activeSearchOption, setActiveSearchOption] = useState('USERS')
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...'/>
</div>
<div className={style.SearchOptions}>
<button id='usersOption' className={activeSearchOption === 'USERS' ? 'active' : ''} onClick={() => setSearchOption('USERS')}>Users</button>
<button id='postsOption' className={activeSearchOption === 'POSTS' ? 'active' : ''} onClick={() => setSearchOption('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)

I keep getting .map() is not a function upon first load, but it works when refreshed...any ideas why? MERN stack

So, I have this appointments array coming in from its parent component. This component should map over the array, and then return a div for each item that occurs at a date later than today. Then, when the user clicks the "confirm" button it should re-render the list and remove that item that was just clicked from the list of rendered divs.
The problem is when it re-renders, I get the same "appointments.map() is not a function" error. If I click refresh the component renders as expected with the div that was clicked on removed from the list.
I can't seem to figure out why, as this component shouldn't even load unless the appointments array has a length (as it states in the parent component).
This is the parent component:
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Link } from 'react-router-dom'
import { getAppointments } from '../../../actions/appointment'
import ConfirmAppointment from '../Appointments/ConfirmAppointment'
const Dashboard = ({user}) => {
const dispatch = useDispatch()
useEffect(() => {
//get appointment data
dispatch(getAppointments(user?.result._id))
}, [dispatch])
const appointments = useSelector((state)=>state?.usersReducer?.appointment)
return (
<>
<div>
<h3>Welcome {user?.result?.firstName}! :D</h3>
{/* <Link to='/bookappointment'>
<button className="ui button" style={{backgroundColor: '#adad85'}}>Book an appointment</button>
</Link> */}
<Link to="/healthhistory/update" >
<button className="ui button" style={{backgroundColor: '#adad85'}}>Update Health History</button>
</Link>
<Link to="/dashboard/receipts">
<button className="ui button" style={{backgroundColor: '#adad85'}}>View Appointment Receipts</button>
</Link>
{appointments?.length === 0 ? (
<div>Loading ...</div>
) : (
<ConfirmAppointment user={user} appointments={appointments} />
)
}
</div>
</>
)
}
export default Dashboard
And this is the component with the .map function:
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { confirmAppointment } from '../../../actions/appointment'
const ConfirmAppointment = ({user, appointments}) => {
const dispatch = useDispatch()
const { _id } = user?.result
const today = new Date()
const [reasonForMassage, setReasonForMassage] = useState('')
const [treatmentConsent, setTreatmentConsent] = useState(false)
const [glutes, setGlutes] = useState(false)
const [chest, setChest] = useState(false)
const [abdomen, setAbdomen] = useState(false)
const [innerThighs, setInnerThighs] = useState(false)
const [areasToAvoid, setAreasToAvoid] = useState('')
const formData = {
reasonForMassage,
consents: {
treatmentConsent,
glutes,
chest,
abdomen,
innerThighs,
areasToAvoid
}
}
const handleSubmit = (e, appointmentId) => {
e.preventDefault()
setTreatmentConsent(true)
//update appointment with the appointment id
dispatch(confirmAppointment(_id, appointmentId, formData))
clear()
}
const clear = () => {
setReasonForMassage('')
setGlutes(false)
setChest(false)
setAbdomen(false)
setInnerThighs(false)
setAreasToAvoid(false)
}
return (
appointments?.length === 0 ? (
<div>
No upcoming appointments
</div>
) : (
<div style={{marginTop: '3em'}}>
<h4>Upcoming Appointments</h4>
{appointments && appointments?.map((appointment) => (
new Date(appointment?.date) >= today && appointment?.consents?.treatmentConsent !== true ? (
<div style={{marginBottom: '3em'}} key={appointment._id} >
<table className="ui table">
<thead>
<tr>
<th>Date</th>
<th>Time</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>{appointment?.date}</td>
<td>{appointment?.time}</td>
<td>{appointment?.duration}</td>
</tr>
</tbody>
</table>
<form className="ui form" onSubmit={(e)=>handleSubmit(e, appointment?._id)} >
<div className="ui fields">
<div className="ui field">
<label>Reason for booking massage:</label>
<input type="text" value={reasonForMassage} onChange={(e)=>setReasonForMassage(e.target.value)}/>
</div>
<div className="ui field">
<h5>I give consent to massage the following areas:</h5>
<div>
<input type="checkbox" value={glutes} onChange={(e)=>setGlutes(e.target.checked)} />
<label>Glutes</label>
<input type="checkbox" value={abdomen} onChange={(e)=>setAbdomen(e.target.checked)} />
<label>Abdomen</label>
<input type="checkbox" value={chest} onChange={(e)=>setChest(e.target.checked)} />
<label>Chest</label>
<input type="checkbox" value={innerThighs} onChange={(e)=>setInnerThighs(e.target.checked)} />
<label>Inner thighs</label>
</div>
</div>
<div className="ui field">
<label>Are there any other areas you would not like to be massaged?</label>
<input type="text" value={areasToAvoid} onChange={(e)=>setAreasToAvoid(e.target.value)} />
</div>
</div>
<button type="submit" className="ui button">Confirm Appointment</button>
</form>
</div>
) : (
<div></div>
)
))}
</div>
)
)
}
export default ConfirmAppointment

React form does not render the state values I put on constructor

I have a tab component with 2 tabs. The first tab contains a list of employees and a button that transfers you in the second tab (contact form) containing the data of the selected employee. In the first tab I create an object of an employee, send it in the second tab and in the second tab i set the state.name, state.surname with the object values.
The problem is that in order to load the data in the form I need to change back in the first tab and go to the second tab again.
The tabs component
import React from 'react';
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box p={3}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
var importErg = new Boolean(false);
export function getImport(){
return importErg;
}
export const globalErg = {
onoma: "",
epitheto: ""
}
export function getGlobalErg(){
return globalErg;
}
async function getErgByeID(ErgEid){
globalErg.onoma = ""
globalErg.epitheto = ""
await fetch(URL+"/ergazomenoi?eid=eq."+ErgEid)
.then(response => {if(response.ok){
return response.json()
}
else {
alert('Something went wrong')
throw new Error('Something went wrong');
}
})
.then(data => {
globalErg.onoma = data[0].onoma
globalErg.epitheto = data[0].epitheto
}
)
}
export default function SimpleTabs() {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
function more(ergID){
setValue(1);
getErgByeID(ergID);
}
}
return (
<div className="main">
<AppBar position="static" id = "topbar">
<Tabs value={value} onChange={handleChange}>
<Tab label="Employees" {...a11yProps(0)}/>
<Tab label="Contact" {...a11yProps(1)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
<Table />
<Button style={{color: "#fff", background: "#111", marginRight: "2.5px", marginLeft:"2.5px", marginTop:"5px"}} onClick={() => more()}>
Contact
</Button>
</TabPanel>
<TabPanel value={value} index={1} id = 'tab'>
<Contact/>
</TabPanel>
</div>
);
}
the form component
import React, { Component, useEffect } from "react";
import {getGlobalErg} from "./Proswpiko-tabs";
class Personal_info extends Component {
constructor(props){
super(props);
let erg = getGlobalErg();
this.state = {
onoma: erg.onoma,
epitheto: erg.epitheto,
};
}
onomaChangeHandler = (event) => {
this.setState({onoma: event.target.value});
}
epithetoChangeHandler = (event) => {
this.setState({epitheto: event.target.value});
}
render() {
return (
<form onSubmit = {this.SubmitHandler}>
<div >
<p id = "topText" align = "center">
<h2>Contact info</h2>
</p>
<img id="top" src="top.png" alt=""></img>
<div id="form_container">
<form id="form" class="appnitro" method="post" action="">
<div class="form_description">
<h2>Personal info</h2>
</div>
<ul>
<li id = "li_3">
<label class="description" for="element_3" >Όνομα </label>
<span>
<input type ="text" id="nameInput" name= "nameInput" class="element text" maxLength="255" size="15" onChange={this.onomaChangeHandler} value = {this.state.onoma} required/>
<label>Name</label>
</span>
<span>
<input type ="text" id="surNameInput" name= "surNameInput" class="element text" maxLength="255" size="14" onChange={this.epithetoChangeHandler} value = {this.state.epitheto} required/>
<label>Surname</label>
</span>
</li>
</ul>
</form>
<div id="footer">
</div>
</div>
<img id="bottom" src="bottom.png" alt=""></img>
</div>
</form>
);}}
export default Personal_info;
It seems that since the setState func is async It didnt had time to fill the imput boxes.
Solution: Added setTimeout and now everything works fine

innerHTML works only once in my react app

What I am trying to do is clear my container div when the user hits the search button so all the existing search results are removed.
The problem is I am using innerHTML to clear the container div but it only works for the first time.
If you search for something for the second time then you will see no results are rendered.
I've just started learning reactjs.
Here is my code (I have removed the API key and the id just FYI).
import React from "react";
import Recipe from "./Recipe";
import "./styles.css";
export default class App extends React.Component {
constructor() {
super();
this.state = {
searchQuery: "Icecream",
searchData: [],
error: false,
loader: false
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
document.querySelector(".control.field").classList.add("is-loading");
this.setState({
error: false,
loader: true
});
document.querySelector(".columns").innerHTML = "test";
fetch(
`https://api.edamam.com/search?q=${
this.state.searchQuery
}&app_id=id&app_key=key`
)
.then(response => response.json())
.then(data => {
this.setState({
searchData: data.hits
});
})
.catch(err => {
this.setState({
error: true
});
})
.finally(() => {
document.querySelector(".control.field").classList.remove("is-loading");
this.setState({
loader: false
});
});
}
renderIngredients = arr => arr.map((el, index) => <li key={index}>{el}</li>);
render() {
let result;
result = this.state.searchData.map((el, index) => (
<Recipe
key={index}
image={el.recipe.image}
title={el.recipe.label}
ingredients={this.renderIngredients(el.recipe.ingredientLines)}
/>
));
return (
<div>
<form className="px-3 py-3" onSubmit={this.handleSubmit}>
<div className="field is-grouped">
<p className="control field is-expanded">
<input
className="input is-rounded"
type="text"
placeholder="Find a recipe"
name="searchQuery"
value={this.state.searchQuery}
onChange={this.handleChange}
/>
</p>
<p className="control">
<button className="button is-primary">Search</button>
</p>
</div>
</form>
{this.state.loader ? (
<div className="spinner-box">
<div className="three-quarter-spinner" />
</div>
) : (
""
)}
{this.state.error ? (
<div className="px-3 py-3">
<div className="notification is-danger">
Some error occured. Unable to fetch
</div>
</div>
) : (
<div className="columns is-multiline px-3 py-3 is-mobile">
{result}
</div>
)}
</div>
);
}
}
Recipe component :
import React from "react";
export default class Recipe extends React.Component {
render() {
return (
<div className="column is-one-quarter-desktop is-half-mobile">
<div className="card">
<div className="card-image">
<figure className="image is-4by3">
<img src={this.props.image} alt="" />
</figure>
</div>
<div className="card-content">
<p className="title is-6">{this.props.title}</p>
<ul>{this.props.ingredients}</ul>
</div>
</div>
</div>
);
}
}
What do you think about using new state for that?
What I mean is that:
this.state = {
...,
focusSearch: false
}
const handleFocus = () => {
this.setState({ ...this.state, focusSearch: true});
}
<input
className="input is-rounded"
type="text"
placeholder="Find a recipe"
name="searchQuery"
value={this.state.searchQuery}
onFocus={this.handleFocus}
onChange={this.handleChange}
/>
render() {
return (
{
focusSearch ? <div></div> : <></>;
}
)
}
I figured it out this morning. It can be done easily using conditional rending and ternary operator.
Thank you, everyone.

Changing variable in one file conflicts the data for other file in React JS

I'm having weird problem in React JS. I have two classes named as Notes.js and DataTables.js
I'm using DataTables in Note.js like this
<DataTables
keyField="id"
columns={columns}
url={this.state.url}
useCallBack={true}
onEdit={this.onEdit}
/>
Please Note that DataTables.js is my own custom created DataTable.js not react-datatable.
All the work like fetching data from URL and showing it in tabular form is in DataTables.js file.
Note.js Code:
import React, { Component } from "react";
import { Constant } from "../shared/Constants";
import DataTables from "../shared/DataTables";
import { Modal, Button } from "react-bootstrap";
import BreadCrumb from "../shared/BreadCrumb";
import "../Style.css";
const columns = Constant.notes;
export class Notes extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
url: "notes/get_notes",
showModal: false,
note: [],
};
this.onEdit = this.onEdit.bind(this);
this.onAdd = this.onAdd.bind(this);
this.onUpdate = this.onUpdate.bind(this);
this.saveNote = this.saveNote.bind(this);
}
onUpdate(key, value) {
let noteData = this.state.note;
noteData[key] = value;
this.setState({
note: noteData,
});
}
saveNote(e) {
e.preventDefault();
}
onEdit(n) {
this.setState({
note: n,
showModal: true,
});
}
onAdd() {
this.setState({
note: [],
showModal: true,
});
}
render() {
return (
<>
<Modal
show={this.state.showModal}
aria-labelledby="example-modal-sizes-title-lg"
onHide={() => this.setState({ showModal: false })}
>
<form method="post" onSubmit={this.saveNote}>
<Modal.Header>
<Modal.Title>My Note</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="row">
<div className="col-sm-12">
<div className="form-group">
<label className="text-muted">Note Title</label>
<input
type="text"
placeholder="Note Title"
className="form-control"
ref="title"
value={this.state.note.title}
onChange={(e) => this.onUpdate("title", e.target.value)}
/>
</div>
<div className="form-group">
<label className="text-muted">Content</label>
<textarea
onChange={(e) => this.onUpdate("content", e.target.value)}
className="form-control"
style={{ height: "250px" }}
placeholder="Content"
>
{this.state.note.content}
</textarea>
</div>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button
variant="secondary"
onClick={() => this.setState({ showModal: false })}
>
Close
</Button>
<Button type="submit" variant="primary">
Save Note
</Button>
</Modal.Footer>
</form>
</Modal>
<BreadCrumb
title="My Notes"
useCallBack={true}
onAdd={this.onAdd}
active_link="Notes"
link=""
link_text="Add New"
/>
<div className="row">
<div className="col-sm-12">
<div className="card">
<div className="card-body">
<div className="card-title">Notes</div>
<DataTables
keyField="id"
columns={columns}
url={this.state.url}
useCallBack={true}
onEdit={this.onEdit}
/>
</div>
</div>
</div>
</div>
</>
);
}
}
export default Notes;
I'm having Problem in Note.js on onUpdate function
onUpdate(key, value) {
let noteData = this.state.note;
noteData[key] = value;
this.setState({
note: noteData,
});
}
Problem: When I update a field in Modal as you can see in my code, then my Table in DataTable.js automatically gets updated, I'don't why :/
Here is DataTables.js function where I'm sending data to onEdit function
const TableData = () => {
return (
<tbody>
{tableData.length === 0 ?
<tr>
<td className="text-center" colSpan="5"><strong>No Data Found</strong></td>
</tr>
:
tableData.map((tData) => (
<tr key={tData[this.props.keyField]}>
{this.props.columns.map((item, index) => (
<td key={index} className="table-content">
{index === 0 ?
[(useCallback === true ? <span key={"sub_"+index} className="link" onClick={() => this.props.onEdit(tData)}>{tData[item.dataField]}</span> :
<Link
to={
this.props.edit_link +
"/" +
tData[this.props.edit_key_first] + (this.props.edit_key_second ? "/" +
tData[this.props.edit_key_second] : '')
}
>
{tData[item.dataField]}
</Link>
)]
: (
tData[item.dataField]
)}
</td>
))}
</tr>
))}
</tbody>
);
};
Please check gif image below so you can understand it :P
You have an onChange function that is updating the content
onChange={(e) => this.onUpdate("content", e.target.value)}
If you don't want to change the content while typing then you will have to remove this.

Categories

Resources