Whenever I trigger readItem() and handleChange() afterwards, browser throws error "this.setState is not a function". HandleChange() works well otherwise. Could someone advise what is causing the error and how to fix it? I also expecting the readtItem() function to update the state, which doesn't happen. Any help will be appreciated.
Thanks
import React from 'react';
import ListItems from '../src/components/ListItems/ListItems.component';
import Button from '../src/components/button/button.component';
import axios from 'axios';
// import axios from 'axios';
// import Home from './components/home/home.component';
import './App.css';
class App extends React.Component {
constructor(props){
super(props);
this.state = {
items:[],
readItem: {
name:'',
email:'',
phone:'',
myId:''
},
currentItem:{
name:'',
email:'',
phone:''
}
}
}
addItem = e => {
e.preventDefault();
const { name,email,phone } = this.state.currentItem;
const myId = Math.random().toString();
const items = [...this.state.items, {name, email, phone, myId: myId}];
this.setState({
items: items,
currentItem:{
name:'',
email:'',
phone:'',
myId:''
}
})
const config = {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials':true
}
};
axios.post('/records', {name , email, phone, myId: myId}, config)
.then(res => {
console.log(res);
console.log(res.data);
})
}
handleChange = event => {
const { name, value } = event.target;
this.setState({
currentItem: {
...this.state.currentItem, [name]: value
}
});
}
deleteItem = myId => {
const filteredItems= this.state.items.filter( item => item.myId!==myId);
this.setState({
items: filteredItems
})
axios.delete('/records/' + myId, {data: {id: myId}})
.then(res => console.log(res))
}
componentDidMount(){
axios.get('/records')
.then(res => {
this.setState({items:res.data});
})
}
readItem = item => {
this.setState = {
readItem: item
}
}
render(){
return (
<div className="App">
<div className='container'>
<div className='form-container'>
<form className='form' onSubmit={this.addItem}>
<input type='text' name='name' placeholder='Name' autoComplete='off' onChange={this.handleChange} value={this.state.currentItem.name} />
<input type='text' name='email' placeholder='Email Address' autoComplete='off' onChange={this.handleChange} value={this.state.currentItem.email} />
<input type='text' name='phone' placeholder='Mobile Number' autoComplete='off' onChange={this.handleChange} value={this.state.currentItem.phone} />
<Button className='submit-button' type='submit' name='Submit' color='white'/>
</form>
</div>
<div className='table-container'>
{
this.state.items.length > 0 ?
(<table className='table'>
<thead className='table-header'>
<tr>
<th>Name</th>
<th>Email Address</th>
<th>Mobile Number</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<ListItems items={this.state.items} deleteItem={this.deleteItem} readItem={this.readItem}/>
</tbody>
</table> )
:null
}
</div>
</div>
{
this.state.readItem ?
(<div className='popup'>
<p>Name: </p>
<p>Email Address: </p>
<p>Mobile Number: </p>
</div>)
: null
}
</div>
);
}
}
export default App;
you can't update state like this.setState = {readItem: item}
setState is a function and should use like this.setState({items: filteredItems})
try to make all your methods arrow functions like handleChange=()=>{} they do not have this in normal function
As you are using this.setState, it sounds like you are using a Component class. When a callback is called on a child element (such as onChange etc.) The context (this) is set to the child element itself. For the correct "this" context to be preserved, you need to bind the callback functions.
In your class' constructor, add the following to bind your methods:
this.handleChange = this.handleChange.bind(this);
See here for more information on handling events
In the future please upload snippets of the code in question.
Make sure this is bound within your callbacks for ex. handleChange by adding the binding code inside your constructor.
constructor(props) {
super(props);
this.state = {
items:[],
readItem: {
name:'',
email:'',
phone:'',
myId:''
},
currentItem:{
name:'',
email:'',
phone:''
}
}
// Add below line for all callbacks
this.handleChange = this.handleChange.bind(this)
}
You can also use arrow function in the callback if you don't want to use the bind code inside constructor. Here is a class component CodeSandbox for both of the use cases. The example is adapted from official doc.
If you are working on a new codebase or new to React I would highly encourage you to use functional components in place of class components. In my opinion, functional components and useState APIs are much simpler and less verbose than class components. Here is a functional component CodeSandbox for the above example.
Related
I am trying to implement eye/eyeslash in on my Register form in React.
This is a function that's is responsible for changing visibility type and eye icon changing.
import React, { useState } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
export const usePasswordToggle = () => {
const [visible, setVisibility] = useState();
const Icon = <FontAwesomeIcon icon={visible ? "eye-slash" : "eye"} />;
const InputType = visible ? "text" : "password";
return [InputType, Icon];
};
I am trying to implement it in component responsible for registering.
import React, { Component, createRef } from "react";
import { usePasswordToggle } from "./usePasswordToggle";
class Register1 extends React.Component {
EmailR = createRef();
UsernameR = createRef();
PasswordR = createRef();
PasswordConfirmR = createRef();
constructor(props) {
super();
this.state = {
message: "",
password: "",
confirmPassword: "",
};
}
handleSubmit = (event) => {
// alert(this.PasswordR.current.value);
// alert(this.PasswordConfirmR.current.value);
if (this.PasswordR.current.value !== this.PasswordConfirmR.current.value) {
alert("The passwords doesn't match");
return false; // The form won't submit
} else {
alert("The passwords do match");
return true; // The form will submit
}
};
onCreateAccount = () => {
let loginInfo = {
Username: this.UsernameR.current.value,
Email: this.EmailR.current.value,
Password: this.PasswordR.current.value,
};
fetch("http://localhost:5000/api/authenticate/register", {
method: "POST",
headers: { "Content-type": "application/json" },
body: JSON.stringify(loginInfo),
})
.then((r) => r.json())
.then((res) => {
if (res) {
this.setState({
message:
"New Account is Created Successfully. Check your email to verify Account.",
});
}
});
};
render() {
return (
<div>
<h2 className="FormDescription">
{" "}
Please enter Account details for registration
</h2>
<div className="Form">
<p>
<label>
Email: <input type="text" ref={this.EmailR} />
</label>
</p>
<p>
<label>
Username: <input type="text" ref={this.UsernameR} />
</label>
</p>
<div>
<label>
Password:{" "}
<input type={usePasswordToggle.InputType} ref={this.PasswordR} />
</label>
<span className="password-toogle-icon">
{usePasswordToggle.Icon}
</span>
</div>
<p>
<label>
ReenterPassword:{" "}
<input type="password" ref={this.PasswordConfirmR} />{" "}
</label>
</p>
<button onClick={this.handleSubmit}> Create </button>
<p>{this.state.message}</p>
</div>
</div>
);
}
}
export default Register1;
My password is always visible, and eye icon is even not visible on the form (it should be inside my input field, but it is not).
Focus on this code snippet:
<div>
<label>
Password: <input type={usePasswordToggle.InputType} ref={this.PasswordR} />
</label>
<span className="password-toogle-icon">{usePasswordToggle.Icon}</span>
</div>
Any suggestion what is the problem?
Change this
const [visible, setVisibility] = useState();
to this
const [visible, setVisible] = useState(true);
as the official documentation here
First, add a default value to your useState, either true or false depending on which icon you want to render first.
Then, you should add a onClick method to your icon which will toggle the visibility state. You're setting the icon based on visible value, but you never toggle the value.
onClick={() => setVisibility(!visible)}
UPDATE
You also need to execute your Hook inside your main component (because yes, you wrote what React call a Hook), like so :
const [inputType, icon] = usePasswordToggle();
But doing so, you'll get an error from React that say you cannot use a Hook within a class component due to how they work.
Basically you need to change your Register1 component to be a functional component, and not a class anymore. Look here for a quick overview on how to : https://reactjs.org/docs/components-and-props.html
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 = () => {
}
Problem
When a user checks/un-checks a checkbox, a post request is made via the onChange event. In order to avoid hammering the API, I am using _.debounce. However, I want to immediately update state when the onChange event is fired.
I understand that my current code wont't allow for that, since I am updating state in updateTodoItem(), which is run in the debounced handleChange() function.
Question.
How do I continue to debounce the post request, while immediacy invoking this.setState()?
Simplified Code
...
import _ from "lodash";
import axios from "axios";
import setAxiosHeaders from "./AxiosHeaders";
class TodoItem extends React.Component {
constructor(props) {
super(props);
this.state = {
complete: this.props.todoItem.complete
};
this.handleChange = this.handleChange.bind(this);
this.updateTodoItem = this.updateTodoItem.bind(this);
this.inputRef = React.createRef();
this.completedRef = React.createRef();
this.path = `/api/v1/todo_items/${this.props.todoItem.id}`;
}
handleChange() {
this.updateTodoItem();
}
updateTodoItem() {
this.setState({
complete: this.completedRef.current.checked
});
setAxiosHeaders();
axios
.put(this.path, {
todo_item: {
title: this.inputRef.current.value,
complete: this.completedRef.current.checked
}
})
.then(response => {})
.catch(error => {
console.log(error);
});
}
render() {
const { todoItem } = this.props;
return (
<tr className={`${this.state.complete ? "table-light" : ""}`}>
<td>
...
</td>
<td className="text-right">
<div className="form-check form-check-inline">
<input
type="boolean"
defaultChecked={this.state.complete}
type="checkbox"
onChange={_.debounce(this.handleChange, 1000)}
ref={this.completedRef}
className="form-check-input"
id={`complete-${todoItem.id}`}
/>
<label
className="form-check-label"
htmlFor={`complete-${todoItem.id}`}
>
Complete?
</label>
</div>
</td>
</tr>
);
}
}
export default TodoItem;
I just needed to call _.debounce() on updateTodoItem.
handleChange() {
this.setState({
complete: this.completedRef.current.checked
});
this.updateTodoItem();
}
updateTodoItem = _.debounce(() => {
setAxiosHeaders();
axios
.put(this.path, {
todo_item: {
title: this.inputRef.current.value,
complete: this.completedRef.current.checked
}
})
.then(response => {})
.catch(error => {
console.log(error);
});
}, 1000);
<input
type="boolean"
defaultChecked={this.state.complete}
type="checkbox"
onChange={this.handleChange}
ref={this.completedRef}
className="form-check-input"
id={`complete-${todoItem.id}`}
/>;
*edit to provide solution in comments
I have an app that renders 2 components, a SearchBar form and a Table of data. After the app mounts, an api call is made and setState is called, which triggers a render of the Table. It works fine.
The problem comes from the SearchBar component. On submission, the functional argument handleSubmit is called to make an api request and set the new state. SetState should trigger a render but it doesn't. The state is verified and accurate but there is no render.
Here is my code.
App.js
class App extends Component {
constructor(props) {
console.log('app constructor')
super(props)
this.state = {
items: [],
}
}
render() {
console.log('app render')
return (
<div>
<SearchBar onSubmit={this.handleSubmit} />
<Table data={this.state.items} />
</div>
)
}
componentDidMount() {
console.log('app mounted')
fetch('/api/items/?search=initial search')
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
console.log('state post mount ' + this.state.items.length)
})
}
handleSubmit(e) {
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
console.log('state post submit ' + this.state.items[0].name)
})
}
}
SearchBar.js
export default class SearchBar extends Component {
constructor(props) {
console.log('search bar constructor')
super(props)
this.onChange = this.handleChange.bind(this)
this.onSubmit = this.props.onSubmit.bind(this)
this.state = {
value: ''
}
}
handleChange(e) {
console.log('search bar changed ' + e.target.value)
this.setState({
searchBarValue: e.target.value
})
}
render() {
return (
<form className='form' id='searchForm' onSubmit={this.onSubmit}>
<input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
</form>
)
}
}
Table.js
export default class Table extends Component {
render() {
if (this.props.data.length === 0) {
return (
<p>Nothing to show</p>
)
} else {
return (
<div className="column">
<h2 className="subtitle">
Showing <strong>{this.props.data.length} items</strong>
</h2>
<table className="table is-striped">
<thead>
<tr>
{Object.entries(this.props.data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{this.props.data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</table>
</div>
)
}
}
}
Please set this in a variable, when function initiate:-
handleSubmit(e) {
let formthis=this;
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
formthis.setState({
items: data
})
console.log('state post submit ' + formthis.state.items[0].name)
})
}
AS I said in the comment, Remove this line this.onSubmit = this.props.onSubmit.bind(this) from the SearchBar component and replace this one
<form className='form' id='searchForm' onSubmit={this.onSubmit}>
with
<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
The problem is when you call bind the onSubmit from the props with the this as you did it is using the context of the SearchBar and not the parent so it sets the response to the state of the Search bar and not the App component which you want that way your items state of the parent component never changes an as such you don't get a re-render
Here is the relevant code for my solution. As harisu suggested, I changed the declaration of the form component. I also added a bind statement in the constructor of the parent.
App.js
class App extends Component {
constructor(props) {
console.log('app constructor')
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
items: [],
}
}
handleSubmit(e) {
e.preventDefault()
console.log('search bar submitted ' + e.target.elements.searchBar.value)
fetch(`/api/items/?search=${e.target.elements.searchBar.value}`)
.then(res => res.json())
.then((data) => {
this.setState({
items: data
})
})
console.log('state post submit ' + this.state.items[0].name)
}
}
SearchBar.js
export default class SearchBar extends Component {
render() {
return (
<form className='form' id='searchForm' onSubmit={this.props.onSubmit}>
<input type='text' className='input' id='searchBar' placeholder='Item, Boss, or Zone' onChange={this.onChange} />
</form>
)
}
}
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)