react.js how to avoid parent state update on child component cancel - javascript

In my react.js project I have parent and child components (Page like parent and Modal with input like child). I receive data in parent by ajax request and pass it for input from parent to child and fill the child state with it. Also in child component I have 2 buttons: Submit and Cancel. Conside the following code:
**Parent**
render() {
const { projectAux } = this.props;
return (
<ProjectNameFormModal
projectAux={projectAux}
onVisibleChange={e => this.onVisibleProjectNameFormModalChange(e)}
visible={this.state.editProjectNameModalVisible}
/>
)
}
**Child**
import React from 'react';
import {Button, Checkbox, Form, Input, List, Modal, Select} from "antd";
class ProjectNameFormModal extends React.Component{
constructor(props){
super(props);
this.state = {
projectAux: props.projectAux,
visible: props.visible
}
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
if(this.state.projectAux != nextProps.projectAux){
this.setState({
projectAux: nextProps.projectAux,
visible: nextProps.visible
});
}
return true;
}
handleProjectNameInputChange = e => {
let current_state = this.state;
current_state.projectAux[e.target.name] = e.target.value;
this.setState(current_state);
}
handleCancelSubmitProjectNameUpdate = e => {
this.props.onVisibleChange(false);
}
handleSubmitProjectNameUpdate = e => {
console.log(' in handleSubmitProjectNameUpdate');
this.setState({...this.state, visible: false});
this.props.onVisibleChange(false);
}
render() {
return (
<Modal
title='Edit project Name'
visible={this.props.visible}
bodyStyle={{}}//height:"800px"
onSave={{}}
maskClosable={false}
onCancel={this.handleCancelSubmitProjectNameUpdate}
footer={[
<Button key="back" onClick={this.handleCancelSubmitProjectNameUpdate}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleSubmitProjectNameUpdate}>
Save
</Button>,
]}
>
<div>
<Input placeholder="ProjectName"
name="name"
onChange={this.handleProjectNameInputChange}
value={this.state.projectAux && (this.state.projectAux.name)}
/>
</div>
</Modal>
);
}
}
export default ProjectNameFormModal;
So the problem is when I enter some new data to input and then press Cancel button, I have also my Parent state updated to the new data which I definetely dont want to happen. When Cancel is pressed, no updates to the parent state should occur but it happens. Btw, parent state does not update when I enter new symbols into input. I tried to use spread operator as it is said here but it did not work.
Any ideas how to fix it would be welcome, thank you.

Related

Changing state of one component from another component in another file

