setState is not updating the state even callback - reactjs

On input field change, the handler function gets called and updates the state, but the state is not getting updated. I know setState() is async so I put the console.log in the callback.
Here are the functions:
class WorkspaceEditModal extends React.PureComponent {
constructor(props) {
super(props);
console.log(props.selectedWorkspace); // prints correct value
this.state = {
editWorkspaceName: props.selectedWorkspace,
exists: false,
};
}
handleEditChange = event => { // this handler is called
console.log(event.target.value); // prints new value
this.setState({ editWorkspaceName: event.target.value },
() => {console.log(this.state.editWorkspaceName)}); // still prints old value
};
handleFocus = e => e.target.select();
handleKeyPress = e => {
if (e.key === 'Enter') {
e.preventDefault();
this.editWorkspace();
}
};
render() {
const { editWorkspaceName, exists } = this.state;
let content;
content = (
<div className="save-workspace">
<div className="modal-header">
<div className="modal-title">
<p>Rename Workspace</p>
</div>
<div className="modal-select">
<span className="input-label">Workspace Name</span>
<div>
<input
className="name-input"
placeholder="Workspace Name"
type="text"
defaultValue={editWorkspaceName}
onChange={this.handleEditChange} // calls the handler on change
onFocus={this.handleFocus}
onKeyPress={this.handleKeyPress}
autoFocus
/>
{notValid && (
<p className="invalid">
Only alphanumeric characters, hyphens, spaces, and
underscores are allowed.
</p>
)}
</div>
</div>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-default"
onClick={this.onCloseModal}
>
Cancel
</button>
<button
type="button"
className="btn btn-primary"
onClick={this.editWorkspace}
disabled={notValid}
>
Update
</button>
</div>
</div>
);
const contentStyle = {
width: '500px',
};
return (
<Modal
isOpen={true}
onClose={this.onCloseModal}
contentStyle={contentStyle}
shouldCloseOnOverlayClick={true}
>
{content}
</Modal>
);
}
}
I don't know why this is happening. This is crazy.
Thank you for your help.

You are passing down the default value of the input via props. That should be your input default value. It should not update the state of the child component.
State should be updated on the parent component, not the child in this case. The handleEditChange (handleChange in example) function should be placed in the parent component, then passed down via props to the input. You want the props value changed in the parent via that parent function so that setState works properly.
You could use getDerivedStateFromProps, but it seems unnecessary here. https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
class ParentWorkSpaceEditModal extends React.Component {
constructor(props) {
super(props);
this.state = {
editWorkspaceName: 'whatever your original value is',
exists: false,
};
}
handleChange = event => {
this.setState({ editWorkspaceName: event.target.value });
};
render() {
const { editWorkspaceName, exists } = this.state;
return (
<ChildWorkspaceEditModal editWorkspaceName={editWorkspaceName} exists={exists} handleChange={this.handleChange} />
)
}
}
const ChildWorkspaceEditModal = ({ editWorkspaceName, exists, handleChange )} => {
content = (
<div className="save-workspace">
<div className="modal-header">
<div className="modal-title">
<p>Rename Workspace</p>
</div>
<div className="modal-select">
<span className="input-label">Workspace Name</span>
<div>
<input
className="name-input"
placeholder="Workspace Name"
type="text"
value={editWorkspaceName}
onChange={handleChange}
/>
{notValid && (
<p className="invalid">
Only alphanumeric characters, hyphens, spaces, and underscores are allowed.
</p>
)}
</div>
</div>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-default"
onClick={this.onCloseModal}
>
Cancel
</button>
<button
type="button"
className="btn btn-primary"
onClick={this.editWorkspace}
disabled={notValid}
>
Update
</button>
</div>
</div>
);
const contentStyle = {
width: '500px',
};
return (
<Modal
isOpen={true}
onClose={this.onCloseModal}
contentStyle={contentStyle}
shouldCloseOnOverlayClick={true}
>
{content}
</Modal>
)
}

Related

empty data instead of data from the array when changing the data in the input react js

