Child component renders even though its being memoized - reactjs

Child component renders even though props are not altered of it.
Following is the parent component
import Child from "./Child";
function Parent({
selected,
show,
setShow,
}) {
const [isRunNow, setIsRunNow] = useState(true);
const [isNotifyMe, setIsNotifyMe] = useState(false);
const handleNotify = useCallback(() => {
setIsNotifyMe(!isNotifyMe);
}, [isNotifyMe]);
const handleSchedule = useCallback(() => {
setIsRunNow(!isRunNow);
}, [isRunNow]);
const WindowForm = () => {
return (
<div>
<Row>
<Col span={12}>
<label className={styles.labelWeight}> Name : </label>
<input
type="text"
value={selected.name}
readOnly={true}
className={styles.input}
/>
<br></br>
<br></br>
<label className={styles.labelWeight}>Description : </label>
<input
type="text"
value={selected.description}
readOnly={true}
className={styles.input}
></input>
<br></br>
<br></br>
<input
type="checkbox"
name="runImm"
id="runImm"
checked={isRunNow}
onChange={handleSchedule}
></input>
<label> Schedule as soon as possible</label>
</Col>
<Col span={12}>
<input
type="checkbox"
name="notifyProcess"
id="notifyProcess"
checked={isNotifyMe}
onChange={handleNotify}
></input>
<label> Notify me when this process ends</label>
<br></br>
<br></br>
<label>Submission Notes : </label>
<textarea
name="notes"
id="notes"
rows="4"
cols="50"
disabled={!isNotifyMe}
></textarea>
</Col>
</Row>
<Row>
<Col span={24}>
<Child isRunNow={isRunNow} />
</Col>
</Row>
</div>
);
};
return (
<Modal
visible={show}
width={800}
centered
maskClosable={false}
onCancel={() => setShow(false)}
title="JKM"
>
<WindowForm />
</Modal>
);
}
Child component is as follows:
import Uploader from "./Uploader";
import Downloader from "./Downloader";
const { TabPane } = Tabs;
const areEqual = (prevProps, nextProps) => {
console.log("passed here"); // THIS IS NEVER LOGGED!!
return true;
};
function Child({ isRunNow }) {
console.log(
`Rendering Childe component...isRunNow value : ${isRunNow}`
);
return (
<div>
<Tabs defaultActiveKey="ka">
<TabPane tab="ka" key="ka">
Panel
</TabPane>
<TabPane tab="sa" key="sa" disabled={isRunNow}>
<Downloader />
</TabPane>
<TabPane tab="da" key="da">
<Uploader />
</TabPane>
</Tabs>
</div>
);
}
export default React.memo(Child, areEqual);
When I check or uncheck check box Notify me, the child component Child re-renders every time. It seems props are not equal and hence its re-rending. I could not figure out where its going wrong.
Please suggest where i m doing wrong.

I suggest to you to separate the WindowForm to a component it's seems that this is the problem.
when I use it as a component the memo start to work
I think is due to this is a function that return component so it's render it no matter what.
the solution:
move WindowForm out side of the component and create a new one with it and then call it and it will work fine
const WindowForm = () => {
return (
<div>
<Row>
<Col span={12}>
<label className={styles.labelWeight}> Name : </label>
<input
type="text"
value={selected.name}
readOnly={true}
className={styles.input}
/>
<br></br>
<br></br>
<label className={styles.labelWeight}>Description : </label>
<input
type="text"
value={selected.description}
readOnly={true}
className={styles.input}
></input>
<br></br>
<br></br>
<input
type="checkbox"
name="runImm"
id="runImm"
checked={isRunNow}
onChange={handleSchedule}
></input>
<label> Schedule as soon as possible</label>
</Col>
<Col span={12}>
<input
type="checkbox"
name="notifyProcess"
id="notifyProcess"
checked={isNotifyMe}
onChange={handleNotify}
></input>
<label> Notify me when this process ends</label>
<br></br>
<br></br>
<label>Submission Notes : </label>
<textarea
name="notes"
id="notes"
rows="4"
cols="50"
disabled={!isNotifyMe}
></textarea>
</Col>
</Row>
<Row>
<Col span={24}>
<Child isRunNow={isRunNow} />
</Col>
</Row>
</div>
);
};
this component and his state to a new component and everything will start to work

