ReactJS: Why does my textarea value always render invisible? - javascript

Trying to set up something simple.
Parent: app.js
class App extends React.Component {
constructor(props) {
super(props);
//This acts as our global state
this.state = {
username: "",
email: "",
bio: ""
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
username: "jonny",
email: "jonny#mail.com",
bio: "My bio...."
});
}, 5000);
}
handleFormChange = data => {
this.setState(data);
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Form data={this.state} onHandleFormChange={this.handleFormChange} />
<p>Name from App state: {this.state.username}</p>
<p>Email from App state: {this.state.email}</p>
<p>Bio from App state: {this.state.bio}</p>
</div>
);
}
}
Child: form.js
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
...this.props.data
};
}
handleSubmit = e => {
e.preventDefault();
};
handleChange = e => {
this.props.onHandleFormChange({ [e.target.name]: e.target.value });
};
// static getDerivedStateFromProps(nextProps, prevState) {
// console.log(nextProps.data)
// return {
// ...nextProps.data
// };
// }
componentDidUpdate(prevProps) {
if (prevProps.data !== this.props.data) {
this.setState({ ...this.props.data });
}
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="username"
defaultValue={this.state.username}
onChange={this.handleChange}
/>
<input
type="email"
name="email"
defaultValue={this.state.email}
onChange={this.handleChange}
/>
<textarea
name="bio"
defaultValue={this.state.bio}
onChange={this.handleChange}
/>
<input type="submit" value="submit" />
</form>
</div>
);
}
}
I created an artificial API call by using a setTimeout() in this example and I'm trying to set the state of the parent with the result of the API call. Then I wish to pass that as a prop to the child...
It's working except in the case of a textarea. I can see it if I inspect the DOM but it doesn't actually show in the browser...
Note the "my bio..." in the inspector, but the textarea being empty in the browser.
I've tried componentWillUpdate(), componentDidUpdate() and getDerivedStateFromProps() but nothing seems to work.
What am I missing?
Note: I am not using value="" because then it stops me typing and this form is supposed to allow you to update existing values
Sandbox... https://codesandbox.io/s/ancient-cloud-b5qkp?fontsize=14

It seems to work fine by using the value attribute instead of defaultValue. The defaultValue attribute should really only be used sparingly, since you almost always want your inputs to connect to component state. The optimal way to create a controlled input is by using value.
<textarea
name="bio"
value={this.state.bio}
onChange={this.handleChange}
/>

Change the defaultValue in textarea to value

Related

Implement recursive onClick event in react js

