Reactjs setState() with a dynamic key name not working - reactjs

Here the code https://codesandbox.io/s/yqxr2z02pv
I am using this code so i dont need setState one by one onUpdate
onUpdate = (event) => {
const { target: { name, value } } = event
console.log(value);
this.setState({ [name]: value })
}
But the component not show the value when input value changed.
Any simple example can made this working?

Instead of duplicating the state in the Parent and the Child you can keep the state just in the Parent component and pass them down as props. You could also put the name prop on your inputs and use the onUpdate prop directly to pass along the event.
Example
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "firstName",
lastName: "lastName"
};
}
onUpdate = event => {
const {
target: { name, value }
} = event;
this.setState({ [name]: value });
};
render() {
const { firstName, lastName } = this.state;
return (
<div>
<h2>Parent</h2>
Value in Parent Component State firstName: {firstName}
<br />
Value in Parent Component State lastName: {lastName}
<br />
<Child
onUpdate={this.onUpdate}
firstName={firstName}
lastName={lastName}
/>
</div>
);
}
}
class Child extends React.Component {
render() {
const { firstName, lastName, onUpdate } = this.props;
return (
<div>
<h4>Child</h4>
first Name
<input
type="text"
placeholder="type here"
name="firstName"
onChange={onUpdate}
value={firstName}
/>
<br />
last Name
<input
type="text"
name="lastName"
placeholder="type here"
onChange={onUpdate}
value={lastName}
/>
</div>
);
}
}
ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Try making an object first and adding the dynamic key to that like so
onUpdate = (event) => {
const { target: { name, value } } = event
console.log(value);
const newData = {};
newData[name] = value;
this.setState(newData);
}

I edit your code. you can try.
https://codesandbox.io/s/m37wlryy78?fontsize=14
Child.js
import React from "react";
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: ""
};
}
update = e => {
this.props.onUpdate(e);
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<div>
<h4>Child</h4>
first Name
<input
type="text"
placeholder="type here"
onChange={this.update}
value={this.state.firstName}
name={"firstName"}
/>
<br />
last Name
<input
type="text"
placeholder="type here"
onChange={this.update}
value={this.state.lastName}
name={"lastName"}
/>
</div>
);
}
}
export default Child;

Related

Callback function from child to parent component in React