Related

React on button click Add textarea

I'm making a simple resume portal and I have a textarea(1), in which user can write his/her work experience. To add more than one work experience I have set a button(Add) which basically will add a new textarea(similar to textarea(1) but without label) just below the textarea(1) and so on. This all should be done when button(Add) is clicked.
If I am able to add my code in a child component that would solve my problem I think.
Here's below what I tried:
Child component ( src -> Routes -> UserForm -> Components -> UserDetails -> index.js )
import React from 'react'
import './style.scss';
import { Row, Col } from 'react-bootstrap'
const UserDetails = () => {
return (
<>
<div className='UserDetails'>
<Row>
<Col lg='6'>
<div className='persnlInfo'>
<h4>
Personal Information
</h4>
<p>Your Name</p>
<input type="text" placeholder="Enter here" />
<p>Your Contact</p>
<input type="text" placeholder="Enter here" />
<p>Your Address</p>
<textarea className='formAddress' rows="5" cols="10" placeholder="Enter here" />
<p id='impLinks'>Important Links</p>
<p>Facebook</p>
<input type="text" placeholder="Enter here" />
<p>Instagram</p>
<input type="text" placeholder="Enter here" />
<p>Linkedin</p>
<input type="text" placeholder="Enter here" />
</div>
</Col>
<Col lg='6'>
<h4>
Professional Information
</h4>
<p>Objective</p>
<textarea className='formObjective' rows="5" cols="10" placeholder="Enter here" />
<p>Work Experience</p>
<textarea className='formObjective' rows="3" cols="10" placeholder="Enter here" />
<div className='addButton'>
<input type='button' id='addWrkExp' value='Add' />
</div>
</Col>
</Row>
</div>
</>
)
}
export default UserDetails;
Parent component ( src -> Routes -> UserForm -> index.js )
import React from 'react'
import Pages from '../../Components/HOC/Page/index'
import UserDetails from '../UserForm/Components/UserDetails/index'
class UserForm extends React.Component{
render() {
return(
<>
<Pages
showHeader
showFooter
>
<UserDetails />
</Pages>
</>
);
}
}
export default UserForm;
Output:
To obtain the said above, I've searched up on this but not getting What and Where to put logic/code exactly. If I'm not wrong this can be done using state , props , onClick() and/or something related.
I hope this is enough from my side to understand the problem. Thanks in advance for your help.
You can use a useState for the number of textareas on your page and then when button(add) gets clicked, that state gets increased by one,
This is your Child component ( src -> Routes -> UserForm -> Components -> UserDetails -> index.js )
import React, { useState } from 'react'
import './style.scss';
import { Row, Col } from 'react-bootstrap'
const UserDetails = () => {
const [ workXPs, setWorkXPs ] = useState(1);
return (
<>
<div className='UserDetails'>
<Row>
<Col lg='6'>
<div className='persnlInfo'>
<h4>
Personal Information
</h4>
<p>Your Name</p>
<input type="text" placeholder="Enter here" />
<p>Your Contact</p>
<input type="text" placeholder="Enter here" />
<p>Your Address</p>
<textarea className='formAddress' rows="5" cols="10" placeholder="Enter here" />
<p id='impLinks'>Important Links</p>
<p>Facebook</p>
<input type="text" placeholder="Enter here" />
<p>Instagram</p>
<input type="text" placeholder="Enter here" />
<p>Linkedin</p>
<input type="text" placeholder="Enter here" />
</div>
</Col>
<Col lg='6'>
<h4>
Professional Information
</h4>
<p>Objective</p>
<textarea className='formObjective' rows="5" cols="10" placeholder="Enter here" />
<p>Work Experience</p>
{[...Array(workXPs).keys()].map((workXP, i) => {
return <textarea key={i} className='formObjective' rows="3" cols="10" placeholder="Enter here" />;
})}
<div className='addButton'>
<input type='button' id='addWrkExp' value='Add' onClick={() => setWorkXPs(prev => (prev + 1))} />
</div>
</Col>
</Row>
</div>
</>
)
}
export default UserDetails;
if you wanna do more advanced and add remove buttons, check this: https://www.educative.io/blog/react-hooks-tutorial-todo-list
hope this helps 🙂
You could just make a state and change the state on the button click.
Then change the style according to the state.
Parent Component
import React from "react";
import Pages from "../../Components/HOC/Page/index";
import UserDetails from "../UserForm/Components/UserDetails/index";
class UserForm extends React.Component {
state = {
textAreas: [{ text: "" }]
};
addTextArea = () => {
let updatedTextArea = [...this.state.textAreas];
updatedTextArea.push({ text: "" });
this.setState({ textAreas: updatedTextArea });
};
render() {
return (
<>
<Pages showHeader showFooter>
<UserDetails textAreas={this.state.textAreas} clicked={this.addTextArea} />
</Pages>
</>
);
}
}
export default UserForm;
i changed <UserDetails textAreas={textAreas} clicked={this.addTextArea} /> to <UserDetails textAreas={this.state.textAreas} clicked={this.addTextArea} />
in the UserForm component
child Component
import React from 'react'
import './style.scss';
import { Row, Col } from 'react-bootstrap'
const UserDetails = (props) => {
const {textAreas, clicked} = props
return (
<>
<div className='UserDetails'>
<Row>
<Col lg='6'>
<div className='persnlInfo'>
<h4>
Personal Information
</h4>
<p>Your Name</p>
<input type="text" placeholder="Enter here" />
<p>Your Contact</p>
<input type="text" placeholder="Enter here" />
<p>Your Address</p>
<textarea className='formAddress' rows="5" cols="10" placeholder="Enter here" />
<p id='impLinks'>Important Links</p>
<p>Facebook</p>
<input type="text" placeholder="Enter here" />
<p>Instagram</p>
<input type="text" placeholder="Enter here" />
<p>Linkedin</p>
<input type="text" placeholder="Enter here" />
</div>
</Col>
<Col lg='6'>
<h4>
Professional Information
</h4>
<p>Objective</p>
<textarea className='formObjective' rows="5"
cols="10" placeholder="Enter here" />
<p>Work Experience</p>
{textAreas.map(item => (
<textarea className='formObjective' value=
{item.value} rows="3" cols="10" placeholder="Enter
here" />
)) }
<div className='addButton' onClick={clicked}>
<input type='button' id='addWrkExp' value='Add' />
</div>
</Col>
</Row>
</div>
</>
)
}
export default UserDetails;

