Render user input in a different component in React - reactjs

I am currently rendering user input in a div, in the same component as my form. I want to render the user input on another page (in my other component) instead. How can I render the user input in a different component of my app?
Structure of app:
App.js (navigation setup for react router)
3 components: Home.js, ProjectIdeas.js and Projects.js
Planning.js (the component which contains my form and currently renders the user input) is the child of Projects.js. Projects.js is where I want to render the user input.
Planning.js
constructor() {
super()
this.state = {
title: '',
features: '',
details: ''
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
})
}
handleSubmit(event) {
const {
title,
features,
details
} = this.state;
event.preventDefault();
this.setState({ title, features, details });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div>{this.state.title}</div>
<div>
<label className="label-title">
Project Title:</label>
<input name="title" id="title" type="text" onChange={this.handleChange}/>
</div>
<div>
<label className="label-features">
Features:</label>
<input name="features" placeholder="Button (directs to contact page), black footer" id="features" type="text" onChange={this.handleChange}/>
</div>
<div>
<label className="label-details">
Other details: </label>
<input name="details" placeholder="Other Details" id="details" type="text" onChange={this.handleChange}/>
</div>
<input class="submit" type="submit" value="Save plan" />
</form>
)
}
}
Projects.js
class Projects extends React.Component {
constructor(props) {
super(props)
this.state = { isEmptyState: true }
}
triggerAddPlanState = () => {
this.setState({
...this.state,
isEmptyState: false,
isAddPlanState: true
})
}
render() {
return (
<div>
{this.state.isEmptyState && <AddPlanButton addPlan={this.triggerAddPlanState} />}
{this.state.isAddPlanState && <Planning />}
</div>
)
}
}