I am trying to implement a recursive method on reactjs, but only when data is submitted or clicked.
Following is the code I am trying with:
import React, { Component } from "react";
class Textbox extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {this.setState({value: event.target.value}); }
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="React" />
</div>
</form>
);
}
}
export default Textbox;
which generates the following view.
I want to use a recursive method onClick or onSubmit, such that it will generate another text box upon submission. Like
which I want to continue until I click some "Exit" or "Stop" button, which I can add to the top of the view.
From what I have read about recursive implementation on ReactJS, I need to call the class/function again inside render. When I do that I think react is getting inside the infinite loop and freezes the browser.
what I tried is to call Textbox inside the <div> <div/> of the render method. Like this:
.... other code lines are same
<div>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="React" />
<Textbox/>
</div>
How can I generate a recursive textbox on submission/clicking event on the previous text box?
You could do it like this, where showNextInput prevents the infinite loop:
class Textbox extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
showNextInput: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {this.setState({value: event.target.value}); }
handleSubmit(event) {
console.log('submit');
event.preventDefault();
this.setState({ showNextInput: true });
}
render() {
return (
<>
<form onSubmit={this.handleSubmit}>
<div>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</div>
</form>
{ this.state.showNextInput ? <Textbox/> : null }
</>
);
}
}
However, your use case looks like something you would usually do by
managing a list of values somewhere,
add items as required inside your handlers, and
then display a list of these items
Here a quick and dirty example:
export class TextboxList extends React.Component {
constructor(props) {
super(props);
this.state = {
values: {
0: ''
},
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(index, value) {
this.setState({
values: {
...this.state.values,
[index]: value
}
});
}
handleSubmit(event) {
event.preventDefault();
this.setState({
values: {
...this.state.values,
[Object.keys(this.state.values).length]: '' // add new empty item to list
}
});
}
render() {
return (
<>
<form onSubmit={this.handleSubmit}>
{ Object.keys(this.state.values).map( (index) => {
const value = this.state.values[index];
return <div key={ index }>
<label>
Name:
<input
type="text"
value={ value }
onChange={ (event) => this.handleChange( index, event.target.value ) }
/>
</label>
<input type="submit" value="Submit" />
</div>;
})}
</form>
</>
);
}
}
export default Textbox;

Why does my React form auto-refresh the page even if I put "event.preventDefault()" on handleSubmit?

I have two files which work together to render things. The first is App.js, which first renders Form.js. The form will then collect information, which on submission, changes the Form state and calls a function from App.js. This function is called "createProject." Calling "createProject" in Form.js "handleSubmit" makes the page auto-refresh. However, if I remove "createProject" from handleSubmit, the page does not auto-refresh. Here are the two files.
import React, { Component } from "react";
import Project from "./components/Project.js"
import Form from "./Form.js";
class App extends Component {
constructor(props) {
super(props);
this.state = {
projectList: [],
myProjects: [],
userList: [],
submitted: false
};
this.createProject = this.createProject.bind(this);
}
createProject(title, desc, langs, len, exp) {
this.setState({
projectList: this.state.projectList.push([
{
title : title,
description : desc,
language : langs,
length : len,
experience : exp
}
]),
submitted : true
});
}
deleteProject(title) {
const projects = this.state.projectList.filter(
p => p.title !== title
);
this.setState({projects});
}
render() {
let info;
if (this.state.submitted) {
info = (
<div>
<p>cccc</p>
</div>
);
} else {
info = (
<br/>
);
}
return(
<div>
<Form/>
{info}
{this.state.projectList.map((params) =>
<Project {...params}/>)}
</div>
);
}
}
export default App;
import React from "react";
import createProject from "./App.js"
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
description: "",
language: "",
length: 0,
experience: "",
submitted: false
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleSubmit(event) {
this.setState({
submitted: true
})
createProject(
this.state.title,
this.state.description,
this.state.language,
this.state.length,
this.state.experience
)
event.preventDefault();
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
let info;
if (this.state.submitted) {
info = (
<div>
<h1>{this.state.title}</h1>
<p>{this.state.description}</p>
<p>{this.state.language}</p>
<p>{this.state.length}</p>
<p>{this.state.experience}</p>
</div>
);
} else {
info = <br/>;
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Title:
<input
name="title"
type="textbox"
checked={this.state.title}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Description:
<input
name="description"
type="textbox"
checked={this.state.description}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Language:
<input
name="language"
type="textbox"
checked={this.state.language}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Length:
<input
name="length"
type="number"
checked={this.state.length}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Experience:
<input
name="experience"
type="textbox"
checked={this.state.experience}
onChange={this.handleInputChange} />
</label>
<br />
<input type="submit" value="Submit" />
</form>
{info}
</div>
);
}
}
export default Form;
I've also tried adding "new" to the "createProject" in handleSubmit, and while that does stop the auto-refresh, it will not call the createProject function. (Or maybe it does, but none of the code in the createProject function seems to be run.) Can anyone help with preventing this auto refresh while also allowing App's createProject function to run properly?
The page auto refreshes because execution never gets to your event.PreventDefault() line. This is due to an error encountered when react tries to evaluate createProject. To fix this, correct handleSubmit like so.
handleSubmit(event) {
event.preventDefault(); // moved up in execution.
this.setState({
submitted: true
})
createProject(
this.state.title,
this.state.description,
this.state.language,
this.state.length,
this.state.experience
)
}
Notice that moving event.PreventDefault() to the top of your handleSubmit(event) function just before this.setState line prevents default form behaviour on submit.
You however get an error because App.js doesn't export a function called createProject. To maintain the createProject within App instance, you need to pass it as a prop which you can then reference as this.props.createProject.
See this answer on how to do call a Parent method in ReactJS.

How to pass default value loaded into form input to the payload

I'm making an edit form which inside Modal. Form inputs has defaultValue that comes from props with an name, age and strength as default value. If I change all inputs value, everything works fine, but if I change only one input value when I console.log payload I got and empty strings, but only value that I changed goes to the payload. How can I make that even I change only one input payload gets inputs default value from props?
My code is here:
import React, { Component } from 'react';
import './Form.scss';
import axios from 'axios';
class Form extends Component {
constructor(props){
super(props);
this.state = {
id: this.props.gnomeId,
submitedName: '',
submitedAge: '',
submitedStrength: ''
}
this.onSubmit = this.onSubmit.bind(this);
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
});
}
onSubmit(event) {
event.preventDefault();
const gnome = {
name: this.state.submitedName,
age: this.state.submitedAge,
strenght: this.state.submitedStrength,
}
console.log(gnome);
axios.post(`${API}/${this.state.id}`, {gnome})
.then( res => {
console.log(res);
console.log(res.data);
});
}
render() {
return(
<form onSubmit={this.onSubmit}>
<div>
<label htmlFor="name">Name</label>
<input type="text" name="submitedName" id="name" defaultValue={this.props.gnomeName} onChange={this.handleChange}/>
</div>
<div>
<label htmlFor="age">Age</label>
<input type="text" name="submitedAge" id="age" defaultValue={this.props.gnomeAge} onChange={this.handleChange}/>
</div>
<div>
<label htmlFor="strength">Strength</label>
<input type="text" name="submitedStrength" id="strength" defaultValue={this.props.gnomeStrength} onChange={this.handleChange}/>
</div>
<button type="submit" className="submit-btn">Submit</button>
</form>
)
}
}
export default Form;
Just set the initial state to the default values:
super(props);
this.state = {
id: this.props.gnomeId,
submitedName: props.gnomeName,
submitedAge: props.gnomeAge,
submitedStrength: props.gnomeStrength
}
You may use Nullish_Coalescing_Operator to give different value from init as null/undefined
this.state = {
submitedName: null,
...
}
const gnome = {
name: this.state.submitedName ?? this.props.submitedName,
...
}
First remove the defaultProps.
add componentDidUpdate in class based component and useEffect in function based component
componentDidUpdate(props) {
if(props){
this.state = {
id: this.props.gnomeId,
submitedName: props.gnomeName,
submitedAge: props.gnomeAge,
submitedStrength: props.gnomeStrength
}
}
}
it is necessary to put props in if because it has undefined initial so not to get error