I am new to React and web dev in general.
I have created a Component containing list of calculator like buttons which is stored in Buttons.js.
There is another component called as Submit stored in Submit.js. Submit component is basically a textbox in which we type a mathematical expression which I want to process later.
Both of these components are then called in another component call Leftwindow.js.
So my question is,
How can I make clicking in Buttons component affect the textbox in Submit component. I know it could be done easily had the buttons and input box been the part of a single component.
Basically if I press the '1' button I want it to be added to the input box.
A snapshot of how it looks -
Overview
Code for Buttons.js -
class Buttons extends Component {
constructor(props){
super(props);
this.state = {
//buttonrows//
};
}
render(){
const row1elems = this.state.row1.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row2elems = this.state.row2.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row3elems = this.state.row3.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row4elems = this.state.row4.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
return (
<div className="center">
<ButtonGroup>
{row1elems}
</ButtonGroup>
<ButtonGroup>
{row2elems}
</ButtonGroup>
<ButtonGroup>
{row3elems}
</ButtonGroup>
<ButtonGroup>
{row4elems}
</ButtonGroup>
</div>
);
}
}
export default Buttons;
Code for Submit.js -
class Submit extends Component{
constructor(props){
super(props);
this.state = {
fx: ''
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event){
const target = event.target;
const val = target.val;
const name = target.name;
this.setState({
[name]: val
})
}
handleSubmit(event){
}
render(){
return(
<div>
<Form onSubmit={this.handleSubmit}>
<FormGroup row>
<Col md={12}>
<Input type="text" id="fx" name="fx" placeholder="Type Here" value = {this.state.fx} onChange={this.handleInputChange} />
</Col>
</FormGroup>
</Form>
<Button type="submit" color="primary">Differentiate</Button>
</div>
)
}
}
export default Submit;
Code for LeftWindow.js --
import React, { Component } from 'react';
import Buttons from './Buttons';
import './Custom.css';
import Submit from './Submit';
class LeftWindow extends Component{
constructor(props){
super(props);
}
render(){
return(
<div className="col-3 bg-dark fullheight">
<Buttons/>
<h3 className="center">Enter the function to be differentiated</h3>
<Submit/>
</div>
);
}
}
export default LeftWindow;
This is how your common ancestor file will look like -
Added state having input.
Added a callback "handleInputChange" which updates this input state.
Passed this callback to Both the components.
In your Submit.js file you will need to change your input tag with this
<Input
type="text"
value={this.props.input}
onChange={e => {
this.props.handleInputChange(e.target.value);
}}
/>
Also in your Buttons.js file call this.props.handleInputChange on Button click.
<Button
onClick={() => {
this.props.handleInputChange(button.label)
}}
color={colours[button.type]}
className="buttonsize"
>
{button.label}
</Button>
That's it.
Hope I could help!
In React you work with tree of components where data can travel from top to bottom. It is possible to notify parent component about changed data via passed callbacks. If you have two components that have common ancestor, you can share data between them through this common ancestor.
Let's say you have component Parent which renders your Buttons and Submit components. If you store your data (or state) in Parent and pass this data as props with callbacks, your components then can notify Parent about things happened and parent can change it's state and pass new state as props to children.
There is a "state management" solutions when your data lives detached of your components and injected on one by one basis. In such you won't need parent to store the data, but if we talk about pure react - to share data between branches in react tree, this branches should have common ancestor somewhere in the tree.

React JS Nested elements both use same onClick event and child event is not working

I have a todos list that I am working on. I want to be able to mark the task complete by clicking the checkbox or anywhere in the div of that todo. However, I get a warning when I only have the onClick event on the parent component
Here is the component code that works but gives me a warning:
render(){
const {todo, handleClick} = this.props;
const className = this.getClassName(todo.complete)
return (
<div
className={className}
onClick={handleClick}
>
<input
type="checkbox"
className="todo-checkbox"
checked={todo.complete}
/>
<span
className='todo-text'>
{todo.text}
</span>
</div>
)
}
}
and here is the warning:
index.js:1375 Warning: Failed prop type: You provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly.
To fix this I used the suggested e.stopPropagation(); and added an onChange event to the child element. However, now only the parent div is working, so I can change successfully mark a todo anywhere in the div except for the checkbox. I think this is because they share the same method that it's not separating them as two different events.
stopBubbling = (e) => {
e.stopPropagation();
}
handleChange = (e, key) => {
this.stopBubbling(e)
this.setCompletebyId(e, key)
}
setCompletebyId = (e, key) => {
const { todos } = this.state;
const index = key - 1;
const complete = todos[index].complete;
todos[index].complete = !complete;
this.setState({
todos
})
}
Any help is appreciated!
Have you tried putting the onClick in your first example on the input itself and not the div?
From React's perspective, it assumes the input is "uncontrolled" and the user cannot interact with it. So the warning is providing options if that is the case. But in this scenario, you want it to be controlled. It was working because the click event on the input checkbox would bubble up to the div and still invoke the click handler.
class Checkbox extends React.Component {
render() {
const { todo, handleClick } = this.props;
return (
<label>
<input
type="checkbox"
className="todo-checkbox"
onClick={handleClick}
checked={todo.complete}
/>
{todo.text}
</label>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todo: { complete: false, text: "TODO" }
};
}
handleClick = () => {
this.setState({ todo: { ...this.state.todo, complete: !this.state.todo.complete } });
};
render() {
const { todo } = this.state;
return <Checkbox todo={todo} handleClick={this.handleClick} />;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I think putting
onChange = {handleClick}
inside the input tag might help as a checkbox expects an onChange function.

How to call parent function in child component

I checked quite a lot of examples but found most of them having events fired in Child Component.
Can someone please suggest how can I call the Parent Component's function in child with the click event on Parent Component? Thanks.
Parent Component (app.js):
Class App extends Component {
handleClick = (e, id, text) => {
e.preventDefault();
this.setState({val: text})
}
render() {
return (
<div>
<Form val={this.state.val} Click={this.handleClick.bind(this) }/>
<Button onClick={(e) => this.handleClick(e, todo.id, todo.text)}>
<Icon>edit_icon</Icon>
</Button>
</div>
)
}
}
Child Component (form.js):
this.props.Click(); //where should i call this function since my button is in parent component
Class Form extends Component{
render() {
const { text } = this.state;
return (
<TextField
value={text}
color="secondary"
/>
)
}
}
}
If you want to call it in your Child component, you need an event to trigger it or maybe a condition. So, for example in your form.js, we will trigger it with a button click form your child component
render() {
const { text } = this.state;
return (
<TextField
value={text}
color="secondary"
/>
<Button onClick={this.props.Click} />
)
}
}
Maybe, using a Button in your child component is not a great choice for your case since you already have a Button to call the Click function in your parent component, this Button in child component I made is only for example
One way you can do this is use a ref to call the function..
// create the ref
constructor() {
super();
this.myFormRef = React.createRef()
}
// click handler for the button
handleClick = (e, id, text) => {
// here you have the child instance which gives you access to its functions
this.myFormRef.someChildMethodThatIsOnTheChildClass()
}
render() {
// notice here we use the ref on the form via... ref={this.myFormRef}
return (
<Form val={this.state.val} ref={this.myFormRef} Click={this.handleClick.bind(this) }/>
<Button onClick={(e) => this.handleClick(e, todo.id, todo.text)}>
<Icon>edit_icon</Icon>
</Button>
)
)
I would like to note though that it doesn't seem to make much sense as to why you want to do this. You should probably re-think your architecture. Also what is the button press supposed to be doing? submitting the form?

Update both state and props in react-redux form on same Submit button click