Two dependant date pickers where start date can never be larger than end date

I am using the date-reactpicker and have a start and end date/time. It should never be possible to pick a smaller date/time in the starter picker. So the obvious is to set the MinDate and MinTime in the end date picker. However the DatePicker requires you to set both MinTime and MaxTime. And i dont want to set anything in the MaxTime. Any pointers how to solve this?
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import DatePicker, { setHours } from "react-datepicker";
import Moment from "react-moment";
import {
Row,
Col,
Card,
CardHeader,
CardBody,
Form,
FormGroup,
Label,
Input,
Button
} from "reactstrap";
const Insights = props => {
const [startDate, setStartDate] = useState();
const [endDate, setEndDate] = useState();
const [endMinDate, setEndMinDate] = useState(new Date());
const [endMinTime, setEndMinTime] = useState(new Date());
const GetSearchForm = () => {
const { register, handleSubmit, watch } = useForm();
const timePickerStyle = { width: 96, important: "true" };
const onSearch = data => {
console.log(data);
console.log(startDate);
};
const onDateStartChange = date => {
setStartDate(date);
setEndMinDate(date);
setEndMinTime(date);
alert(date);
};
const onDateEndChange = date => {
setEndDate(date);
};
return (
<Form onSubmit={handleSubmit(onSearch)}>
<Row>
<Col>
<FormGroup>
<Label for="exampleEmail">Account Id</Label>
<Input
type="number"
name="account"
id="account"
placeholder="AccountId"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="examplePassword">Email</Label>
<Input
type="email"
name="email"
id="email"
placeholder="Email"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="exampleEmail">xPage Id</Label>
<Input
type="number"
name="xpageid"
id="xpage"
placeholder="xPage Id"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="examplePassword">Content Devider Id</Label>
<Input
type="number"
name="contentdevider"
id="contentdeviderid"
placeholder="Content Devider Id"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="examplePassword">Custom Page Id</Label>
<Input
type="number"
name="custompage"
id="custompageid"
placeholder="Custom Page Id"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="examplePassword">Service</Label>
<Input
type="text"
name="servicename"
id="servicename"
placeholder="Custom Page Id"
innerRef={register}
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col xs="4">
<FormGroup>
<Label for="exampleEmail">Start</Label>
<DatePicker
isClearable
innerRef={register}
name="datetimestart"
className={"form-control"}
selected={startDate}
onChange={date => onDateStartChange(date)}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
timeCaption="time"
dateFormat="dd-MM-yyyy H:mm"
/>
</FormGroup>
</Col>
<Col xs="4">
<FormGroup>
<Label for="exampleEmail">End</Label>
<DatePicker
isClearable
innerRef={register}
name="datetimeend"
className={"form-control"}
selected={endDate}
onChange={date => onDateEndChange(date)}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
timeCaption="time"
dateFormat="dd-MM-yyyy H:mm"
maxDate={startDate}
minTime={endMinDate}
maxTime={startDate}
/>
</FormGroup>
</Col>
</Row>
<Button>Submit</Button>
</Form>
);
};
return (
<Row>
<Col xs="12" lg="12">
<Card>
<CardHeader>
<i className="fa fa-align-justify"></i> Insights
</CardHeader>
<CardBody>
<GetSearchForm></GetSearchForm>
<div>
startDate:
{startDate == null ? "" : startDate.toString()}
</div>
<div>endDate:{endDate == null ? "" : endDate.toString()}</div>
</CardBody>
</Card>
</Col>
</Row>
);
};
export default Insights;
You can check the datetime in the onDateEndChange function. For do this you need to:
- remove min and max time props from the end datepicker
- add in the onDateEndChange comparison of start date and end date; if end date will be early or similar - don't update state or update state with minimum date (start date plus one minute, for example).