Passing ref up to parent in react

I have a little app that has an input and based on the search value, displays weather for a particular city. I'm stuck at a certain point though. The idea is that once you search a city, it hides the text input and search button and displays some weather info and another search button to search a new city. My issue is that I want to focus on the search box once I click to search again. I hope that makes sense. I read that the ideal way to do this is with refs. I wired it up like such:
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
With this I can pass that ref up to the parent to use in my search by using this.state.myRefs.current.value; It works great, but when I try to reference this.state.myRefs.current in a different function to use .focus(), it returns null.
resetSearch = () => {
console.log(this.state.myRefs.current); // <- returns null
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
Is this because I'm hiding and showing different components based on the search click? I've read numerous posts on SO, but I still can't crack this. Any help is appreciated. I'll include the full code below. To see it in full here is the git repo: https://github.com/DanDeller/tinyWeather/blob/master/src/components/WeatherMain.js
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
recentCities: [],
details: [],
isOpen: true,
myRefs: '',
video: '',
city: ''
};
this.updateInputValue = this.updateInputValue.bind(this);
this.getRefsFromChild = this.getRefsFromChild.bind(this);
this.resetSearch = this.resetSearch.bind(this);
this.getWeather = this.getWeather.bind(this);
}
updateInputValue = (e) => {
...
}
resetSearch = () => {
console.log(this.state.myRefs.current);
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
getWeather = (e) => {
...
}
getRefsFromChild = (childRefs) => {
...
}
render() {
return (
<section className={style.container}>
<div className={style.weatherMain + ' ' + style.bodyText}>
<video key={this.state.video} className={style.video} loop autoPlay muted>
<source src={this.state.video} type="video/mp4">
</source>
Your browser does not support the video tag.
</video>
<div className={style.hold}>
<div className={style.weatherLeft}>
<WeatherForm
updateInputValue={this.updateInputValue}
getWeather={this.getWeather}
passRefUpward={this.getRefsFromChild}
resetSearch={this.resetSearch}
isOpen={this.state.isOpen}
/>
<WeatherList
details={this.state.details}
city={this.state.city}
isOpen={this.state.isOpen}
/>
</div>
<div className={style.weatherRight}>
<Sidebar
recentCities={this.state.recentCities}
/>
</div>
<div className={style.clear}></div>
</div>
</div>
</section>
);
}
}
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
export default Weather;
You try to achieve unmounted component from DOM, because of this you can not catch the reference. If you put this code your instead of render function of WeatherForm component, you can catch the reference. Because i just hide it, not remove from DOM.
render() {
return (
<div>
<div className={style.weatherForm}
style={this.props.isOpen ? {visibility:"initial"} :{visibility:"hidden"}}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
<div className={style.resetButton} style={this.props.isOpen ? {visibility:"hidden"} :{visibility:"initial"}}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
</div>
)
}
console.log(this.state.myRefs.current) returns null , because it's a reference to an input dom element which does not exists as currently Weather form is displaying Search another city along with a reset button.
In reset function state changes, which results in change of prop isOpen for WeatherForm component. Now, screen would be displaying the input field along with search button.
After component is updated ComponentDidUpdate lifecycle method is called.
Please add ComponentDidUpdate lifecycle method in WeatherForm and add ,
this.city.current.focus() in the body of method.
There is no need to pass reference of a dom element to the parent element as it is not consider as a good practise.
Edit 1 :-
Need to set input field in focus only if prop ( isOpen ) is true as we will get reference to the input field only if its mounted.
ComponentDidUpdate(){
if(this props.isOpen)
this.city.current.focus
}
Link to Lifecycle method :-
https://reactjs.org/docs/react-component.html#componentdidupdate
Hope this helps,
Cheers !!

