React form not filling out with fetched data - reactjs

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
/>

Related

React form behaving strange with this.handleChange and Axios

SOLVED
I have a React/Typescript app which has a form that I'm using in a website that renders movies we've seen from a Mongo database. I have it set up so that we can edit one of the movies in the database through an edit form. Everything goes very smoothly but for the first field which is the movie title. Whenever I change that field it immediately stops me from doing anything else and I have to click the field again.
I.E. initial title: "Foo" -> "Fo" -> click -> "F" -> click -> "" -> click -> "B" -> click -> "Ba" -> click -> "Bar".
All other fields in the don't have this behavior. As far as I can tell all input forms are the same (type: text) and there should not be any difference. I can type full sentences without a pause in any other input field but no matter what I do, the first one doesn't allow me to. Why?
const EditBody = styled.div`{blablabla}`;
export interface Props {
match: {
params: {
title: string
}
},
history: any
}
type MovieObject = {
comments: string,
imdb: string,
mtc: string,
rtc: string,
smn: string,
title: string
}
class Edit extends Component<Props, { movie: any }> {
constructor(props: any) {
super(props);
this.state = {
movie: [
{
comments: '',
imdb: '',
mtc: '',
rtc: '',
smn: '',
title: '',
pictureUrl: '',
__v: Number,
_id: String
}]
};
this.handleChange = this.handleChange.bind(this);
};
componentDidMount() {
this.callServerForDatabase()
.then(data => this.setState({ movie: data }))
.catch(err => console.log(err));
};
callServerForDatabase = async () => {
const { match: { params } } = this.props;
const response = await fetch(`/edit/${params.title}`);
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
};
handleChange(e: any) {
e.preventDefault();
const name = e.target.name;
const value = e.target.value;
let thisStateMovieClone: MovieObject[] = [...this.state.movie];
let updatedMovieObj: MovieObject = {
...thisStateMovieClone[0],
[name]: value
}
thisStateMovieClone[0] = updatedMovieObj;
this.setState({
movie: thisStateMovieClone
})
}
handleUpdate = (e: any) => {
e.preventDefault();
axios.post(`/edit/${this.state.movie.title}`, this.state.movie)
.then(() => this.props.history.push("/database"));
};
handleDelete = (e: any) => {
e.preventDefault();
axios.post('/delete', this.state.movie)
.then(() => this.props.history.push("/database"));
};
render() {
return (
<EditBody>
<h2>Update Movie Data</h2>
{this.state.movie.map((movie: any) =>
<div key={movie.title} className="container">
<form action={`/edit/${movie.title}?_method=PUT`} method="PUT">
<div className="inputrow">
<p className="description">Movie Title
<input className="input" type="text" value={movie.title} onChange={this.handleChange} name="title" /></p></div>
<div className="inputrow">
<p className="description">Rotten Tomatoes Score
<input className="input" type="text" value={movie.rtc} onChange={this.handleChange} name="rtc" /></p></div>
<div className="inputrow">
<p className="description">Metacritic Score
<input className="input" type="text" value={movie.mtc} onChange={this.handleChange} name="mtc" /></p></div>
<div className="inputrow">
<p className="description">IMDB Score
<input className="input" type="text" value={movie.imdb} onChange={this.handleChange} name="imdb" /></p></div>
<div className="inputrow">
<p className="description">Scary Movie Night Score
<input className="input" type="text" value={movie.smn} onChange={this.handleChange} name="smn" /></p></div>
<div className="inputrow">
<p className="description">Comments
<input className="input" type="text" value={movie.comments} onChange={this.handleChange} name="comments" /></p></div>
<button id="submitButton" type="submit" onClick={this.handleUpdate}><h2>Update Movie</h2></button> or <button id="submitButton" type="submit" onClick={this.handleDelete}><h2>Delete Movie</h2></button>
</form>
</div>
)}
</EditBody>
)
}
};
On top of that I have a problem with the axios request to the server. It works (i.e. the movie gets updated or deleted from the DB) but the this.props.history.push either doesn't work (right now) or when it does, it's faster than the server response so it re-routes back to a /database page which hasn't received the updated information yet. Only after a refresh does /database show the correct information. I've tried async/await, setTimeout, this.props.context.push, and as you an see a .then() after the returned promise. Nothing works. I think because this.history pushes to an old version of the page, instead of actually loading the page again?
Both these problems are driving me nuts! Any help would be appreciated. If someone needs to see more code let me know.
EDIT:
The componentDidMount() and callServer.. functions are because we get to this /edit page by clicking on a movie that we want to edit. So it gets the information for that movie from the server after I click it on another page.
For anyone reading: it was because I had the {movie.title} also as the key of the container div. This meant it re-rendered the div each time, since the key also changed. I created an index parameter and assigned the key to that which solves the rendering problem. Now just the axios problem left..
{this.state.movie.map((movie: any, index: any) =>
<div key={index} className="container"> //CHANGED KEY VALUE
<form action={`/edit/${movie.title}?_method=PUT`} method="PUT">
// WHICH COLLIDED WITH THIS VALUE
<div className="inputrow">
<p className="description">Movie Title
<input className="input" type="text" value={movie.title} onChange={this.handleChange} name="title" /></p></div>
And also solved the axios problem. Updated through this.setState and added a conditional in the render function. Now it works with . For anyone happening upon this question.

Render user input in a different component in React

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.

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. 🙂

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>
);

React couldn't read the state

I used the same function I got on a react native in a react app and it didn't work, looks like I couldn't access the sate although I defined it in the constructor, the goal is to push data to firebase, I tried with random strings and it definitely works, it's just when using the form that it crashes.
As you can see I'm using text components to take a look a the state on the HTML page :
import React, { Component } from 'react';
import fire from './config/Fire';
class Home extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
isOpen: false,
title: '',
description: '',
loading: true
};
}
handleChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
saveData(e) {
e.preventDefault();
let title = this.state.title;
let description = this.state.description;
const { currentUser } = fire.auth();
fire
.database()
.ref(`/master/setup/`)
.push({ title, description })
.then(() => {
this.setState({ loading: false }).catch(error => {
console.log(error);
});
});
}
render() {
return (
<div>
<Container>
<Row>
<Col sm="2" lg="3" />
<Col sm="8" lg="6">
<h1>General Setup</h1>
<form>
<div class="form-group">
<label for="exampleInputEmail1">Title</label>
<input
value={this.state.title}
onChange={this.handleChange}
name="title"
class="form-control"
id="title"
placeholder="Enter event title"
/>
</div>
<div class="form-group">
<label for="exampleInputEmail1">Description</label>
<input
value={this.state.description}
onChange={this.handleChange}
name="description"
class="form-control"
id="description"
placeholder="Enter event description"
/>
</div>
<button onClick onClick={this.saveData} class="btn btn-primary">
Submit
</button>
</form>
<p>{this.state.title}</p>
<p>{this.state.description}</p>
<p>{this.state.loading.toString()}</p>
</Col>
<Col sm="2" lg="3" />
</Row>
</Container>
</div>
);
}
}
export default Home;
TypeError: Cannot read property 'state' of undefined
Please, someone, let me know what's going on with this code?
You can change saveData to an arrow function hence binding isn't required. This is an ES6 version, do something like below
saveData = e => {
e.preventDefault();
let title = this.state.title;
let description = this.state.description;
const { currentUser } = fire.auth();
fire.database().ref(`/master/setup/`)
.push({ title, description })
.then(() => {
this.setState({ loading: false})
.catch((error) => {
console.log(error);
})
});
}
You need to bind saveData in constructor.
this.saveData = this.saveData.bind(this);
You forgot to bind scope to saveData method.
Do it in constructor same as you bind it to handleChange method.
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.saveData = this.saveData.bind(this);
this.state = {
isOpen: false,
title: '',
description: '',
loading: true,
};
or
change saveData definition to one that uses arrow function syntax from ES6
saveData = (e) => {...function body as you already have it}
and parent scope will be bind for you by default

Resources