Avoid form being reset

I am trying react-hook-forms. Is there anyway to not reset the form when submitting? Everytime I click submit form is reset.
import React from "react";
import { useForm } from "react-hook-form";
import {
Row,
Col,
Card,
CardHeader,
CardBody,
Form,
FormGroup,
Label,
Input,
Button
} from "reactstrap";
const Insights = props => {
const { register, handleSubmit, watch } = useForm();
const GetSearchForm = () => {
const timePickerStyle = { width: 96, important: "true" };
const onSearch = data => {
console.log(data);
};
return (
<Form onSubmit={handleSubmit(onSearch)}>
<Row>
<Col>
<FormGroup>
<Label for="exampleEmail">Account Id</Label>
<Input
type="number"
name="account"
id="account"
placeholder="AccountId"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="examplePassword">Email</Label>
<Input
type="email"
name="email"
id="email"
placeholder="Email"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="exampleEmail">xPage Id</Label>
<Input
type="number"
name="xpageid"
id="xpage"
placeholder="xPage Id"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="examplePassword">Content Devider Id</Label>
<Input
type="number"
name="contentdevider"
id="contentdeviderid"
placeholder="Content Devider Id"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="examplePassword">Custom Page Id</Label>
<Input
type="number"
name="custompage"
id="custompageid"
placeholder="Custom Page Id"
innerRef={register}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="examplePassword">Service</Label>
<Input
type="text"
name="servicename"
id="servicename"
placeholder="Custom Page Id"
innerRef={register}
/>
</FormGroup>
</Col>
</Row>
<Row>
<Col xs="4">
<FormGroup>
<Label for="exampleDate">Date</Label>
<Row>
<Col xs="8">
<Input
type="date"
name="date"
id="exampleDate"
placeholder="date placeholder"
innerRef={register}
/>
</Col>
<Col xs="3">
<Input
style={timePickerStyle}
type="time"
name="time"
id="exampleTime"
placeholder="time placeholder"
innerRef={register}
/>
</Col>
</Row>
</FormGroup>
</Col>
<Col xs="4">
<FormGroup>
<Label for="exampleDate">Date</Label>
<Row>
<Col xs="8">
<Input
type="date"
name="date"
id="exampleDate"
placeholder="date placeholder"
innerRef={register}
/>
</Col>
<Col xs="3">
<Input
style={timePickerStyle}
type="time"
name="time"
id="exampleTime"
placeholder="time placeholder"
innerRef={register}
/>
</Col>
</Row>
</FormGroup>
</Col>
</Row>
<Button>Submit</Button>
</Form>
);
};
return (
<Row>
<Col xs="12" lg="12">
<Card>
<CardHeader>
<i className="fa fa-align-justify"></i> Insights
</CardHeader>
<CardBody>
<GetSearchForm></GetSearchForm>
</CardBody>
</Card>
</Col>
</Row>
);
};
export default Insights;
Because GetSearchForm is its own component, it gets created every time Insights is being rerendered.
You call the register function with the innerRef, but since the Input changed, and it is not the same component (newly created), its getting newly registered, which resets the state.
You either can move the useForm to the GetSearchForm and pass the data back up on submit or inline the whole form in Insights.