I am trying to make a chat. But when I send a message I need to refresh page to get data on the page. I have to components Forms and SendMsg.
Parent:
...
import client from '../Utils/Contentful';
export default class Forms extends Component {
constructor() {
super()
this.state = {
messages: [],
}
}
componentDidMount(){
client.getEntries({limit:300, order: 'sys.createdAt', content_type:'nameTest'}).then(response => {
this.setState({messages: response.items});
}).catch(e => {
console.log(e);
});
}
render() {
return (
<div className="chat">
<div className="container-xl">
<MessageList messages={this.state.messages}/>
<SendMsg />
</div>
</div>
);
}
}
And child component
...
import client from '../Utils/ContentfulCM';
export default class SendMsg extends Component {
constructor() {
super()
this.state = {
message:'',
userEmail:'ddd#gmail.com',
chatName:'ggg'
}
this.sendMessage = this.sendMessage.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.setState({
message: e.target.value,
})
}
sendMessage(e) {
e.preventDefault();
const form = e.target;
const data = new FormData(form);
client.getSpace(client.space)
.then((space) => space.getEnvironment('master'))
.then((environment) => environment.createEntry('nameTest', {
fields: {
chatName: {
'en-US': data.get('chatName')
},
//... some data
}
}))
.then((entry) => entry.publish())
.catch(console.error)
this.setState({
message: ''
})
}
render() {
return (
<div className="send-message">
<Form className="send-msg" onSubmit={this.sendMessage}>
<FormGroup>
<Input type="hidden" name="userEmail" value={this.state.userEmail}/>
</FormGroup>
<FormGroup>
<Input type="hidden" name="chatName" value={this.state.chatName}/>
</FormGroup>
<FormGroup>
<Input
type="text"
name="text"
onChange={this.handleChange}
value={this.state.message}
placeholder="Write your message here"
required />
</FormGroup>
<FormGroup>
<Input type="hidden" name="dateCreated" value={moment().format()} onChange={this.handleChange}/>
</FormGroup>
</Form>
</div>
);
}
}
I try to add props but not sure about right place for them
Any suggestions?
Update 1
Both components have "import client" (they are different because have uniq accessToken), that's why I can't use them in one component.
Update 2
I've change question according to suggestion below, but still need to refresh page in order to get displayed data.
Parent:
export default class Forms extends Component {
constructor() {
super()
this.state = {
messages: [],
}
this.sendMessage = this.sendMessage.bind(this);
}
componentDidMount(){
client1.getEntries({limit:300, order: 'sys.createdAt', content_type:'nameTest'}).then(response => {
this.setState({messages: response.items});
}).catch(e => {
console.log(e);
});
}
sendMessage(data) {
client2.getSpace(client2.space)
.then((space) => space.getEnvironment('master'))
.then((environment) => environment.createEntry('nameTest', {
fields: {
chatName: {
'en-US': data.get('chatName')
... some data
}
}))
.then((entry) => entry.publish())
.catch(console.error)
}
render() {
return (
<div className="chat">
<div className="container-xl">
<MessageList messages={this.state.messages}/>
<SendMsg onSendMessage={this.sendMessage}/>
</div>
</div>
);
}
}
And child component
export default class SendMsg extends Component {
constructor() {
super()
this.state = {
message:'',
userEmail:'ddd#gmail.com',
chatName:'ggg'
}
this.sendMessage = this.sendMessage.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.setState({
message: e.target.value,
})
}
sendMessage(e) {
e.preventDefault();
const { onSendMessage } = this.props;
const form = e.target;
const data = new FormData(form);
// if send message handler was passed, invoke with form data
onSendMessage && onSendMessage(data);
this.setState({
message: ''
})
}
render() {
return (
<div className="send-message">
<Form className="send-msg" onSubmit={this.sendMessage}>
<FormGroup>
<Input type="hidden" name="userEmail" value={this.state.userEmail}/>
</FormGroup>
<FormGroup>
<Input type="hidden" name="chatName" value={this.state.chatName}/>
</FormGroup>
<FormGroup>
<Input
type="text"
name="text"
onChange={this.handleChange}
value={this.state.message}
placeholder="Write your message here"
required />
</FormGroup>
<FormGroup>
<Input type="hidden" name="dateCreated" value={moment().format()} onChange={this.handleChange}/>
</FormGroup>
</Form>
</div>
);
}
}
Define the callback in the parent. Split out the logic of sending the message data from extracting it from the form in the child. The parent's callback receives the message data and sends it, while the child component's function pulls the form data, formats it, calls the callback passed in props, and clears the input field.
parent
export default class Forms extends Component {
constructor() {
super()
this.state = {
messages: [],
}
this.sendMessage = this.sendMessage.bind(this);
}
componentDidMount(){
client.getEntries({limit:300, order: 'sys.createdAt', content_type:'nameTest'}).then(response => {
this.setState({messages: response.items});
}).catch(e => {
console.log(e);
});
}
sendMessage(data) {
client.getSpace(client.space)
.then((space) => space.getEnvironment('master'))
.then((environment) => environment.createEntry('nameTest', {
fields: {
chatName: {
'en-US': data.get('chatName')
},
//... some data
}
}))
.then((entry) => entry.publish())
.catch(console.error);
}
render() {
return (
<div className="chat">
<div className="container-xl">
<MessageList messages={this.state.messages}/>
<SendMsg onSendMessage={sendMessage} />
</div>
</div>
);
}
}
child
export default class SendMsg extends Component {
constructor() {
super()
this.state = {
message:'',
userEmail:'ddd#gmail.com',
chatName:'ggg'
}
this.sendMessage = this.sendMessage.bind(this)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.setState({
message: e.target.value,
})
}
sendMessage(e) {
e.preventDefault();
const { onSendMessage } = this.props;
const form = e.target;
const data = new FormData(form);
// if send message handler was passed, invoke with form data
onSendMessage && onSendMessage(data);
this.setState({
message: ''
});
}
render() {
return (
<div className="send-message">
<Form className="send-msg" onSubmit={this.sendMessage}>
<FormGroup>
<Input type="hidden" name="userEmail" value={this.state.userEmail}/>
</FormGroup>
<FormGroup>
<Input type="hidden" name="chatName" value={this.state.chatName}/>
</FormGroup>
<FormGroup>
<Input
type="text"
name="text"
onChange={this.handleChange}
value={this.state.message}
placeholder="Write your message here"
required />
</FormGroup>
<FormGroup>
<Input type="hidden" name="dateCreated" value={moment().format()} onChange={this.handleChange}/>
</FormGroup>
</Form>
</div>
);
}
}
UPDATE to include syncing capability
Sync API
Using the Sync API with Javascirpt
Updates to parent component:
Add a class instance timer variable to hold interval timer reference
Create functions to handle syncing data calls
Update componentDidMount to sync initial data when the component mounts, and setup data synchronization polling (since this isn't event driven)
Add timer cleanup in componentWillUnmount lifecycle function
Parent
constructor() {
super()
this.state = {
messages: [],
}
this.syncTimer = null;
this.sendMessage = this.sendMessage.bind(this);
}
initialSyncClient = () => client1.sync({
initial: true
limit:100,
order: 'sys.createdAt',
content_type: 'nameTest',
});
syncClient = () => {
const { nextSyncToken } = this.state;
client1.sync({
nextSyncToken
})
.then(this.handleSyncResponse)
.catch(e => {
console.log(e);
});
};
handleSyncResponse = ({ entries, nextSyncToken}) => {
// response shape is a little different,
// response.entries vs. response.items, so need to access correctly
// also need to save nextSyncToken for all subsequent syncs
this.setState({
messages: entries.items,
nextSyncToken,
});
};
componentDidMount(){
// do initial sync
this.initialSyncClient()
.then(this.handleSyncResponse)
.catch(e => {
console.log(e);
});
// setup sync polling, 15 second interval
this.syncTimer = setInterval(syncClient, 15 * 1000);
}
componentWillUnmount() {
// clean up polling timer when component unmounts
clearInterval(this.syncTimer);
}
NOTE: These changes based purely on the contentful documentation, so there may be need of some tweaking to get working as expected, or if you prefer not using arrow functions, etc...
i don't see where you try to add function sendMessage to parent.
You can provide it by props, why not.
in child component, you can do something like this
interface IChildComponentWithSendMessage{
sendMessage
}
export class ChildComponent extends React.Component<IChildComponentWithSendMessage>
and you can provide you messageMethod by props
also you don't have to do
this.sendMessage = this.sendMessage.bind(this)
also i thinkt that could be your problem why you can't provide this method to child/parent component
you can create functions like this:
sendMessage = () => {
}

Input tag: nothing typed is appearing

My first React session data storage, so thanks. I am trying to set up inputing data and then placing it in session storage, so it can be edited, viewed or deleted later. There are 2 pieces of data, "title" and "note" to be inputed into a form. Nothing happens when I type into the form inputs. Any other help welcome also.
class AddNote extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
content: ''
}
}
componentDidMount() {
this.getFormData();
}
//let notes = getSessionItem(keys.notes);
//if (!notes) { notes = ""; }
onTitleChange(event) {
this.setState({ title: event.target.value }, this.storeFormData);
this.storeFormData();
}
onContentChange(event) {
this.setState({ content: event.target.value }, this.storeFormData);
}
storeFormData() {
const form = {
title: this.state.title,
content: this.state.content
}
setSessionItem(keys.user_form, form);
}
getFormData() {
const form = getSessionItem(keys.user_form);
if (form) {
this.setState({
title: form.name,
content: form.content
});
}
}
render() {
return (
<div>
<div>
<h2>ADD NOTE PAGE</h2>
</div>
<form classname="nav1">
<div>
<label><b>Title</b></label>
<input type="text"
value={this.state.title}
onchange={this.onTitleChange.bind(this)}
/>
</div>
<div>
<label><b>Content</b></label>
<input type="text"
value={this.state.content}
onchange={this.onContentChange.bind(this)}
/>
</div>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default AddNote;
and the storage file:
export const keys = {
title: 'title',
notes: 'notes'
}
export const getSessionItem = function (key) {
let item = sessionStorage.getItem(key);
item = JSON.parse(item);
return item;
}
export const setSessionItem = function (key, value) {
value = JSON.stringify(value);
sessionStorage.setItem(key, value);
}
export const removeSessionItem = function (key) {
sessionStorage.removeItem(key);
}
No need to have 2 change handler for your input. You can do it using a common change handler.
<form classname="nav1">
<div>
<label><b>Title</b></label>
<input type="text"
value={this.state.title}
name="title" <---- Provide name here
onChange={this.onChange}
/>
</div>
<div>
<label><b>Content</b></label>
<input type="text"
value={this.state.content}
name="content" <---- Provide name here
onChange={this.onChange}
/>
</div>
<button type="submit">Submit</button>
</form>
Your onChange function should be, and use callback in setState to call your storeFormData function.
onChange = (e) => {
this.setState({
[e.target.name] : e.target.value
}, () => this.storeFormData())
}
Note: In React we use camelCase, for example, onchange should be onChange and classname should be className.
Also make sure you bind this to storeFormData and getFormData functions, or you can use arrow function's to automatically bind this.
Demo

React pass multi state between two components

i found a gist about how to pass state between two components.
Here the jsbin
But how about the multi state?
I want two input fields and show the entered text in other components when i edit it.
i tried edited like this
this.state = {
fieldVal: "" //first input state
otherFieldVal: "" //second
}
and
//input onChange
onUpdate = name => (event) => {
this.setState({ [name]: event.target.value });
};
with no luck.
How can i made it work on multi state for multi input fields ?
Don't need to keep state in both Child and parent. You can write your child component like below, and you can access tow states dynamically by using data-attirb or you can folloe #Isaac 's answer.Keep the state in Child and pass state to Parent or keep the event to Parent from Child.
export class Child extends React.Component {
update = (e) => {
this.props.onUpdate(e.target)
};
render() {
return (
<div>
<h4>Child</h4>
<input
type="text"
placeholder="type here"
onChange={this.update}
data-state = "fieldVal"
value={this.props.fieldVal}
/><br/><br/>
<input
type="text"
placeholder="type here"
onChange={this.update}
data-state = "otherFieldVal"
value={this.props.otherFieldVal}
/>
</div>
)
}
}
export class OtherChild extends React.Component {
render() {
return (
<div>
<h4>OtherChild</h4>
Value in OtherChild Props passedVal1: {this.props.passedVal1} <br/>
Value in OtherChild Props passedVal2: {this.props.passedVal2}
</div>
)
}
}
and in parent :
class App extends Component {
onUpdate = (data) => {
this.setState({
[data.dataset.state]: data.value
})
};
render() {
return (
<div>
<h2>Parent</h2>
Value in Parent Component State fieldVal: {this.state.fieldVal} <br/>
Value in Parent Component State otherFieldVal: {this.state.otherFieldVal}
<br/>
<Child onUpdate={this.onUpdate} fieldVal= {this.state.fieldVal} otherFieldVal ={this.state.otherFieldVal}/>
<br />
<OtherChild passedVal1={this.state.fieldVal} passedVal2={this.state.otherFieldVal}/>
</div>
);
}
}
demo
renderInput = (prop) => {
return (
<Input
onChange={(event) => {
this.setState({ [prop]: event.target.value });
}}
/>
)
}
render() {
<div>
{this.renderInput('name')}
{this.renderInput('age')}
</div>
}
We can set a renderInput method and render different input using parameter to achieve your objective

react will not allow me to push two elements

I'm trying to pass 2 elements within the todo array, however it just returns the term element value. I see no error in the console.
It seems that the
items: [...this.state.items, this.state.term, this.state.name ]
only accepts two parameters.
I'm currently following this
https://reactjs.org/docs/handling-events.html
const { Component } = React;
class App extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
name: '',
items: []
};
}
onChange = (event) => {
this.setState({name: event.target.value, term: event.target.value});
}
onSubmit = (event) => {
event.preventDefault();
this.setState({
term: '',
name: '',
items: [
...this.state.items,
this.state.term,
this.state.name
]
});
}
render() {
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input value={this.state.term} onChange={this.onChange}/>
<input value={this.state.name} onChange={this.onChange}/>
<button>Submit</button>
</form>
<pre>{JSON.stringify(this.state.items)}</pre>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I suspect the issue you're asking about is the fact that your onChange event updates both term and name and whatever you type into one input goes into the other. Here's how you can resolve that:
Add a name attribute to your input that corresponds to the key in the state.
Access the value of name in onChange and update the value accordingly.
Solution
const { Component } = React;
class App extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
name: '',
items: []
};
}
onChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
}
onSubmit = (event) => {
event.preventDefault();
this.setState({
term: '',
name: '',
items: [
...this.state.items,
this.state.term,
this.state.name
]
});
}
render() {
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input name="term" value={this.state.term} onChange={this.onChange}/>
<input name="name" value={this.state.name} onChange={this.onChange}/>
<button>Submit</button>
</form>
<pre>{JSON.stringify(this.state.items)}</pre>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I dont think your this.setState has any issue for your example.
You seem to be using this.onChange wrong.
I changed its implementation so that both input can have their own respective handler.
onChange = key => event => {
this.setState({ [key]: event.target.value }); };
Also changed input callbacks to pass key value from the render function below.
render() {
const { term, name, items } = this.state;
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input value={term} onChange={this.onChange('term')} />
<input value={name} onChange={this.onChange('name')} />
<button>Submit</button>
</form>
{items.join(", ")}
</div>
);
}
}
Your prior code was problematic which was rendering same value for name and term with whatever you type in any of your input boxes.