I am very new to react so it could get lousy.
I started with wizard form from https://redux-form.com/7.2.3/examples/wizard/ and to trying to submit form and change page on same submit click.
I mean, submit form on WizardFormSecondPage and change page to WizardFormThirdPage to show values. Up to now I managed to submit form and change page but separately and struggling to connect both events on same button click.
Maybe it is possible to validate props with statement in
and trigger .state?
(Of course syntactically it is wrong but just trying to show idea)
class WizardForm extends Component {
constructor(props) {
super(props)
this.nextPage = this.nextPage.bind(this)
this.previousPage = this.previousPage.bind(this)
this.state = {
page: 1
}
}
nextPage() {
this.setState({ page: this.state.page + 1 })
}
previousPage() {
this.setState({ page: this.state.page - 1 })
}
render() {
const { onSubmit } = this.props
const { page } = this.state
return (
<div>
{page === 1 && <WizardFormFirstPage onSubmit={this.nextPage} />}
{page === 2 && (
<WizardFormSecondPage
previousPage={this.previousPage}
if(onSubmit={onSubmit}) {
this.nextPage
}
/>
)}
{page === 3 && (
<WizardFormThirdPage
/>
)}
</div>
)
}
}
WizardForm.propTypes = {
onSubmit: PropTypes.func.isRequired
}
export default WizardForm
UPDATE:
So another idea would be render third page from showResults.js that is called from index.js on <WizardForm onSubmit={showResults} />
import React from "react";
const testField = () => (
<div>
<h1>TEST</h1>
</div>
);
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
export default (async function showResults(values) {
await sleep(500); // simulate server latency
console.log(`You submitted:\n\n${JSON.stringify(values, null, 2)}`);
testField;
});
By default, showResults return console.log with submitted fields info, now i am trying just to render plain div but without any decent results. May someone tell where the point is missed and what could be best practices to create showResults component?
https://github.com/final-form/react-final-form has solved my problem, it is much more functional and easier to use.
you can create a:
class WizardFormSecondPage extends Component {
constructor(props) {
super(props);
this.myHandleSubmit = this.myHandleSubmit.bind(this)
}
myHandleSubmit(){
this.props.handleSubmit()
this.props.nextPage()
}
....
....
<Form onSubmit={this.myHandleSubmit}>
}
So Basically pass that custom Handler to your form or button!

React: Give back the child new inserted element to parent for update the state

I have a parent component in React who display a list of items, also a state of (selectedList) who give an active class to this list item + display others components depending of the active class. This is the parent component.
In a child, I display the form where I can set a new list and an event onSubmit where I insert it in a Collection (meteor+mongo)
The problem is I can't make a relation between the new item (id) and the parent component cause I would like to select the list newly created (so give active class and display other components). Then I think I should update the state.selectedListId but I don't know how in a child, I can send it to the parent component ?
Here is few lines of codes:
PARENT ELEMENT (PAGE)
class TodosPage extends Component {
constructor(props) {
super(props);
this.state = {
listSelected: "",
};
}
selectList(listId) {
this.setState({ listSelected: listId });
}
renderLists() {
return this.props.lists.map((list) => (
<List
selectedItemId={this.state.listSelected}
selectList={() => this.selectList(list._id)}
key={list._id}
list={list}
countPendingTasks={this.countPendingTasks(list._id)}
/>
));
}
render() {
return (
<div className="container">
<ListForm />
<ListGroup>
{this.renderLists()}
</ListGroup>
CHILD ELEM (ListForm)
handleSubmit(event) {
event.preventDefault();
const name = ReactDOM.findDOMNode(this.refs.nameInput).value.trim();
Meteor.call('lists.insert', name, (err, listId) => {
console.log("in method insert = " + listId);
// HERE I CAN HAVE THE GOOD ID
});
ReactDOM.findDOMNode(this.refs.nameInput).value = '';
}
render() {
return (
<Form bsClass="col-xs-12" onSubmit={this.handleSubmit.bind(this)} >
<FormGroup bsClass="form-group">
<FormControl type="text" ref="nameInput" placeholder="Add New List" />
</FormGroup>
</Form>
);
}
Then, I can have the good ID in HandleSubmit but I don't know how to give it back to the parent component ..
Thanks for help
Have the parent (TodosPage) pass a function as a prop to its child (ListForm). Then onSubmit, have ListForm call the function.
class TodosPage extends React.Component {
handleListFormSubmit = (goodId) => {
// do something with goodId
}
render() {
return <ListForm onSubmit={this.handleListFormSubmit} />;
}
}
class ListForm extends React.Component {
handleSubmit = (event) => {
// get GOOD ID from the form, then call the parent function
// [...]
this.props.onSubmit(goodId);
}
render() {
<Form onSubmit={this.handleSubmit}>
{/* form stuff here */}
</Form>
}
}
In fact, it was pretty simple,
I just used my SelectList function and like #Ty Le said sent it to the child via prop, but to set the new state, I have to add in my parent constructor:
this.selectList = this.selectList.bind(this);
Or I get an error: this.setState is undefined ..
Thanks

Categories