I would like to use AddItem to add items to a list in another component. However, I keep getting undefined.
How do I correctly add an item to a list?
I've put it inside a CodeSandbox too: https://codesandbox.io/s/Mjjrm3zrO
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [x.movies],
};
}
render() {
return (
<div>
<CreateNew addItem={item => this.setState({ movie: [item].concat(this.state.movie) })} />
{x.movies.map(movie => (
<Result key={movie.id} result={movie} addItem={item => this.setState({ genres: [item].concat(this.state.genres) })} />
))}
</div>
);
}
}
class CreateNew extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
genres: '',
};
}
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state.value)
console.log(this.props.item);
};
onChange = e => {
this.setState({ value: {'name': e.target.value}, genres: [{ name: 'Test', type: 1 }, { name: 'Foo', type: 10 }] });
console.log(this.state.value);
};
render() {
const { value, genres } = this.props;
return (
<form onSubmit={this.handleSubmit1}>
Add a new movie
<input onChange={this.onChange} type="text" />
<button type="submit">Save</button>
</form>
);
}
}
class Result extends React.Component {
render() {
const { result } = this.props;
return (
<div>
<li>
{result.name} {' '}
({result.genres.map(x => x.name).join(', ')}){' '}
</li>
</div>
);
}
}
Changes:
1. Instead of sending only name from child component send the whole state variable that will contain name and genres.
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state)
};
2. You are storing the initial value movies from json to state variable so use that state variable to create the ui, because you are updating the state variable once you adding any new item, so if you use initial json to create ui then new item will not reflect in ui.
{this.state.movies.map(movie => (
<Result key={movie.id} result={movie} />
))}
3. Update the state variable movies like this:
<CreateNew addItem={item => this.setState({ movies: [{name: item.value.name, genres: item.genres}].concat(this.state.movies) })} />
Check the working solution: https://codesandbox.io/s/3nD0RgRp
Related
My code has 2 classes which are "Forum" and PostEditor". When the button "Post" is clicked which is in the "PostEditor" class, the textarea which has the state "newPostBody" is submitted but the input which has the state "newTitle" is not submitted in ReactJs. Below there is an image of it. What am I doing wrong?
const Post = props => (
<div>
<div >{props.postBody}</div>
</div>
);
class PostEditor extends Component {
constructor(props) {
super(props);
this.state = {
newPostBody: '',
newTitle: ''
};
}
handleInputChange(ev) {
this.setState({
newPostBody: ev.target.value
});
}
handleTitleChange(ev) {
this.setState({
newTitle: ev.target.value
});
}
createPost() {
this.props.addPost(this.state.newPostBody, this.state.newTitle);
this.setState({
newPostBody: '',
newTitle: ''
});
}
render() {
return (
<div>
<div>
<input type="text" value={this.state.newTitle}
onChange={this.handleTitleChange.bind(this)}/>
<textarea value={this.state.newPostBody}
onChange={this.handleInputChange.bind(this)} />
<button onClick={this.createPost.bind(this)}
disabled={!this.state.newPostBody} > Post </button>
</div>
</div>
);
}
}
class Forum extends Component {
constructor(props) {
super(props);
this.state = {
posts: [],
};
}
addPost(newPostBody) {
const newState = Object.assign({}, this.state);
newState.posts.push(newPostBody);
this.setState(newState);
}
render() {
return (
<div>
{this.state.posts.map((postBody, idx) => {
return (
<div >
<Post key={idx} postBody={postBody} />
</div>);
})}
<PostEditor addPost={this.addPost.bind(this)} />
</div>
);
}
}
Your addPost method in Forum class is only expecting body and not expecting 2nd argument which I guess would be title:
You should update it with something like below:
class Forum extends Component {
constructor(props) {
super(props);
this.state = {
posts: [],
};
}
addPost(newPostBody, newPostTitle) {
const newState = Object.assign({}, this.state);
let post = {body: newPostBody, title: newPostTitle}
newState.posts.push(post);
this.setState(newState);
}
render() {
return (
<div>
{this.state.posts.map((post, idx) => {
return (
<div >
<Post key={idx} postBody={post.body} postTitle={post.title} />
</div>);
})}
<PostEditor addPost={this.addPost.bind(this)} />
</div>
);
}
}
Post.js
const Post = props => (
<div>
<div> {props.postTitle} </div>
<div> {props.postBody} </div>
</div>
);
it looks like in your addPost function after clicking the Post button, you are not receiving the same params as the arguments you provided from the createPost:
createPost() {
this.props.addPost(this.state.newPostBody, this.state.newTitle); <== 2 args
this.setState({
newPostBody: '',
newTitle: ''
});
}
vs
addPost(newPostBody) { <== 1 arg only
const newState = Object.assign({}, this.state);
newState.posts.push(newPostBody);
this.setState(newState);
}
I think you want to make the call to addPost look like: this.props.addPost({body: this.state.newPostBody, title: this.state.newTitle})
Then you will have to update the Post UI to handle both title and body values.
I have a List of products-ID and a button. When I press the button, I want to refresh the data in the ListComponent. I have no idea how can I do this in React. Can someone help me?
constructor(props) {
super(props);
this.state = {
products: this.props.productData //where productData an array of all products-ID
};
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({ products: null });
this.forceUpdate();
}
render() {
const { products } = this.state;
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
);
}
}
const mapStateToProps = (state, ownProps) => {
const products = selectAllProducts(state); //function that fetches-takes all products
return {
productData: products.map(products => ({
productId: product.get("productId")
}))
};
};
Your refresh function needs to call an action that fetches the data, and updates the Redux store accordingly. And because you've mapped part of your Redux state to this component's props, it will re-render when that data is fetched and saved via the reducer.
Therefore, you don't need to set local state at all in this component. Provided you have an action called fetchProductData:
class ProductList extends React.Component {
constructor (props) {
super(props)
this.refresh = this.refresh.bind(this)
}
// if you don't already have the data in your store, you can fetch it here to kick things off
componentDidMount () {
this.props.fetchProductData()
}
refresh () {
this.props.fetchProductData()
}
render () {
const { products } = this.state
return (
<div>
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
const products = selectAllProducts(state)
return {
productData: products.map(products => ({
productId: product.get("productId")
}))
}
}
export default connect(mapStateToProps, { fetchProductData })(MyComponent)
Again, this assumes that fetchProductData dispatches an action that will update the redux state where products are stored. Passing the action to connect like this will make it available as a prop within the component.
It looks like you've placed your refresh() inside the constructor, try:
constructor(props) {
super(props);
this.state = {
products: this.props.productData //where productData an array of all products-ID
};
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({ products: null });
this.forceUpdate();
}
render() {
const { products } = this.state;
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
);
}
I made a minimal component that does what you want it to do. Instead of binding in the constructor i use a fat arrow function for refresh.
import { Component } from "react";
const ListItem = props => props.item.text;
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [{ id: 0, text: "zero" }, { id: 1, text: "one" }]
};
}
refresh = () => {
this.setState({ items: [] });
};
render() {
const { items } = this.state;
return (
<div>
{items.map(i => (
<div key={i.id}>
<ListItem item={i} />
</div>
))}
<button onClick={this.refresh}>refresh</button>
</div>
);
}
}
export default List;
You don't need to forceUpdate(), the component will re-render by default when its props are changed.
For an explanation of the fat arrow and what it does to this, check out https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4.
I'm having trouble setting the state of a component in React. The component is called "Search" and uses react-select. The full component is here:
class Search extends React.Component {
constructor(props){
super(props);
let options = [];
for (var x in props.vals){
options.push({ value: props.vals[x], label: props.vals[x], searchId: x });
};
this.state = {
inputValue: '',
value: options
};
}
handleChange = (value: any, actionMeta: any) => {
if(actionMeta.action == "remove-value"){
this.props.onRemoveSearch({ searchId: actionMeta.removedValue.searchId })
}
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleSearch = ({ value, inputValue }) => {
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)], // Eventually like to take this out...
});
this.props.onSearch({ inputValue });
}
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.handleSearch({
value,
inputValue
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
className={"tags"}
components={components}
inputValue={inputValue}
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Add filters here..."
value={value}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
You've probably noticed the strange thing that I'm doing in the constructor function. That's because I need to use data from my firebase database, which is in object form, but react-select expects an array of objects
with a "value" and "label" property. Here's what my data looks like:
To bridge the gap, I wrote a for-in loop which creates the array (called options) and passes that to state.value.
The problem: Because I'm using this "for in" loop, React doesn't recognize when the props have been changed. Thus, the react-select component doesn't re-render. How do I pass down these props (either modifying them inside the parent component or within the Search component) so that the Search component will re-render?
I would suggest not using the value state. What you do is simply copying props into your state. You can use props in render() method directly.
I reckon you use the value state because you need to update it based on user actions. In this case, you could lift this state up into the parent component.
class Parent extends React.Component {
constructor() {
this.state = { value: //structure should be the same as props.vals in ur code };
}
render() {
return (
<Search vals={this.state.value}/>
);
}
}
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
inputValue: '',
};
}
render() {
const { inputValue } = this.state;
const { vals } = this.props;
let options = [];
for (var x in vals){
options.push({ value: vals[x], label: vals[x], searchId: x });
};
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
value={options}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
I am using addItem to add a value to a list from another component
I am adding it to this.state.movies. It appears, however it has the inactive/noresults className applied to it.
How do I determine which styling is applied to an item that has not appeared yet (ie using addItem)? Thanks
Full example on Codesandbox is here. Add an movie to the list and you will see it gets the stying applied: https://codesandbox.io/s/3OGK2pP9
Parent component where I add the item
<CreateNew addItem={item => this.setState({ movies: [{ name: item.value.name, genres: item.genres }].concat( movies, ), })} />
Child component that creates the item
class CreateNew extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
genres: '',
};
}
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state);
};
onChange = e => {
this.setState({
value: { name: e.target.value },
genres: [{ name: 'Test', type: 1 }, { name: 'Foo', type: 10 }],
});
};
render() {
const { value, genres } = this.props;
return (
<form onSubmit={this.handleSubmit1}>
Add a new movie
<input onChange={this.onChange} value={value} type="text" />
<button type="submit">Add</button>
</form>
);
}
}
Was related to filtering on my const x instead of my state this.state.movies.
I changed it from const filteredResults = andFilter({x}, Object.keys(selectedFilters)); to `const filteredResults = andFilter({this.state.movies}, Object.keys(selectedFilters));
I have child components that receive props from a parent, but on an event (button click) in a child I want to setState again with the new props. So the parent passes down all items in a list to the children. In the child prop a button deletes an item in the list. But how can you update the state so the list item is also removed from the view. This is my code:
const Parent = React.createClass({
getInitialState:function(){
return {
items: []
};
},
componentWillMount:function(){
axios.get('/comments')
.then(function(response) {
this.setState({
items: response.data
})
}.bind(this));
},
render() {
return (
<div>
<Child1 data={this.state.items}/>
</div>
);
}
});
export default Parent;
export default function Child1(props){
return(
<div>
{ props.data.map((comment,id) =>(
<p key={id}>
{comment.name}<Delete data={comment.id}/>
</p>
)
)}
</div>
)
}
class Delete extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
Purchase.Action(this.props.data,'remove');
axios.post('/comments', {
item: this.props.data
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
}
render() {
return <Button onClick={this.handleClick}>Delete</Button>;
}
}
module.exports = Delete;
So although the comment is deleted at the server, I want to delete the comment from the component view by updating the state.
If you want to delete the comment from the component, you have to update your Parent state.
In order to do that you can create a new method, delete(id), in your Parent component where you remove the deleted item from the state.
const Parent = React.createClass({
getInitialState:function(){
return {
items: []
};
},
componentWillMount:function(){
this.setState({
items: [
{id: 1,name: "Name 1"},
{id: 2,name: "Name 2"},
{id: 3,name: "Name 3"}
]
})
},
delete(id){
// axios.post(...)
let items = this.state.items.filter(item => item.id !== id);
this.setState({items});
},
render() {
return (
<div>
<Child1
data={this.state.items}
handleClick={this.delete} // Pass the method here
/>
</div>
);
}
});
function Child1(props){
return(
<div>
{ props.data.map((comment,id) =>(
<p key={id}>
{comment.name}
<Delete
data={comment.id}
handleClick={() => props.handleClick(comment.id)} // Pass the id here
/>
</p>
)
)}
</div>
)
}
class Delete extends React.Component {
constructor(props) {
super(props);
}
render() {
return <button onClick={this.props.handleClick}>Delete</button>;
}
}
jsfiddle