You'll need to store the value outside the component, either in a common parent component or somewhere orthogonal.
If the components share a nearby parent, you can have the parent keep track of the state and pass it down as props to both:
function ParentComponent () {
const [value, setValue] = React.useState();
<ComponentA value={value} onChange={v => setValue(v)} />
<ComponentB value={value} onChange={v => setValue(v)} />
}
There are many other ways to do it if your app doesn't lend itself to the above solution. I'd recommend you consider using a store like Redux or possibly look at React Context. (Your scenario doesn't seem like a good use of context but you could make it work.)
Implementing your own store is not particularly complicated. Here's an example of one way to do it.

Related

Keep getting max update exceeded error but cannot seem to find error in code

I have made forms like this before but I seem to be missing something in this one. I keep getting the error "maximum update depth exceeded error" but I dont see where I am goin wrong and I've spent too much time looking at it. I already tried to change my onChange to include an arrow because others have suggested to do so , but when that happens I cant type in the input boxes. like so
onChange={()=>this.handleChange("username")}
I should note that I only get the error when I try to register the user and not when I type into the input. Here is the full error as well.
at checkForNestedUpdates (react-dom.development.js:23804)
at scheduleUpdateOnFiber (react-dom.development.js:21836)
at Object.enqueueSetState (react-dom.development.js:12468)
at Router.Component.setState (react.development.js:366)
at react-router.js:75
at listener (history.js:156)
at history.js:174
at Array.forEach (<anonymous>)
at Object.notifyListeners (history.js:173)
at setState (history.js:562)
Here is my code, please help.
import React from "React"
class Splash extends React.Component{
constructor(props) {
super(props)
this.state = this.props.user;
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.props.clearErrors();
}
handleSubmit(event) {
event.preventDefault();
this.props.signUp(this.state);
}
handleChange(field) {
return (e) => {
this.setState({ [field]: e.currentTarget.value })
};
}
render() {
return (
<div className="splash-background">
<div className="modal-screeen">
<form className="modal" onSubmit={this.handleSubmit}>
<h2 className="welcom-text"></h2>
<input className="user-input" type="text" placeholder="Name" onChange={this.handleChange("name")} value={this.state.name}/>
<input className="user-input" type="text" placeholder="Email" onChange={this.handleChange("email")} value={this.state.email}/>
<input className="user-input" type="text" placeholder="Create Username" onChange={this.handleChange("username")} value={this.state.username}/>
<input className="user-input" type="password" placeholder="Create Password" onChange={this.handleChange("password")} value={this.state.password}/>
<button>Sign Up</button>
</form>
</div>
</div>
);
}
}
export default Splash
import { connect } from "react-redux";
import { signup, login, clearErrors } from "../../actions/session_actions.js";
import Splash from "./splash";
const mapStateToProps = ({ errors }) => {
return {
errors: errors.session,
user: {
username: "",
password: "",
name:"",
email: "",
},
};
};
const mapDispatchToProps = (dispatch) => {
return {
signUp: (user) => dispatch(signup(user)),
login: (user) => dispatch(login(user)),
clearErrors: () => dispatch(clearErrors()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Splash);
I believe the problem here is the implementation of redux and react state. If you're using redux to manage the form state then I don't think there is a need to also manage that same state with react.
Try something like this, but keep in mind this code isn't tested.
class Splash extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.props.clearErrors();
}
handleSubmit(event) {
event.preventDefault();
this.props.signUp(this.props.user);
}
handleChange(e) {
// here you would have another action to update redux state depending
// on which input has changed. You can grab the input name via e.target.name
}
render() {
return (
<div className="splash-background">
<div className="modal-screeen">
<form className="modal" onSubmit={this.handleSubmit}>
<h2 className="welcom-text"></h2>
<input
className="user-input"
type="text"
placeholder="Name"
name="name"
onChange={this.handleChange}
value={this.props.user.name}
/>
<input
className="user-input"
type="text"
placeholder="Email"
name="email"
onChange={this.handleChange}
value={this.props.user.email}
/>
<input
className="user-input"
type="text"
placeholder="Create Username"
name="username"
onChange={this.handleChange}
value={this.props.user.username}
/>
<input
className="user-input"
type="password"
placeholder="Create Password"
name="password"
onChange={this.handleChange}
value={this.props.user.password}
/>
<button>Sign Up</button>
</form>
</div>
</div>
);
}
}
export default Splash;
When it comes to form data, I find it's easier to manage just with react state. Generally redux is used to manage state that is shared across the whole application/multiple components.
The problem was actually in my route util file. I had an infinite loop of rerouting!

Adding a form to Gatsby JS, with an existing template that is export default

I am attempting to follow this tutorial to add a form to Gatsby JS. I understand it if my file wasn't setup differently. Firstly the tutorials component starts like this
export default class IndexPage extends React.Component {
Where I have this
export default ({ data }) => (
Then I am asked to place the following inside of it. I tried with both the render and return portion, and without.
state = {
firstName: "",
lastName: "",
}
handleInputChange = event => {
const target = event.target
const value = target.value
const name = target.name
this.setState({
[name]: value,
})
}
handleSubmit = event => {
event.preventDefault()
alert(`Welcome ${this.state.firstName} ${this.state.lastName}!`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
First name
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleInputChange}
/>
</label>
<label>
Last name
<input
type="text"
name="lastName"
value={this.state.lastName}
onChange={this.handleInputChange}
/>
</label>
<button type="submit">Submit</button>
</form>
)
}
Here is all my code without the render and return portion
import React from 'react'
import { HelmetDatoCms } from 'gatsby-source-datocms'
import { graphql } from 'gatsby'
import Layout from "../components/layout"
export default ({ data }) => (
<Layout>
state = {
firstName: "",
lastName: "",
}
handleInputChange = event => {
const target = event.target
const value = target.value
const name = target.name
this.setState({
[name]: value,
})
}
handleSubmit = event => {
event.preventDefault()
alert(`Welcome ${this.state.firstName} ${this.state.lastName}!`)
}
<form onSubmit={this.handleSubmit}>
<label>
First name
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleInputChange}
/>
</label>
<label>
Last name
<input
type="text"
name="lastName"
value={this.state.lastName}
onChange={this.handleInputChange}
/>
</label>
<button type="submit">Submit</button>
</form>
<article className="sheet">
<HelmetDatoCms seo={data.datoCmsPricing.seoMetaTags} />
<section className="left-package-details">
<h1 className="sheet__title">{data.datoCmsPricing.title}</h1>
<p>
<span>${data.datoCmsPricing.priceAmount}</span> | <span>{data.datoCmsPricing.lengthOfSession}</span>
</p>
{data.datoCmsPricing.details.map(detailEntry => { return <li key={detailEntry.id}> {detailEntry.task}</li>})}
<p>
{data.datoCmsPricing.numberOfSessions}
</p>
book
<p>{data.datoCmsPricing.minimumMessage}</p>
</section>
<section className="right-package-details">
<img src={data.datoCmsPricing.coverImage.url} />
<div
className=""
dangerouslySetInnerHTML={{
__html: data.datoCmsPricing.descriptionNode.childMarkdownRemark.html,
}}
/>
</section>
</article>
</Layout>
)
export const query = graphql`
query WorkQuery($slug: String!) {
datoCmsPricing(slug: { eq: $slug }) {
seoMetaTags {
...GatsbyDatoCmsSeoMetaTags
}
title
priceAmount
details{
task
}
lengthOfSession
numberOfSessions
minimumMessage
descriptionNode {
childMarkdownRemark {
html
}
}
coverImage {
url
}
}
}
`
and the error I get is
There was a problem parsing "/mnt/c/Users/Anders/sites/jlfit-cms/src/templates/pricingDetails.js"; any GraphQL
fragments or queries in this file were not processed.
This may indicate a syntax error in the code, or it may be a file type
that Gatsby does not know how to parse.
File: /mnt/c/Users/Anders/sites/jlfit-cms/src/templates/pricingDetails.js
The problem you are facing is because you are trying to use state (and setState) on a functional component when the example uses a class.
Functional components don't have the same tools/syntax/APIs available to you as a class component (for better or worse) so you have to ensure you're using the correct approach for each case.
In the most recent versions of React you can have the equivalent of state and setState made available to you by using React hooks, more specifically the useState hook.
I've put together a quick working example of the code you pasted in your question converted to React hooks. You can find it on this sandbox.
I recommend you have a read over the initial parts of the React docs to ensure you're familiar with the foundational concepts or React, it will save a lot of headache in the future. 🙂

React form not filling out with fetched data

I am currently building a react application with a .net core back end. My current issue lies in a view that is meant to edit an article (which is made up of only a title and description). On componentDidMount, I am getting the route param id from the route and retrieving the article from the server with it (I've verified that this works correctly). My issue is that my form is not filling out with the fetched data. I'm of the understanding that since the form fields set to this.state... then they should update as the state updates however this is not what I'm seeing. I believe the issue is may lie with the warning I'm receiving in console:
index.js:2177 Warning: A component is changing a controlled input of
type hidden to be uncontrolled. Input elements should not switch from
controlled to uncontrolled (or vice versa). Decide between using a
controlled or uncontrolled input element for the lifetime of the
component.
I've read the documentation the warning points to and am not seeing how my component violates this.
My component is below in full:
import React, { Component } from 'react';
import CKEditor from 'react-ckeditor-component';
export class ArticlesEdit extends Component {
displayName = ArticlesEdit.name
constructor(props) {
super(props);
this.state = {
title: '',
description: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount () {
const { id } = this.props.match.params;
fetch(`https://localhost:44360/api/articles/${id}`)
.then((article) => {
this.setState({
title: article.title,
description: article.description
});
});
}
updateDescription(event){
this.setState({description: event.target.value});
}
render() {
return(
<form onSubmit={this.handleSubmit} >
<div className="form-group row" >
<label className=" control-label col-md-12" htmlFor="Title">Title</label>
<div className="col-md-4">
<input className="form-control" type="text" id="title" name="title" defaultValue={this.state.title} required />
</div>
</div >
<CKEditor activeClass="editor" content={this.state.description} events= {{"change": this.onEditorChange.bind(this) }} />
<input type="hidden" id="description" name="description" value={this.state.description} onChange={this.updateDescription}/>
<div className="form-group">
<button type="submit" className="btn btn-default">Save</button>
</div >
</form >
);
}
onEditorChange(evt){
var newContent = evt.editor.getData();
this.setState({
description: newContent
});
}
handleSubmit(event) {
event.preventDefault();
const data = new FormData(event.target);
console.log(this.state.title);
// POST request for Add employee.
fetch('https://localhost:44360/api/articles/', {
method: 'PUT',
body: data
}).then((response) => response.json())
.then((responseJson) => {
this.props.history.push("/articles");
})
}
}
You are not parsing the JSON you get as response to your fetch in componentDidMount. If you add .then((response) => response.json()) it should work as expected.
componentDidMount () {
const { id } = this.props.match.params;
fetch(`https://localhost:44360/api/articles/${id}`)
.then((response) => response.json())
.then((article) => {
this.setState({
title: article.title,
description: article.description
});
});
}
You also need to use the value prop instead of the defaultValue prop on your input so that it will have the value of title in your state.
<input
className="form-control"
type="text" id="title"
name="title"
value={this.state.title}
required
/>

Displaying from JSON stored in state

I'm trying to access an API and pull out information to use in my application. Right now, I'm fetching the data I want to use from the SWAPI, and the object gets stored in state in the way I want it to. However, I'm not able to display the JSON from the saved state. Im sure this is easy, but I haven't been able to figure it out. Here is my code:
import React, { Component } from 'react';
const APIQuery = 'https://swapi.co/api/';
class Searchbutton extends Component {
constructor(props) {
super(props);
this.state = { value: 'planets/1', data: [] };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
/*Funcionality to handle form and state of form*/
/* Changes state of value whenever the form is changed, in realtime. */
handleChange(event) {
this.setState({ value: event.target.value });
}
/* Prevents default formsubmit for now, and logs the state that is saved.*/
handleSubmit(event) {
console.log('Be with you, and this was written:' + this.state.data);
event.preventDefault();
}
handleJson(json) {
JSON.stringify(this.state.data);
}
/*Lifecycle method that fetches from API*/
componentDidMount() {
fetch(APIQuery + this.state.value)
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
return (
<div className="search_wrapper">
<form onSubmit={this.handleSubmit}>
<label>
Search:
<input
type="text"
className="search_bar"
value={this.state.value}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="May the Force" />
</form>
{this.state.data}
json goes here
</div>
);
}
}
You need to stringify the data before you show it.
You have a method that does that although it does not return anything and you also never call it.
So you could change it to
handleJson(json) {
return JSON.stringify(this.state.data); // added the return statement
}
and then when you want to display it use
{this.handleJson()}
json goes here
Or you could directly stringify before showing it
{JSON.stringify(this.state.data)}
json goes here
You can use
<pre>{JSON.stringify(this.state.data)}</pre>
Here is an example of it: https://stackblitz.com/edit/react-y8bk6f
One of the good alternate solution I found is this:
https://stackoverflow.com/a/35340052/2630817
Changes to your render method
return (
<div className="search_wrapper">
<form onSubmit={this.handleSubmit}>
<label>
Search:
<input
type="text"
className="search_bar"
value={this.state.value}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="May the Force" />
</form>
<pre>{JSON.stringify(this.state.data)}</pre>
json goes here
</div>
);

Efficient way to init state storage of forms

I'm working on a quite comprehensive form and was wondering if there is a smart way to prevent me of doing the following state initialisation:
class Demo extends React.Component {
state = { firstName = "",
secondName = "" };
//and so on...
render() {
const { firstName, secondName } = this.state;
//and so on
return (
<div>
<Form>
<Form.Input
placeholder="Name"
name="name"
value={firstName}
/>
//and so on
</Form>
</div>
);
}
}
If I don't init the state with empty strings I get the following Warning:
Component is changing an uncontrolled input of type text to be
controlled. Input elements should not switch from uncontrolled to
controlled (or vice versa). Decide between using a controlled or
uncontrolled input element for the lifetime of the component.
What's the state of the art way to deal with this?
Thanks!
Stefan
If you are using value as your state value then it will be undefined in render method in case you don't initialize state. So it is recommended to initialize your state like you're doing because you have controlled inputs. I believe this would help your case
Something like this should work but problem is you need input handlers for each fields which is cumbersome if you have huge form.
class Demo extends React.Component {
constructor(props) {
super(props)
this.state = {
firstName: '',
lastName: '',
submitted: false
};
}
handleFirstName = (e) => {
this.setState({firstName: e.target.value});
}
handleLastName = (e) => {
this.setState({lastName: e.target.value});
}
handleSubmit(e) {
e.preventDefault();
this.setState({ submitted: true });
const payload = [{this.state.firstName, this.state.lastName}];
this.props.saveData(payload);
}
render() {
return(
<form>
<label>
Firstname:
<input type="text" value={this.state.firstName} onChange={this.handleFirstName} />
</label>
<label>
Lastname:
<input type="text" value={this.state.lastName} onChange={this.handleLastName} />
</label>
<input type="submit" value="Submit" onClick={this.handleSubmit} />
</form>
);
}
}
Better solution for handling form is using lib like react-final-form

Resources