I have a simple todo list that consists of multiple inputs.
I made the editing functionality and now everything works as it should, but only once. When I change the input data for the first time, it saves everything to an array with the correct data.
And when I want to do it a second time, then in order to save this data, three inputs must be changed.
I want that even when changing one input, the data is saved in an array (data that has not been changed is overwritten).
Stackblitz code
App.js
function App(props) {
const [tasks, setTasks] = useState(props.tasks);
function editTask(id, newName, newTranslate, newNote) {
const editedTaskList = tasks.map((task) => {
if (id === task.id) {
return { ...task, name: newName , translate: newTranslate , note: newNote };
}
return task;
});
setTasks(editedTaskList);
}
const taskList = tasks
.map((task) => (
<Todo
id={task.id}
name={task.name}
translate={task.translate}
note={task.note}
completed={task.completed}
key={task.id}
editTask={editTask}
tasks={tasks}
/>
));
return (
<div className="todoapp stack-large">
<ul
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
{taskList}
</ul>
</div>
);
}
export default App;
I did a check and added the save button onClick which outputs the data to the console. It gives the data correctly the first time, and if the same item in the todo is changed the second time, it gives an empty space instead of the data that has not been changed.
Todo.js
export default function Todo({name, translate, note, editTask, id, tasks}) {
const [isEditing, setEditing] = useState(false);
const [newName, setNewName] = useState(name);
const [newTranslate, setNewTranslate] = useState(translate);
const [newNote, setNewNote] = useState(note);
function handleChange(e) {
setNewName(e.target.value)
}
function handleChangeTranslate(e) {
setNewTranslate(e.target.value);
}
function handleChangeNote(e) {
setNewNote(e.target.value)
}
function handleSubmit(e) {
e.preventDefault();
if (!newName.trim()|| !newTranslate.trim() || !newNote.trim()) {
return;
}
editTask(id, newName,newTranslate,newNote);
setNewName("");
setNewTranslate("");
setNewNote("");
setEditing(false);
}
const editingTemplate = (
<form className="stack-small" onSubmit={handleSubmit}>
<div className="form-group">
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newName || name}
onChange={handleChange}
placeholder="write word"
/>
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newTranslate || translate}
onChange={handleChangeTranslate}
placeholder="write translate"
/>
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newNote || note}
onChange={handleChangeNote}
placeholder="write note"
/>
</div>
<div className="btn-group">
<button
type="button"
className="btn todo-cancel"
onClick={() => setEditing(false)}
>
Cancel
</button>
<button type="submit" className="btn btn__primary todo-edit" onClick={()=>console.log(newName, newTranslate, newNote)}>
Save
</button>
</div>
</form>
);
const viewTemplate = (
<div className="stack-small">
<div className="c-cb">
<label className="todo-label" htmlFor={id}>
{name}
</label>
<label className="todo-label" htmlFor={id}>
{translate}
</label>
<label className="todo-label" htmlFor={id}>
{note}
</label>
</div>
<div className="btn-group">
<button
type="button"
className="btn"
onClick={() => setEditing(true)}
>
Edit <span className="visually-hidden">{name}</span>
</button>
</div>
</div>
);
return <li className="todo">{isEditing ? editingTemplate : viewTemplate}</li>;
}
Since you want to keep those preview state which was not edit and still print out those state with the one you edit, you can just remove all the "reset state '' you put, since all your initial state from useState already had a value and is not an empty string "" like this
function handleSubmit(e) {
e.preventDefault();
if (!newName.trim()|| !newTranslate.trim() || !newNote.trim()) {
return;
}
editTask(id, newName,newTranslate,newNote);
setEditing(false);
}

Append data in same page in button click with react Js

How can we list data from the array list on the same page in button click using react?
When user enter quantity setting value text box change event
class ProductList extends Component {
constructor(props) {
super(props);
this.state={
CartArray:[],
ProductList:[],
}
}
handleInputChange = event =>
{
const cart_values = event.target.name.split('-');
let newCart = {};
newCart["Key"]=cart_values[0]
newCart["ProductName"]=cart_values[1]
newCart["ProductBrand"]=cart_values[2]
this.setState(prevState => ({CartArray: [...prevState.CartArray, newCart]}))
}
viewCart = () => {
//What need to write here show data from CartArray:[] to my basket
}
}
Below is my render method. Numerical text box change i am setting in state value
render() {
return (
<div className="card" style={{ marginBottom: "10px"}}>
<div> <button className="btn btn-sm btn-warning float-right" onClick={this.viewCart}>View cart</button></div>
{this.state.ProductList.map((product, key) =>(
<div className="card-body">
<div className="card-title" key={key} value={key}>{product.ProductName}
<img src= {`data:image/jpeg;base64,${product.Image2}`} width="200" height="80" />{product.Brand}
<div>
<button className="btn btn-sm btn-warning float-right"
onClick={this.addToCart}>Add to cart</button>
<div>
<input type="number" min="0" pattern="[0-9]*" onInput={this.handleInputChange.bind(this)} name={`Name${key}-${product.ProductName}-${product.Brand}`} />
</div>
</div>
</div>
</div>
))}
</div>
)
}
If by show you mean to show on screen, not inside viewCart but in separate method render()
render(){
return(
<div>
{
this.state.CartArray.map((product) => {
<p>Key: {product.Key} </p>
<p>ProductName: {product.ProductName} </p>
<p>ProductBrand: {product.ProductBrand} </p>
})
}
</div>
);
}

