How to redirect in react router in self page. e.g I have a component ProfileUpdate it updates name & stores to backend. After successful update I want to redirect to same page but it's not working. Here is a sample code.
class ProfileUpdate extends Component {
constructor() {
super();
this.state = {
name: ''
};
}
onSubmit(e) {
e.preventDefault();
this.props.history.replace('/');
}
onChange(e) {
const name = e.target.value;
this.setState(() => ({
name
}))
}
render() {
return (
<form onSubmit={this.onSubmit}>
<div>
<h4>Display Name:</h4>
<div>
<input name='displayName' placeholder='Display name' type='text' value={this.state.name} onChange={this.onChange} />
</div>
</div>
<div>
<button type='submit'>Update</button>
</div>
</form>
);
}
}
Expected behaviour if I update name then click submit it will replace the route/redirect & name input should be empty.
But when I choose a different path to replace it works. e.g this.props.history.replace('/users') it works. But here this.props.history.replace('/'); it doesn't work.
Related
//App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
formVal: {},
showMaker: true,
showTemplate: false,
uniMakerArr: [],
uniTemplateArr: [],
this.handleChange = this.handleChange.bind(this);
this.onClickMaker = this.onClickMaker.bind(this);
this.onClickTemplate = this.onClickTemplate.bind(this);
this.addUniFn = this.addUniFn.bind(this);
handleChange = (e) => {
let formVal = this.state.formVal;
let name = e.target.name;
let val = e.target.value;
formVal[name] = val;
this.setState({formVal})
};
addUniFn(e) {
const {uniMakerArr, uniTemplateArr} = this.state;
uniMakerArr.push(<UniMaker key={uniqid()} />);
uniTemplateArr.push(<UniTemplate key={uniqid()} />)
this.setState({
uniMakerArr, uniTemplateArr
});
};
onClickMaker(e) {
this.setState({
showMaker: true,
showTemplate: false,
});
};
onClickTemplate(e) {
this.setState({
showMaker: false,
showTemplate: true,
});
};
render() {
const {firstName, lastName} = this.state.formVal;
const {showMaker, showTemplate, uniMakerArr, uniTemplateArr} = this.state;
return (
<div>
<header className="header">
Curriculum Vitae!
</header>
<nav className="nav">
<a onClick={this.onClickMaker}>Maker</a>
<div className='vl'></div>
<a onClick={this.onClickTemplate}>Preview</a>
</nav>
<div>
{showMaker ? <Maker
handleChange={this.handleChange} add={this.addUniFn}
uniMakerState={uniMakerArr} /> : null}
{showTemplate ? <Preview
uniMakerArr={uniMakerArr}
uniTemplateArr={uniTemplateArr}
//state value for object "formVal"
firstName={firstName}
lastName={lastName}/> : null}
</div>
</div>
);
}
}
export default App;
Here is component Maker:
class Maker extends Component {
constructor(props) {
super(props);
}
render() {
const {handleChange, formVal, add, uniMakerState} = this.props;
return(
<div className='maker'>
<p className="headerText">Personal Information</p>
<form className='personalInp'>
<label htmlFor='firstName'></label>
<input type="text" name="firstName" value={formVal} onChange={handleChange} placeholder="First Name"></input>
<label htmlFor='lastName'></label>
<input type="text" name="lastName" value={formVal} onChange={handleChange} placeholder="Last Name"></input>
{uniMakerState.map((component) => {
return <UniMaker formVal={formVal} handleChange={handleChange}
key={component.key} }/>
})}
<button type="button" className="addBtn" onClick={add}>Add</button>
</form>
<div/>
)
}
}
export default Maker;
Here is component Preview:
class Preview extends Component {
constructor(props) {
super(props);
}
render() {
const {firstName, lastName,uniTemplateArr} = this.props;
return (
<div className='template'>
<p className="name">{firstName || 'Your'} {lastName || 'Name'}</p>
{uniTemplateArr.map(component => <UniTemplate />)}
</div>
)
}
}
export default Preview;
So what this basically does is add two-component when clicking the add button to Maker and Preview tabs. These two components are child components to Maker and Preview components. And they are identical to these two components. This means They will add two more HTML input elements and the fields where the values can be shown.
But the problem is when I make changes in the form that is in the Maker tab. It only reads and changes the value for the first two fields that are in the Preview tab. But ignores the other dynamically added fields. How can I make changes to my code so that the dynamically added input fields relate to the other component that was also added dynamically and only changes values to the corresponding input elements to the divs field?
I've tried putting unique keys to the input elements. But I don't know how to use those keys to update the values for the correct fields. Lastly, I'm sorry if the code is not so readable and the question is confusing. This is my first question, and I tried to be as thorough and transparent as possible. Thanks!
I am getting the error: Uncaught TypeError: Cannot read properties of undefined (reading 'state') even though state is defined in the constructor of my React component. I get the error at the line where I set the value of the <input> to {this.state.deckName}
export class DeckForm extends React.Component {
constructor(props) {
super(props);
this.state = {
deckName: '',
deckList: ''
};
// Bind our event handler methods to this class
this.handleDeckNameChange = this.handleDeckNameChange.bind(this);
this.handleDeckListChange = this.handleDeckListChange.bind(this);
this.handleSubmission = this.handleSubmission.bind(this);
}
// Event handler method to update the state of the deckName each time a user types into the input form element
handleDeckNameChange(event) {
let typed = event.target.value;
this.setState({ deckName: typed });
}
// Event handler method to update the state of the deckList each time a user types into the textarea from element]
handleDeckListChange(event) {
let typed = event.target.value;
this.setState({ deckList: typed });
}
// Event handler method to handle validation of deckName and deckList
handleSubmission(event) {
console.log(`${this.state.deckName}`);
console.log(`${this.state.deckList}`)
}
render() {
return (
<form className='was-validated'>
<this.DeckName />
<this.DeckList />
<button type='submit' className='btn-lg btn-warning mt-3'>Create</button>
</form>
);
}
DeckName() {
return (
<div className='form-group mb-3'>
<input
value={this.state.deckName} /* ERROR HERE */
onChange={this.handleDeckNameChange}
type='text'
placeholder='Deck name'
className='form-control'
required
/>
</div>
);
}
DeckList() {
let format = 'EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3'
return (
<div className='form-group'>
<textarea
value={this.state.deckList}
onChange={this.handleDeckListChange}
className='form-control'
rows='15'
required
>
{format}
</textarea>
</div>
);
}
}
Use below code it's working for me
https://codesandbox.io/s/weathered-water-jm1ydv?file=/src/App.js
DeckName() {
return (
<div className="form-group mb-3">
<input
value={this?.state.deckName} /* ERROR HERE */
onChange={this?.handleDeckNameChange}
type="text"
placeholder="Deck name"
className="form-control"
required
/>
</div>
);
}
DeckList() {
let format =
"EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3";
return (
<div className="form-group">
<textarea
value={this?.state.deckList}
onChange={this?.handleDeckListChange}
className="form-control"
rows="15"
required
>
{format}
</textarea>
</div>
);
}
You can use es6 function to return components which exist outside of parent rather than using method,change only this part of code:
1.instead of DeckName(){...}use DeckName =()=>{....}
2.instead of DeckList(){...}use DeckList =()=>{....}
Full modified code:
import React, { Component } from "react";
export class DeckForm extends Component {
constructor(props) {
super(props);
this.state = { deckName: "", deckList: "" };
// Bind our event handler methods to this class
this.handleDeckNameChange = this.handleDeckNameChange.bind(this);
this.handleDeckListChange = this.handleDeckListChange.bind(this);
this.handleSubmission = this.handleSubmission.bind(this);
}
// Event handler method to update the state of the deckName each time a user types into the input form element
handleDeckNameChange(event) {
let typed = event.target.value;
this.setState({ deckName: typed });
}
// Event handler method to update the state of the deckList each time a user types into the textarea from element]
handleDeckListChange(event) {
let typed = event.target.value;
console.log(typed);
this.setState({ deckList: typed });
}
// Event handler method to handle validation of deckName and deckList
handleSubmission(event) {
console.log(`${this.state.deckName}`);
console.log(`${this.state.deckList}`);
}
render() {
return (
<form className="was-validated">
<this.DeckName />
<this.DeckList />
<button type="submit" className="btn-lg btn-warning mt-3">
Create
</button>
</form>
);
}
DeckName = () => {
return (
<div className="form-group mb-3">
<input
value={this.state.deckName}
onChange={this.handleDeckNameChange}
type="text"
placeholder="Deck name"
className="form-control"
required
/>
</div>
);
};
DeckList = () => {
let format =
"EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3";
return (
<div className="form-group">
<textarea
value={this.state.deckList}
onChange={this.handleDeckListChange}
className="form-control"
rows="15"
required
>
{format}
</textarea>
</div>
);
};
}
Live Demo:
https://codesandbox.io/s/brave-hill-l0eknx?file=/src/DeckForm.js:0-2091
Another way to solve the issue is defining DeckName() method using arrow function. Here's a code snippet I tried with react 17.0.2 which worked perfectly fine for me.
It's always recommended to use arrow function to define methods in class based components, since arrow function inherit "this" from the block its called from, so you also don't have to do .bind(this) whenever you call methods.
JSX
import React, { Component } from 'react'
class Test extends Component {
constructor(props) {
super(props);
this.state = {
deckName: '',
deckList: ''
};
}
DeckName = () => {
return (
<div className='form-group mb-3'>
<input
value={this.state.deckName} /* ERROR HERE */
onChange={this.handleDeckNameChange}
type='text'
placeholder='Deck name'
className='form-control'
required
/>
</div>
);
}
render() {
return (
<form className='was-validated'>
<this.DeckName />
<button type='submit' className='btn-lg btn-warning mt-3'>Create</button>
</form>
);
}
}
export default Test
ContactData.js
import React, { Component } from 'react';
import Button from '../../../components/UI/Button/Button';
import classes from './ContactData.module.css'
class ContactData extends Component {
state = {
name: '',
age: '',
address: {
street: '',
postalCode: ''
}
}
orderHandler = () => {
console.log(this.props.ingredients)
}
render() {
console.log(this.props.ingredients);
return (
<div className={classes.ContactData}>
<h4>Enter Your Contact Data</h4>
<form>
<input type="text" name="name" placeholder="Your Name" />
<input type="email" name="email" placeholder="Your Mail" />
<input type="text" name="street" placeholder="Street" />
<input type="text" name="postal" placeholder="Postal Code" />
<Button btnType="Success" clicked={this.orderHandler}>ORDER</Button>
</form>
</div>
);
}
}
export default ContactData;
CheckOut.js
import React from 'react';
import CheckoutSum from '../../components/Checkout/Checkout'
import { Route } from 'react-router-dom';
import ContactData from '../../container/Checkout/ContactData/ContactData'
class Checkout extends React.Component {
state = {
ingredients: {
salad: 1,
meat: 1,
cheese: 1,
bacon: 1
}
}
componentDidMount() {
const query = new URLSearchParams(this.props.location.search);
const ingredients = {};
for (let param of query.entries()) {
ingredients[param[0]] = parseInt(param[1]);
}
// console.log(ingredients);
this.setState({ ingredients: ingredients });
}
cancelHandle = () => {
this.props.history.goBack();
}
continueHandle = () => {
this.props.history.replace('/checkout/contact-data');
}
render() {
console.log(this.props)
console.log(this.state.ingredients)
return (
<div>
<CheckoutSum
cancel={this.cancelHandle}
continue={this.continueHandle}
ingredients={this.state.ingredients}
/>
<Route
path={this.props.match.path + '/contact-data'}
exact
render={() => (<ContactData ingredients={this.state.ingredients} />)} />
</div>
);
}
}
export default Checkout;
Problem is when I hit Order button in CotanctData component the page reloads and for some reason my URL changes to this http://localhost:3000/checkout/contact-data?name=&email=&street=&postal= and then Checkout component renders again and for some reason componentDidMount fires twice. At last I was expecting the printing of ingredients object.
Also, I'm using search query in URL to change the state of checkout component
Full Project At-https://github.com/aniket-hue/Burger-App-React/tree/Routes
Please bear with me if you don't like the question I didn't know how to frame the question.
You need to either:
Call e.preventDefult() on the <form>'s submit event.
OR, add <button type="button"> to your button to make it not submit your form.
You have to add preventDefault to your orderHandler method, beacause if button in form has no type, the type is automaticlly submit, so on every click your submitting a form. Also can you add type="button" to your button inside form.
orderHandler = e => {
e.preventDefault();
console.log(this.props.ingredients);
}
I am having a dilemma as to where Where My Form State Should Live.
React Docs mention:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the
components that need the state in the hierarchy). Either the common
owner or another component higher up in the hierarchy should own the
state. If you can’t find a component where it makes sense to own the
state, create a new component simply for holding the state and add it
somewhere in the hierarchy above the common owner component.
I have a simple comment UI.
The user enters a comment in the text area.
The user enters name in input field.
The user clicks post and the comment is displayed below.
Components Highrarchy is as follows:
- CommentSection.jsx ---> main (should "Post" state (?))
-comment-post.jsx ---> User Comment Form
- comment-table.jsx
- comment.jsx
- comment-avatar.jsx
- comment-body.jsx
Issue: In comment-post.jsx the input and textarea fields have onChange() event handler, and there is also a click event handle on submit post button. I can choose to do one of two:
In comment-post.jsx when onChange is trigger send the commentBody to CommentSection.jsx The issue here would be I will be sending the commentBody as soon as user types, then sending name later when it triggers and so on
In comment-post.jsx when onChange is trigger SAVE the value in state, then name the name field in the state, when user clicks submit button send to CommentSection.jsx Benefit is Fields are set first in state, and when the user clicks post they are sent to the parent (As it should be right?)
Should the input fields of the comment i.e. commentBody and Name be
saved in the comment-post.jsx (form component) or the parent?
Right now the state I am doing the 2. Saving form fields in state and then sending the state values on submit.
I think the issue is onChange and onClick are two different handlers, the question is which one should pass the values to parent component ideally?
class CommentSection extends Component {
state = {
posts: []
};
handleCommentPost = post => {
const posts = this.state.posts;
posts.push(post);
this.setState({ posts });
};
render() {
console.log("comment:", this.state.posts);
return (
<React.Fragment>
<h1>comments</h1>
<div className="row bootstrap snippets">
<div className="col-md-6 col-md-offset-2 col-sm-12">
<div className="comment-wrapper">
<Comment_Post onClick={this.handleCommentPost} />
<Comment_Table comments={this.state.posts} />
</div>
</div>
</div>
</React.Fragment>
);
}
}
class Comment_Table extends Component {
render() {
const posts = this.props.comments;
let count = 0;
return (
<div className="panel panel-info">
<hr />
<ul className="media-list">
{posts.map(post => (
<Comment key={count++} comment={post.commentBody} />
))}
</ul>
</div>
);
}
}
class Comment extends Component {
render() {
return (
<li className="media">
<Comment_Avatar userAvatar={this.props.commentAvatar} />
<Comment_Body userComment={this.props.comment} />
</li>
);
}
}
class Comment_Body extends Component {
render() {
const { userComment } = this.props;
return (
<div className="media-body">
<span className="text-muted pull-right">
<small className="text-muted">30 min ago</small>
</span>
<strong className="text-success">#MartinoMont</strong>
<p>
{userComment}
{}
</p>
</div>
);
}
}
class Comment_Post extends Component {
state = {
commentBody: null
};
onCommentChange = e => {
this.setState({ commentBody: e.target.value });
};
onCommentPost = e => {
const commentBody = this.state.commentBody;
if (commentBody !== null) {
this.props.onClick({ commentBody });
}
};
onNameInput = e => {};
onCommentPostError() {}
render() {
return (
<React.Fragment>
<div className="panel-heading p-heading">Comment panel</div>
<div className="panel-body">
<textarea
onChange={this.onCommentChange}
className="form-control"
placeholder="write a comment..."
rows="3"
/>
<label htmlFor="fname" onChange={this.onNameInput}>
Name:{" "}
</label>
<input id="fname" placeholder="John" />
<br />
<button
type="button"
className="btn btn-info pull-right"
onClick={this.onCommentPost}
>
Post
</button>
<div className="clearfix" />
</div>
</React.Fragment>
);
}
}
class Comment_Avatar extends Component {
render() {
return (
<a href="#" className="pull-left">
<img
src="https://bootdey.com/img/Content/user_1.jpg"
alt=""
className="img-circle"
/>
</a>
);
}
}
I think the issue is onChange and onClick are two different handlers, the question is which one should pass the values to parent component ideally?
There are two types of Forms that we use in standard User interface design. First is when any change in the Input saves the change. Second, when after making changes to the Form Elements, you press a submit button and then the changes are saved.
Since you have implemented the second type, your onChange should deal with controlling your TextArea state and your onClick should deal with the submit. So your code is just fine.
I am having a dilemma as to where Where My Form State Should Live.
That depends...In your case you only have one Form, and two Form Elements none of which are re-usable. So these uncontrolled Forms work well for you. However, if you wanted to have a Form Component that is re-usable, or if you wanted to have a Form with 15 Fields in it, you would not want to write a separate onChange handler for each one of them. For this, you would want to make a controlled Form Component that can handle all these things for you. Here's an example of what that would look like.
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
values: {}
};
}
#boundMethod
handleSubmit(event) {
event.preventDefault();
this.props.submit(this.state.values);
}
#boundMethod
handleChange(event) {
const { name, value } = event.target;
const newValues = Object.assign(
{ ...this.state.values },
{ [name]: value }
);
this.setState({
values: newValues
});
}
public render() {
const { values } = this.state;
return (
<form onSubmit={this.handleSubmit} noValidate={true}>
<div>
{React.Children.map(
this.props.children,
child => (
{React.cloneElement(child, {
value: values[child.props.name],
onChange: this.handleChange
})}
)
)}
<div>
<button type="submit">
Submit
</button>
</div>
</div>
</form>
);
}
}
Then you will be able to use this Form class like so:
<Form
submit={values => {
/* work with values */
}}
>
<input type="hidden" name="name" />
<input type="hidden" name="rating" />
</Form>;
I am trying to create a customer details form in react (currently using react-json-form) where I can reuse the values in the inputs to create a saved file that the app can refer to. I have created the form and can output the results but I am unsure how to save the input values for future use or call them back once they are saved.
If anyone has any suggestions or examples of a form that does this then I would be greatly appreciative.
My code is as follows:
import React, { Component } from 'react';
import JSONTree from 'react-json-tree';
import { BasicForm as Form, Nest, createInput } from 'react-json-form';
const Input = createInput()(props => <input type="text" {...props} />);
const UserFields = () => (
<section>
<h3>User</h3>
<div>Name: <Input path="name" /></div>
<div>Email: <Input path="email" /></div>
</section>
);
export default class ExampleForm extends Component {
state = { data: {} };
updateData = data => this.setState({ data });
render() {
return (
<Form onSubmit={this.updateData}>
<Nest path="user">
<UserFields />
</Nest>
<button type="submit">Submit</button>
<JSONTree data={this.state.data} shouldExpandNode={() => true} />
</Form>
);
}
}
A more simple solution would be to use a form, like a semanti-ui-react form, store the information to the state onChange, then convert the info to JSON for storage.
import { Form, Button } from 'semantic-ui-react'
export default class App extends Component {
constructor() {
super()
this.state = {
name: "",
email: ""
}
}
handleChange = (e, {name, value}) => {
console.log(name, value)
this.setState({[name]: value})
}
render() {
return (
<div>
<Form onSubmit={this.sendDataSomewhere}>
<Form.Field>
<Form.Input name="name" value={this.state.name} onChange={this.handleChange}/>
</Form.Field>
<Form.Field>
<Form.Input name="email" value={this.state.email} onChange={this.handleChange}/>
</Form.Field>
<Button type="submit">Submit</Button>
</Form>
</div>
)
}
}
I use a dynamic method of receiving the input from different fields using the name and val attributes. The values captured in state are then accessible by this.state.whatever
Hope this helped