How to independently delete dynamically-added input fields in ReactJS

I'm trying to independently delete dynamic inputs in a form in React. I have a user attribute group, and then user attribute children. I need to be able to dynamically add new user attribute groups and children, but then delete those fields without deleting ALL of the child attributes.
Right now, when I delete a child attribute, it deletes one from EACH user attribute group.
I have a working fiddle here that shows my code: https://codesandbox.io/embed/23kr654w80
import React, { Component } from "react";
import { Button, Input, Row, Col, Form, FormGroup, Label } from "reactstrap";
class OfferCriteria extends Component {
constructor(props) {
super(props);
this.state = {
single: "",
attributeSingle: [{ single: "" }],
child: "",
attributeChild: [{ child: " " }]
};
}
handleNameChange = event => {
this.setState({
name: event.target.value
});
};
handleAddSingleAttribute = () => {
this.setState({
attributeSingle: this.state.attributeSingle.concat([{ name: "" }])
});
};
handleRemoveSingleAttribute = idx => () => {
this.setState({
attributeSingle: this.state.attributeSingle.filter(
(s, sidx) => idx !== sidx
)
});
};
handleAddChildAttribute = () => {
this.setState({
attributeChild: this.state.attributeChild.concat([{ child: "" }])
});
};
handleRemoveChildAttribute = idz => () => {
this.setState({
attributeChild: this.state.attributeChild.filter(sidz => idz !== sidz)
});
};
render() {
return (
<div>
<Row>
<Col lg="10">
<hr />
</Col>
<Col lg="2" className="float-right">
<Button color="success" onClick={this.handleAddSingleAttribute}>
Add Attribute Group
</Button>
</Col>
</Row>
{this.state.attributeSingle.map(() => (
<div>
<br />
<Row>
<Col lg="2">
<Label>User Attributes</Label>
</Col>
<Col lg="3" className="float-left">
<FormGroup check inline>
<Input
className="form-check-input"
type="radio"
id="includeUserAttributes"
name="inline-radios"
value="includeUserAttributes"
/>
<Label
className="form-check-label"
check
htmlFor="inline-radio1"
>
Include
</Label>
</FormGroup>
<FormGroup check inline>
<Input
className="form-check-input"
type="radio"
id="excludeUserAttributes"
name="inline-radios"
value="excludeUserAttributes"
/>
<Label
className="form-check-label"
check
htmlFor="inline-radio2"
>
Exclude
</Label>
</FormGroup>
</Col>
<Col lg="4">
<Input
type="text"
name="text-input"
placeholder="This is parent attribute"
/>
</Col>
</Row>
<br />
<Row>
<Col lg="3">
{this.state.attributeChild.map(() => (
<div className="shareholder">
<Input
type="text"
name="text-input"
placeholder="This is child attribute"
/>
</div>
))}
</Col>
<Col lg="3" className="float-right">
{this.state.attributeChild.map(() => (
<div className="shareholder">
<Button
color="primary"
onClick={this.handleAddChildAttribute}
>
Add Attribute Child
</Button>
<br />
</div>
))}
</Col>
<Col lg="3" className="float-right">
{this.state.attributeChild.map(idz => (
<div className="shareholder">
<Button
color="danger"
onClick={this.handleRemoveChildAttribute(idz)}
>
Remove Attribute Child
</Button>
<br />
</div>
))}
</Col>
</Row>
<hr />
</div>
))}
</div>
);
}
}
export default OfferCriteria;
I need these child attributes to delete ONLY in their parent attribute group, instead of deleting all of them from all the attribute groups.
There are a couple of things going wrong with your code, but I'll focus on your initial question.
The problem is that you use the same array of child for all your groups. In order to be correct, you should include the attributeChild state into the attributeSingle objects :
{
attributeSingle: [
{
single: "",
attributeChild: [
{
child: " "
}
]
}
]
}
That way, children remain independent between groups.