Reactjs not able to render a component which has a form

In my App.js, I have a class component Addition which will display 2 numbers and have a text box to get the result from the user. In the same file I have another class component App from which when I try to call <Addition/>, it renders the form perfectly as expected. However, I want the <Addition/> component to be triggered on a button click, which is not working.
class Addition extends Component{
render(){
return (
<form onSubmit={this.validateResult}>
<div >
<Box1 val={this.state.value1}/>
<Box1 val="+"/>
<Box1 val={this.state.value2}/>
<Box1 val="=" />
<input className="resbox"
value={this.state.result}
onChange={event => this.setState({result: event.target.value})}
required
/>
<br/>
<Box2 val={this.state.message} />
<br/><br/>
<input className="btn btn-info" type="submit" value="Submit" />
</div>
</form>
)
}
}
export default class App extends Component {
handleClick= (event) => {
event.preventDefault()
console.log("inside handleclick")
return (
<Addition />
);
}
render(){
return (
<div className="App">
<header className="row App-header">
<p >
KIDS MATHS
</p>
<div className="row col-2">
<button className="btn btn-lg offset-1" onClick={this.handleClick}> + </button>
</div>
</header>
</div>
)
}
}
When I tried to include the <Addition/> within the on-click event, the page simply remains same.
Please advice.
Everything you want to render has to called directly from within the render function. You can use conditional rendering to achieve to selective rendering of Addition. For this add a state member and set the value accordingly at the trigger point. This will call render again and the content will be shown as expected.
export default class App extends Component {
state = {
showAddition: false
}
handleClick= (event) => {
event.preventDefault()
console.log("inside handleclick")
this.setState({showAddition: true})
}
render(){
const { showAddition } = this.state
return (
<div className="App">
<header className="row App-header">
<p >
KIDS MATHS
</p>
<div className="row col-2">
<button className="btn btn-lg offset-1" onClick={this.handleClick}> + </button>
</div>
</header>
{showAddition && <Addition/>}
</div>
)
}
}
You can also consider to convert the components to function components and use useState instead of the state object. This will shorten the code a little bit.

Reactjs - Submitting multiple lines from one textarea