React updating props with state data

I have a Component that is handling a contact forum submission from a user. I want to take the state that the user submits and add it to my props data. Right now everything is working, but the handleSubmit, I am not sure how to take the state and pass it to my this.data.props to update the data to include the new object.
My data is an array of Objects. The state takes user input and updates itself. Next I want to take the state object and add it to my props.data and then display it on the screen.
EDIT: UPDATED WITH LATEST CODE
import React, { Component, PropTypes } from 'react';
const testData = [
{
name: 'Joe',
email: 'joemail'
},
{
name: 'Bill',
email: 'billmail'
},
{
name: 'Dude',
email: 'dudemail'
}
]
class FormContact extends Component {
constructor(props) {
super(props)
this.state = {
formValues: {
name: '',
email: ''
}
}
}
handleChange(event) {
let formValues = this.state.formValues;
let name = event.target.name;
let value = event.target.value;
formValues[name] = value;
this.setState({
formValues
});
}
handleSubmit(event) {
event.preventDefault();
console.log("NEW FORM VALUES " + this.state.formValues.name + " " + this.state.formValues.email);
const {name, email} = this.state.formValues
this.props.addContact({name, email});
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<label> Name:
<input type="text" name="name" placeholder="Name" value={this.state.formValues["name"]} onChange={this.handleChange.bind(this)} />
</label><br />
<label> Email:
<input type="text" name="email" placeholder="Email" value={this.state.formValues["email"]} onChange={this.handleChange.bind(this)}/>
</label><br />
<input className="btn btn-primary" type="submit" value="Submit" />
</form>
)
}
}
FormContact.PropTypes = {
data: PropTypes.arrayOf(
PropTypes.object
)
}
FormContact.defaultProps = {
data: testData
}
class Contact extends Component {
constructor(props) {
super(props)
this.state = {
data: testData
}
}
addContact(contact) {
this.setState({data: this.state.data.concat(contact)});
}
render() {
const renObjData = this.props.data.map( (anObjectMapped, index) => {
return (<p key={index}>
Name: {anObjectMapped.name} < br/>
Email: {anObjectMapped.email} <br /></p>
)
});
return (
<div>
<h1>CONTACT PAGE</h1>
<FormContact data={this.state.data} addContact={this.addContact.bind(this)} />
{renObjData}
</div>
)
}
}
Contact.PropTypes = {
data: PropTypes.arrayOf(
PropTypes.object
)
}
Contact.defaultProps = {
data: testData
}
export default Contact;
What you are looking at here is having a parent container that passes down data as props to the form component. You already have your Contact component so you can make it hold the data state.
How it would work is you would write a function on the Contact component called addContact and it would take a contact as an argument and then set its own state with the new contact IE concat it to its own data array through setting state.
class Contact extends React.Component {
constructor() {
super();
this.state = {
data: testData
}
}
addContact = (contact) => {
this.setState({data: this.state.data.concat(contact)});
};
render() {
const contacts = _.map(this.state.data, (value, index) => {
return <li key={index + value}> {value.email} {value.name} </li>
})
return (
<div>
<h1>CONTACT PAGE</h1>
<FormContact data={this.state.data} addContact={this.addContact} />
<h3> Contacts</h3>
<ul>{contacts} </ul>
</div>
)
}
}
and then in your handleSubmit function all you have to do is add
handleSubmit(event) {
event.preventDefault();
const {name, email} = this.state.formValues
this.props.addContact({name, email})
}
this will push it onto the data array in the parent component and then once the parent component updates it will pass that down as props to the form component.
Here is a code pen showing all that in action. http://codepen.io/finalfreq/pen/VKPXoN
UPDATE: Also in Contacts added how to display data, you can easily replace lodash _.map with this.state.data.map(function(value, index)

Resources