How to dynamically create two inputs placeholder in render ReactJS

My class is calculating the hours between two places given the hour and minute at each place. I would like to change the <Input> part such that I don't have to repeat myself. The function time is taking two parameters the "hour" and the "minute". I tried but could not figure out. Please help.
updateTime(time, value){
let clock = this.state.time;
clock[time] = value;
this.setState(clock);
}
render(){
return(
<div>
<Card>
<CardBody>
<InputGroup>
<InputGroupAddon addonType={"prepend"}> Hour Origin</InputGroupAddon>
<Input placeholder=“1” onChange={(event)=> this.updateTime(‘hour’,event.target.value)}/>
<InputGroupAddon addonType={"prepend"}>Minute Origin</InputGroupAddon>
<Input placeholder=“1” onChange={(event)=> this.updateTime(‘minute’, event.target.value)}/>
</InputGroup>
<InputGroup>
<InputGroupAddon addonType={"prepend"}> Hour Destination</InputGroupAddon>
<Input placeholder=“1” onChange={(event)=> this.updateTime(‘hour’,event.target.value)}/>
<InputGroupAddon addonType={"prepend"}>Hour Destination</InputGroupAddon>
<Input placeholder=“1” onChange={(event)=> this.updateTime(‘minute’, event.target.value)}/>
</InputGroup>
</CardBody>
</Card>
</div>
)
}
You can package the InputGroups in components and then render the components.
Here's an example:
const HourMinuteInputGroup = props => {
const { locationName, updateTime } = props;
return (
<InputGroup>
<InputGroupAddon addonType={"prepend"}>Hour { locationName }</InputGroupAddon>
<Input placeholder="1" onChange={ event => updateTime('hour',event.target.value) }/>
<InputGroupAddon addonType={"prepend"}>Minute { locationName }</InputGroupAddon>
<Input placeholder="1" onChange={ event => updateTime('minute', event.target.value) }/>
</InputGroup>
)
}
Then your original render becomes:
render(){
return(
<div>
<Card>
<CardBody>
<HourMinuteInputGroup locationName="Origin" updateTime={ this.updateTime }/>
<HourMinuteInputGroup locationName="Destination" updateTime={ this.updateTime }/>
</CardBody>
</Card>
</div>
)
}

Resources