When I add data to the table it does not get populated with the new record. I created a list and displayed it to see if it populates the new record, it seems to have updated there. I add a console.log to the componentWillReceiveProps function to see if the list updates with the new record, the list does get updated but the table never updates. Fairly new to react, not sure what I am missing.
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/postActions';
import MaterialTable from 'material-table';
class Posts extends Component {
componentWillMount() {
this.props.fetchPosts();
}
componentWillReceiveProps(nextProps) {
if(nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
render () {
const postItems = this.props.posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
));
return (
<div>
<div>
<h1>Posts</h1>
{postItems}
</div>
<div style={{marginLeft:'15px',marginRight:'15px'}}>
<div style={{ maxWidth: "100%" }}>
<MaterialTable
title="Users List"
columns={[
{ title: 'User Name', field: 'id' },
{ title: 'Email', field: 'title' },
]}
data={this.props.posts}
/>
<br/><br/><br/>
</div>
</div>
</div>
);
}
}
Posts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.array.isRequired,
newPost: PropTypes.object
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchPosts })(Posts);
You cannot set props from inside the component, only from outside. That's the difference between state and props (it's quite fundamental in React, read about it).
First, initialize the state in constructor:
constructor(props) {
super(props);
this.state = { posts: [] };
}
then, update the state when props change:
componentWillReceiveProps(nextProps) {
if(nextProps.newPost) {
let updatedPosts = this.state.props;
updatedPosts.unshift(nextProps.newPost);
this.setState({ posts: updatedPosts });
}
}
}
finally, use the state in your table:
...
data={this.state.posts}
...
Related
I am creating a todo list where when the user clicks the checkbox "complete" that is next to the todo item, it appears in the complete component however there is a duplicate of that item that is being added as well and i am also having an issue trying to have the checkbox not appear in the completed component...
When a user creates a new todo it appears in the active component first and it has a checkbox next to it called completed and when the user clicks the checkbox it appears in the completed component
import React from 'react';
import Active from './Components/Active';
import Completed from './Components/Completed';
import Todoform from './Components/Todoform';
import './App.css';
class App extends React.Component {
state = {
items: [],
task: '',
id: 0,
completedItems: []
}
handleInput = (event) => {
this.setState({
task: event.target.value
})
}
handleSubmit = (event) => {
event.preventDefault()
const newTask = {
id: this.state.id,
title: this.state.task
}
const updatedItems = [...this.state.items, newTask]
this.setState({
items: updatedItems,
task: '',
id: this.state.id + 1
})
}
handleComplete = (newTask) => {
this.setState({completedItems: [...this.state.items, newTask]})
//console.log(this.state.items)
}
render() {
return (
<div id="main-content">
<h1>Task Lister</h1>
<Todoform
handleChange={this.handleInput}
handleSubmit={this.handleSubmit}
task={this.state.task}
/>
<Active
items={this.state.items}
handleComplete={this.handleComplete}
/>
<Completed
completedItems={this.state.completedItems}
/>
</div>
)
}
}
export default App;
import React from 'react'
class Todo extends React.Component{
state = {
checked: false
}
handleCheck = () => {
this.setState({
checked: !this.state.checked
})
}
handleClick = () => {
this.props.handlecompletedList(this.props.title)
}
render(){
const { title } = this.props
return (
<div className="ui checked checkbox">
<input type="checkbox" checked={this.state.checked} onChange={this.handleCheck}
onClick={this.handleClick}/>
<label>Completed {title}</label>
</div>
)
}
}
export default Todo;
import React from 'react'
import Todo from './Todo'
const Active = (props) => {
const { items, handleComplete } = props
return(
<div id="activeList">
<h2 className="position">Active</h2>
<ul id="tasks">
{
items.map(item => {
return(
<Todo key={item.id} handlecompletedList={handleComplete} title={item.title}/>
)
})
}
</ul>
</div>
)
}
export default Active;
import React from 'react'
import Todo from './Todo'
const Completed = (props) => {
const { completedItems } = props
return(
<div id="completedList">
<h2 className="position">Completed</h2>
<ul id="tasks">
{
completedItems.map(item => {
return(
<Todo key={item.id} title={item.title}/>
)
})
}
</ul>
</div>
)
}
export default Completed
import React from 'react';
class Todoform extends React.Component {
render(){
const {task, handleChange, handleSubmit} = this.props;
return(
<form onSubmit={handleSubmit}>
<label>Task description:</label>
<input type="text" name="name" placeholder="description" value={task} onChange={handleChange}/>
<button>Create New Task</button>
</form>
)
}
}
export default Todoform;
To hide the checkbox next to completed items you need to use Conditional Rendering. An example would be to add a prop IsCompleted to your component and use it when rendering html like this:
{this.props.isCompleted &&
<input
type="checkbox"
checked={this.state.checked}
onChange={this.handleCheck}
onClick={this.handleClick}/>
}
The duplicate item issue is probably because you use this.state.items in your handleComplete method instead of using this.state.completedItems if this is not the issue, would you mind sharing the code for the Todoform component as well?
EDIT: The item duplicates because when the handleComplete is called it copies this.state.items to the list and adds the one that you clicked on.
You should use this.state.completedItems in the handleComplete, also you are currently only sending and appending the title in the handleComplete method, you should be appending an object that has a title. The solution would be to update your handleClick method to this and update handleComplete to use this.state.completedItems:
handleClick = () => {
this.props.handlecompletedList({
title: this.props.title
});
};
I'm creating a simple CRUD React app that let's you manage products. Products only have a name and a price.
In my AddProduct component, my onSubmit method can successfully log this.nameInput.value, this.priceInput.value, but when I change the log to this.props.onAdd I get this.props.onAdd is not a function.
I'm following a tutorial, so I'm sure I'm missing one small thing, but could use another set of eyes on my code.
Here's my addProduct component - the onSubmit method has the this.props.onadd(...):
import React, { Component } from 'react';
class AddProduct extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(event) {
event.preventDefault();
this.props.onAdd(this.nameInput.value, this.priceInput.value);
}
render() {
return (
<form onSubmit={this.onSubmit}>
<h3>Add Product</h3>
<input placeholder="Name" ref={nameInput => this.nameInput = nameInput}/>
<input placeholder="Price" ref={priceInput => this.priceInput = priceInput}/>
<button>Add</button>
<hr />
</form>
);
}
}
And here's my App.js:
import React, { Component } from 'react';
import './App.css';
import ProductItem from './ProductItem';
import AddProduct from './AddProduct';
const products = [
{
name: 'iPad',
price: 200
},
{
name: 'iPhone',
price: 500
}
];
localStorage.setItem('products', JSON.stringify(products));
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: JSON.parse(localStorage.getItem('products'))
};
this.onAdd = this.onAdd.bind(this);
this.onDelete = this.onDelete.bind(this);
}
componentWillMount() {
const products = this.getProducts();
this.setState({ products });
}
getProducts() {
return this.state.products;
}
onAdd(name, price) {
const products = this.getProducts();
products.push({
name,
price
});
this.setState({ products })
}
onDelete(name) {
const products = this.getProducts();
const filteredProducts = products.filter(product => {
return product.name !== name;
});
this.setState({ products: filteredProducts });
}
render() {
return (
<div className="App">
<h1>Products Manager</h1>
<AddProduct
/>
{
this.state.products.map(product => {
return (
<ProductItem
key={product.name}
{...product}
onDelete={this.onDelete}
/>
);
})
}
</div>
);
}
}
export default App;
What's the matter with my code? When I click the Add button, I get the error.
It's because you pass no prop to the <AddProduct /> you render.
You should add it like so:
<AddProduct onAdd={this.onAdd}/>
You need to pass the props to the component:
<AddProduct onAdd={onAdd} />
Just change the following line
<form onSubmit={this.onSubmit}>
to
<form onSubmit={this.onSubmit.bind(this)}>
onAdd is a field in the props of AddProduct. It means you should delivery a function to AddProduct in App. like: <AddProduct onAdd={() => console.log('works')} />
I created a project in Reactjs where I have a list of names that I want to display in a custom list. Each item has a button to delete the item, however whenever I click the button, the last item is removed from the list no matter which list item I click.
I have already tried to debug my code using the js-console but that made the problem even stranger since the console displays the correct state wheras the component "List" renders a list item which is no longer present in the state object
import React, { Component } from 'react';
import './ListItem'
import ListItem from './ListItem';
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{name: 'Tobi'},
{name: 'Maxi'},
{name: 'David'},
{name: 'Peter'},
]
}
}
removeItem = (id) => {
let few = this.state.items;
few.splice(id,1);
//console.log(this.state.items);
this.setState({items: few}, function(){
console.log(this.state.items.map((item) => item.name));
this.forceUpdate();
});
}
render() {
return (
<div>
<ul>
{this.state.items.map((item, i) => <ListItem name={item.name} key={i} id={i} remove={this.removeItem}/>)}
</ul>
</div>
);
}
}
import React, { Component } from 'react';
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name,
id: this.props.id
}
}
test = () => {
this.props.remove(this.state.id);
}
render() {
return (
<li>{this.state.name} <button onClick={() => this.test()}>click me</button></li>
);
}
}
export default ListItem;
As is said i excpected the right list item to be removed however it is always the last item that isnt rendered anymore even though the state object says different.
The main problem is that you're using an array index as a key. When you first render the ListItems you have :
ListItem name={'Tobi'} key={0}
ListItem name={'Maxi'} key={1}
ListItem name={'David'} key={2}
ListItem name={'Peter'} key={3}
Let's say you removed the item with index 1, all other items will shift index:
ListItem name={'Tobi'} key={0}
ListItem name={'David'} key={1}
ListItem name={'Peter'} key={2}
React will only compare the keys, and because the only difference between the first and second render is that the item with key={3} is not present, this is the item that will be removed from the dom.
Also avoid mutating the state directly (few.splice(id,1)), and try to avoid this.forceUpdate()
Try using an actual id in your data :
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, name: "Tobi" },
{ id: 2, name: "Maxi" },
{ id: 3, name: "David" },
{ id: 4, name: "Peter" }
]
};
}
removeItem = id => {
let few = this.state.items.filter(item => item.id !==id);
//console.log(this.state.items);
this.setState({ items: few }, function() {
console.log(this.state.items.map(item => item.name));
//this.forceUpdate();
});
};
render() {
return (
<div>
<ul>
{this.state.items.map((item, i) => (
<ListItem
name={item.name}
key={item.id}
id={item.id}
remove={this.removeItem}
/>
))}
</ul>
</div>
);
}
}
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name,
id: this.props.id
};
}
test = () => {
this.props.remove(this.state.id);
};
render() {
return (
<li>
{this.state.name} <button onClick={() => this.test()}>click me</button>
</li>
);
}
}
function App() {
return (
<div className="App">
<List />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I'm getting this error 'Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of', When not adding the key in child component.
Why is this key necessary include here? because without this also component render correctly.
component - (parent)
import React, { Component } from 'react';
import ChildKey from './ChildKey';
import './App.css';
class ParentKey extends Component {
constructor() {
super();
this.state = {
keyList: [{
name: 'key1',
},{
name: 'key1',
},{
name: 'key1',
},{
name: 'key1'
}]
}
}
render() {
const {keyList} = this.state;
return (
<div style={{marginLeft: '40%'}}>
<h1>Parent Component</h1>
<br/>
<div>
{
keyList && keyList.map((data, index) => <ChildKey name={data.name} />)
}
</div>
</div>
)
}
}
export default ParentKey;
component (children) -
import React, {Component} from 'react';
export default class ChildKey extends Component {
constructor() {
super();
}
getStyle() {
return {
rootStyle: {
width: '40px',
height: '40px',
display: 'block'
}
}
}
render() {
const styles = this.getStyle();
return (
<div style={styles.rootStyle}>
<div>{this.props.name}</div>
</div>
)
}
}
It is necessary in keyList && keyList.map((data, index) => <ChildKey name={data.name} />) because when one row changes React needs to know which row changed and it will update only that specific row. Otherwise React will re-render all the rows and this is not good for the performance
Example: keyList && keyList.map((data, index) => <ChildKey key={data.name} name={data.name} />)
I have this class:
import React from 'react';
import {Link} from 'react-router';
import '../../styles/about-page.css';
import Item from './Item';
// Redux
import { connect } from 'react-redux';
import actions from '../../redux/actions';
import { bindActionCreators } from 'redux';
// Auth
import Auth from '../../modules/Auth';
import User from '../../constants';
class CommentForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit() {
// alert('A name was submitted: ' + this.state.value);
this.props.onFinish({
name: this.props.user.name,
userId: this.props.user.id,
comment: this.state.value
});
}
render() {
if (!this.props.user || !this.props.user.name) return <div/>;
return (<div className="item">
{this.props.user.name}:
<label style={{width: '60%', margin: '10px'}}>
<input style={{width: '100%'}} type="text" value={this.state.value} onChange={this.handleChange.bind(this)} />
</label>
<input style={{width: '16%', display: 'inline-block', margin: '10px'}} type="submit" value="Enviar" onClick={this.handleSubmit.bind(this)}/>
</div>
);
}
}
#connect((state) => state)
class ItemPage extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null,
pret: null
};
}
componentWillMount() {
let self = this;
this.props.actions.getPret(this.props.routeParams.id).then(function(a) {
self.setState({
pret: self.props.pret
})
});
if (Auth.isUserAuthenticated()) {
User.getBearer(Auth, function(info) {
self.setState({user: info});
});
}
}
onFinish(comment) {
//changing the state in react
//need to add '6' in the array
//create a copy of this.state.a
//you can use ES6's destructuring or loadash's _.clone()
const currentStatePretCopy = Object.assign({}, this.state.pret, { b: this.state.pret.comments.concat([comment])})
console.log(1, currentStatePretCopy);
currentStatePretCopy.comments.push(comment);
this.props.actions.updatePret(currentStatePretCopy);
}
render() {
let self = this;
if (!this.state || !this.state.pret) return <div/>;
return (<div>
<section>
<Item full={true} user={this.state.user} item={this.state.pret} actions={this.state.actions}/>
</section>
<div>
<CommentForm user={this.state.user} pret={this.state.pret} onFinish={this.onFinish.bind(this)}/>
{/* TODO: ad here */}
{this.state.pret.comments && this.state.pret.comments.length ? this.state.pret.comments.map(function (comment, index) {
return (<div className="item" key={index}> by <Link to={'/user/' + comment.userId}> #{comment.name} </Link> : {comment.comment} </div>);
}) : null}
</div>
</div>
);
}
}
function mapStateToProps (state) {
return {
pret: state.prets[0]
};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ItemPage);
When i want to update the object, since props should be immutable, the documentation suggest cloning the object and put it in the state.
I am modifying the state with the new value and sending it to Redux actions but the system complains:
Uncaught Error: A state mutation was detected between dispatches, in the path 'prets.0.comments.1'. This may cause incorrect behavior.
Since i copy the object I do not know how should I update the store via React+Redux
The comments array is still the original one. So you are mutating the original object with push.
Replace
currentStatePretCopy.comments.push(comment);
With
currentStatePretCopy.comments = currentStatePretCopy.comments.concat([comment])
And everything should work fine