I'm trying to allow a textarea to take a list of names and display the submission of names on the page. However, I want the user to only be able to submit one name per line.
How would one go about this? More specifically, how would one grab the line breaks and split them into, say... an array to be mapped through and displayed in a dom element.
In my code the state of names is an empty string, but I think it would be easier/more manageable if it were an array.
Thanks!
class Content extends Component {
constructor(props) {
super(props);
this.state = {
names: ""
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit = (e) => {
e.preventDefault();
}
handleChange = (e) => {
this.setState({
names: e.target.value
});
}
render() {
return (
<section className="flex">
<div className="content flex">
<div className="sandbox flex">
<div className="directions">
<h1>Please enter a list of names.</h1>
<h3>Select a langauge at the top of the page.</h3>
</div>
<form id="nameForm" className="names flex">
<div className="form-group">
<textarea
id="names"
name="hard"
value={this.state.names}
cols={20}
rows={20}
onChange={this.handleChange}
wrap="hard">
</textarea>
</div>
</form>
<button id="formButton" form="nameForm" type="submit">Submit</button>
<div className="nametags flex">
<div className="nametags-group flex">
<button type="button" className="nametag">{this.state.names}</button>
<p className="greeting">Hello there, happy to see you back!</p>
</div>
<div className="nametags-group flex">
<button type="button" className="nametag">John Doe</button>
<p className="greeting">Hello there, happy to see you back!</p>
</div>
</div>
</div>
</div>
</section>
)
}
}
export default Content;
This code should work perfectly for you:
import React, { Component } from 'react'
class Content extends Component {
constructor(props) {
super(props)
this.state = {
names: ''
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleSubmit = name => {
alert(`Submitted name: ${name}`)
}
handleChange = e => {
this.setState({
names: e.target.value
})
}
render() {
return (
<section className="flex">
<div className="content flex">
<div className="sandbox flex">
<div className="directions">
<h1>Please enter a list of names.</h1>
<h3>Select a langauge at the top of the page.</h3>
</div>
<form id="nameForm" className="names flex">
<div className="form-group">
<textarea
id="names"
name="hard"
value={this.state.names}
cols={20}
rows={20}
onChange={this.handleChange}
wrap="hard"
/>
</div>
</form>
<button id="formButton" form="nameForm" type="submit">
Submit
</button>
<div className="nametags flex">
<div className="nametags-group flex">
{this.state.names
.split('\n')
.filter(n => n) // to filter out empty names
.map((name, index) => (
<button
key={index}
type="button"
className="nametag"
onClick={() => this.handleSubmit(name)}
>
{name}
</button>
))}
<p className="greeting">Hello there, happy to see you back!</p>
</div>
</div>
</div>
</div>
</section>
)
}
}
export default Content
I think it's easier to keep the name state as string.
When rendering the names, split the string at \n, filter out the empty names (basically those extra newlines without names), and render the rest.
When clicking on those name buttons, call handleSubmit and pass name as argument.
So the rest to do is what you want to do with those names in handleSubmit.
Something like this:
handleChange = e => {
this.setState({
names: e.target.value,
submitNames: e.target.value.split(/\r?\n/)
});
};
And change how you display buttons:
{ this.state.submitNames.map(
(name) =>
<button
onClick={() => this.handleSubmit(name)}
type="button"
className="nametag"
>
{name}
</button>
)}

How to change the state of an item affected by onClick in array?

I'm making a page where I need to make multiple selections of buttons (like a filter, which I'll use for the next page).
the information from these buttons is coming from an array and I'm using .map () to mount the button list.
My problem is how do I change the state of only the button that was clicked. The way it is now, when I click, all the buttons are active.
How can I solve this?
Thank you.
import React from 'react';
import { Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import messages from './messages';
import { getLevel, getDiscipline } from '../../functions';
import template from './index.pug';
export default class ConfigAssessment extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
constructor(props){
super(props);
this.state = {
level: getLevel(),
discipline: getDiscipline(),
active: '',
first_click: true,
}
}
changeActive = () => {
if (this.state.first_click === true) {
this.setState({
active: 'active',
first_click: false
});
} else {
this.setState({
active: '',
first_click: true,
});
}
}
render() {
return(
<div className="configuration">
<div className="config-title">
<i className="ti-settings" />
<h2>
<FormattedMessage {...messages.configAssessment} />
</h2>
</div>
<div className="config-items">
<div className="form-group">
<label>
<FormattedMessage {...messages.level} />
</label>
<div className="row">
{this.state.level.map((level, i) => (
<div className="col-xs-1 col-md-4 col-lg-3" key={level.id}>
<button
className={`btn btn-light-gray btn-block ${this.state.active}`}
id={level.id}
onClick={this.changeActive}
>
{level.level}
</button>
</div>
))}
</div>
</div>
<div className="form-group">
<label>
<FormattedMessage {...messages.discipline} />
</label>
<div className="row">
{ this.state.discipline.map((discipline, i) => (
<div className="col-xs-1 col-md-4 col-lg-3" key={i}>
<button
className={`btn btn-light-gray btn-block ${this.state.active}`}
onClick={this.changeActive}
>
{discipline.discipline}
</button>
</div>
))}
</div>
</div>
<div className="form-group">
<label>
<FormattedMessage {...messages.selectQuestion} />
</label>
<div className="row">
<div className="col-xs-1 col-md-4 col-lg-3">
<button
className={`btn btn-light-gray btn-block ${this.state.active}`}
onClick={this.changeActive}
>
<FormattedMessage {...messages.typeAutomatic} />
</button>
</div>
<div className="col-xs-1 col-md-4 col-lg-3">
<button
className={`btn btn-light-gray btn-block ${this.state.active}`}
onClick={this.changeActive}
>
<FormattedMessage {...messages.typeManual} />
</button>
</div>
</div>
</div>
<div className="form-group fg-right">
<Link className="btn btn-warning" to="#">
<FormattedMessage {...messages.createAssessment} />
</Link>
</div>
</div>
</div>
);
}
}
Create a separate component for button
class MyButton extends Component {
constructor(props){
super(props);
this.state = {
person: this.props.person
}
}
buttonActiveHandler = () => {
let oldStatus = this.props.person.status;
this.props.person.status = (!oldStatus ? 'active': '');
this.setState({
person:this.props.person
});
}
render() {
return (
<button className={this.state.person.status} onClick={this.buttonActiveHandler}>{this.state.person.name}</button>
);
}
}
export default MyButton;
Then import button component. use map function to for your code block
<div className={classes.Box}>
<h4>Lorem, ipsum.</h4>
{
this.props.survey.map((person, i) => {
return (
<MyButton key={i} person={person}/>
)
})
}
</div>
The easiest solution to this problem is making the component of the content inside the map and then handling the state of that component there. Hence it will maintain individual states.
It depends what you need.
You can create separate component for button with state.
You can also keep state of each button in react state as an array, and then you can get the state of each button by index.
I'd recommend the first solution, it'd easier to manage such state, but it will be harder to get the state of a specific button from the parent component.

Resources