React collect form properties and submit

I am new to React and Redux, hence, sorry for the dummy question.
I have a component:
export default class AddReminder extends Component {
render() {
return (
<div>
Name: <input name="name" />
Description: <input name="description" />
<button>
Add reminder
</button>
</div>
)
}
}
Also, I have an action located in different file what I want to call on button click.
export function addReminder(reminder) { ... }
So, I would like to create a reminder object with name and description properties and call an action. Also, I do not want to create a <form>, just a simple div. Can you please explain how can I do that?
If you don't want to use form element, you can store inputs values in state and on button click, pass state to addReminder func:
export default class AddReminder extends Component {
constructor() {
this.state = {
name: '',
description: ''
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleNameChange = this.handleNameChange.bind(this);
}
handleNameChange(e) {
this.setState({
name: e.target.value
});
}
handleDescChange(e) {
this.setState({
description: e.target.value
});
}
handleSubmit() {
addReminder(this.state);
}
render() {
return (
<div>
Name: <input name="name" value={this.state.name} onChange={handleNameChange} />
Description: <input name="description" value={this.state.description} onChange={handleDescChange} />
<button onClick={this.handleSubmit}>
Add reminder
</button>
</div>
)
}
}
But this solution is cumbersome, I think.Instead of using state, you can use form element, and inside of onSubmit callback, serialize it values to object and pass them to addReminder func:
// You should install `form-serialize` package from npm first.
import serialize from 'form-serialize';
// Note, we are using functional stateless component instead of class, because we don't need state.
function AddReminder() {
let form;
function handleSubmit(e) {
// Preventing default form behavior.
e.preventDefault();
// serializing form data to object
const data = serialize(form, { hash: true });
addReminder(data);
}
// Assigning form node to form variable
return (
<form ref={node => form = node} onSubmit={handleSubmit}>
Name: <input name="name" />
Description: <input name="description" />
<button type="submit">
Add reminder
</button>
</form>
);
}
class AddReminder extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
const parent = e.target.parentNode.children;
const { name, description } = parent;
dispatch(actionName({name.value, description.value}));
}
render() {
return (
<div>
Name: <input name="name" />
Description: <input name="description" />
<button onClick={this.handleClick}>
Add reminder
</button>
</div>
);
}
}
import {addReminder} from './addReminder';
export default class AddReminder extends Component {
render() {
addReminder() {
//call your action or do whatever
return {
name: this.refs.name.value,
description: this.refs.description.value
}
}
return (
<div>
Name: <input name="name" />
Description: <input name="description" />
<button onClick={addReminder.bind(this}>
Add reminder
</button>
</div>
)
}
}

Categories

Resources