New to React, I am having hard time selecting an item from a recipe list. I am working on how to delete a recipe from the list, but first I want to figure out how to select that particular recipe.
Here's an example of a well-working demo:
https://www.freecodecamp.com/challenges/build-a-recipe-box
As you can see, each item has its own Delete button and I have done in my code as well.
I have the following code in my container:
src/containers/recipebox.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ListGroup, ListGroupItem, Panel, Button, Modals } from 'react-bootstrap'
import { bindActionCreators } from 'redux';
import { deleteRecipe } from '../actions/index';
class RecipeBox extends Component {
constructor(props){
super(props);
this.state = {
open: false
};
this.renderRecipeList = this.renderRecipeList.bind(this)
}
renderRecipeList(recipeItem,index){
const recipe = recipeItem.recipe;
const ingredients = recipeItem.ingredients;
return(
<div key={index}>
<Panel bsStyle="primary" collapsible header={<h3>{recipe}</h3>}>
<ListGroup >
<ListGroupItem header="Ingredients"></ListGroupItem>
{ingredients.map(function(ingredient,index){
return <ListGroupItem key={index}>{ingredient}</ListGroupItem>;
})}
<ListGroupItem>
<Button
onClick={this.props.deleteRecipe}
value={recipeItem}
bsStyle="danger">Delete
</Button>
<Button
onClick={() => console.log('EDIT!')}
bsStyle="info">Edit
</Button>
</ListGroupItem>
</ListGroup>
</Panel>
</div>
)
}
render(){
return(
<div className="container">
<div className='panel-group'>
{this.props.addRecipe.map(this.renderRecipeList)}
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
addRecipe : state.addRecipe
};
}
function mapDispatchToProps(dispatch){
return bindActionCreators({deleteRecipe : deleteRecipe}, dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(RecipeBox);
Where my Action is as so:
src/actions/index.js
export const RECIPE_ADD = 'RECIPE_ADD';
export const RECIPE_EDIT = 'RECIPE_EDIT';
export const RECIPE_DELETE = 'RECIPE_DELETE';
export function addRecipe(recipe) {
return {
type: RECIPE_ADD,
payload: recipe
}
}
export function editRecipe(recipe) {
return {
type: RECIPE_EDIT,
payload: recipe
}
}
export function deleteRecipe(event) {
return {
type: RECIPE_DELETE,
payload: event.target.value
}
}
Specifically I am looking at this in my container:
<Button
onClick={this.props.deleteRecipe}
value={recipeItem}
bsStyle="danger">Delete
</Button>
In my reducer, I am seeing as
payload: "[object Object]"
How do I use onClick event listener that will select the appropriate recipe from the list?
(note: I haven't implemented the reducer yet, I just want to see how I can see the action.payload to be the selected recipe)
EDIT:
I found the solution. Simply I needed to figure out how to pass an argument using onClick without invoking by itself.
The following ES6 code did the trick:
<Button
onClick={() => this.props.deleteRecipe(recipeItem)}
bsStyle="danger">Delete
</Button>
Cheers
I found the solution. Simply I needed to figure out how to pass an argument using onClick without invoking by itself. The following ES6 code did the trick:
<Button
onClick={() => this.props.deleteRecipe(recipeItem)}
bsStyle="danger">Delete
</Button
Related
So I am trying to gen a div with a button onClick of a button but I get an error that is stopping me from doing this.
Error: TypeError: this.state.addroom.map is not a function
But I saw that when I click my button once it doesn't show the error but it doesn't generate the div with the button either.
Here is my code:
import React, { Component } from 'react';
import Select, { components } from 'react-select';
import styles from '../styles/loginsignup.css'
import axios from 'axios'
import nextId from "react-id-generator";
export default class AccomodationInfo extends Component {
constructor() {
super();
this.state = {
accomcate: null,
addroom: ['one'],
isLoading: true,
}
}
handleClick = event => {
const htmlId = nextId()
event.preventDefault()
const addroom = this.state.addroom
this.setState({ addroom: htmlId })
return (
<div>
{this.state.addroom.map(addrooms => (
<button key= {addroom.id} className={addrooms.modifier}>
{addrooms.context}
</button>
))}
</div>
);
}
render() {
return(
<div>
<button onClick={this.handleClick}>Add</button>
</div>
)
}
}
}
Anyone knows what causes it and how we can fix it?
There are a few things off with your code.
First of all the addroom in your state is a string array in your constructor, but in the handleClick method you set it like this.setState({ addroom: htmlId }) which will set it to a string and on a string type the map function is not defined, hence the error.
You should add an item to the array like this.setState({ addroom: [...this.state.addroom, htmlId] })
Secondly, in your handleClick you shouldn't return jsx, if you wan to render data for your addroom array, you should do it in the render method, and in the handleClick you should just modify the addroom state variable.
You can achieve this like:
render() {
return (
<div>
<button onClick={this.handleClick}>Add</button>
{this.state.addroom.map((addroom) => (
<button>{addroom}</button>
))}
</div>
)
}
Lastly, your addrom variable is a string array only, so you can't access id, modifier and context in an item in that array.
I have created a simple Todo list, adding item works but when I clicked on the 'delete' button, my Item is not deleting any item from the List. I would like to know what mistakes I am making in my code, Would appreciate all the help I could get. Thanks in Advance!
And ofcourse, I have tried Looking through google and Youtube, But just couldnot find the answer I am looking for.
Link: https://codesandbox.io/embed/simple-todolist-react-2019oct-edbjf
App.js:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import TodoForm from "./TodoForm";
import Title from "./Title";
class App extends React.Component {
// myRef = React.createRef();
render() {
return (
<div className="App">
<Title />
<TodoForm />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
----------------------
TodoForm.js:
import React from "react";
import ListItems from "./ListItems";
class TodoForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: [],
id: 0
};
}
inputValue = e => {
this.setState({ value: e.target.value });
};
onSubmit = e => {
e.preventDefault();
this.setState({
value: "",
id: 0,
items: [...this.state.items, this.state.value]
});
};
deleteItem = (itemTobeDeleted, index) => {
console.log("itemTobeDeleted:", itemTobeDeleted);
const filteredItem = this.state.items.filter(item => {
return item !== itemTobeDeleted;
});
this.setState({
items: filteredItem
});
};
// remove = () => {
// console.log("removed me");
// };
render() {
// console.log(this.deleteItem);
console.log(this.state.items);
return (
<div>
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="Enter task"
value={this.state.value}
onChange={this.inputValue}
/>
<button>Add Item</button>
</form>
<ListItems items={this.state.items} delete={() => this.deleteItem} />
</div>
);
}
}
export default TodoForm;
----------------------
ListItems.js
import React from "react";
const ListItems = props => (
<div>
<ul>
{props.items.map((item, index) => {
return (
<li key={index}>
{" "}
{item}
<button onClick={props.delete(item)}>Delete</button>
</li>
);
})}
</ul>
</div>
);
export default ListItems;
The problem is, you must pass a function to the onDelete, but you are directly calling the function
updating the delete item like so,
deleteItem = (itemTobeDeleted, index) => (event) => {
and update this line, (since the itemTobeDeleted was not reaching back to the method)
<ListItems items={this.state.items} delete={(item) => this.deleteItem(item)} />
fixes the issue
Working sandbox : https://codesandbox.io/s/simple-todolist-react-2019oct-zt5w6
Here is the working example: https://codesandbox.io/s/simple-todolist-react-2019oct-xv3b5
You have to pass in the function into ListItems and in ListItems run it passing in the correct argument (the item).
Your solution is close; there are two fixes needed for your app to work as expected.
First, when rendering the ListItems component, ensure that the item is passed through to your deleteItem() function:
<ListItems items={this.state.items} delete={(item) => this.deleteItem(item)} />
Next, your ListItems component needs to be updated so that the delete callback prop is called after an onclick is invoked by a user (rather than immediatly during rendering of that component). This can be fixed by doing the following:
{ props.items.map((item, index) => {
return (<li key={index}>{item}
{/*
onClick is specified via inline callback arrow function, and
current item is passed to the delete callback prop
*/}
<button onClick={() => props.delete(item)}>Delete</button>
</li>);
)}
Here's a working version of your code sandbox
first make a delete function pass it a ind parameter and then use filter method on your array in which you saved the added values like
function delete(ind){
return array.filter((i)=>{
return i!==ind;
})
}
by doing this elements without the key which you tried to delete will not be returned and other elements will be returned.
Suppose I have two components which aren't nested: a button and a panel. When the button is clicked, the panel will show or hide depending on the previous state (like an on/off switch). They aren't nested components, so the structure looks like this:
<div>
<Toolbar>
<Button />
</Toolbar>
<Content>
...
<ButtonPanel />
</Content>
</div>
I can't change the structure of the DOM. I also can't modify any other component other than the button and panel components.
The Button and ButtonPanel components are related, however, and will be used together throughout the solution. I need to pass a property to the panel to let it know when to show or when to hide. I was thinking about doing it with Context API, but I think there's something I'm doing wrong and the property never updates.
This is my code:
Context
import React from 'react';
export const ButtonContext = React.createContext({
showPanel: false,
});
Button
import React, { Component } from 'react';
import { ButtonContext } from './ButtonContext';
class Button extends Component {
constructor() {
super();
this.state = {
showPanel: false,
};
}
render() {
return (
<ButtonContext.Provider value={{ showPanel: this.state.showPanel }}>
<li>
<a
onClick={() => this.setState({ showPanel: !this.state.showPanel }, () => console.log('Changed'))}
>
<span>Button</span>
</a>
</li>
</ButtonContext.Provider>
);
}
}
export { Button };
Panel
import React, { Component } from 'react';
import { Panel, ListGroup, ListGroupItem } from 'react-bootstrap';
import { ButtonContext } from './ButtonContext';
class ButtonPanel extends Component {
static contextType = ButtonContext;
render() {
return (
<ButtonContext.Consumer>
{
({ showPanel }) => {
if (showPanel) {
return (
<Panel id="tasksPanel">
<Panel.Heading >Panel Heading</Panel.Heading>
<ListGroup>
<ListGroupItem>No Items.</ListGroupItem>
</ListGroup>
</Panel>
);
}
return null;
}
}
</ButtonContext.Consumer>
);
}
}
export { ButtonPanel };
I've also tried simply accessing the context in the ButtonPanel component like so:
render() {
const context = this.context;
return context.showPanel ?
(
<Panel id="tasksPanel">
<Panel.Heading >Tasks</Panel.Heading>
<ListGroup>
<ListGroupItem className="tasks-empty-state">No tasks available.</ListGroupItem>
</ListGroup>
</Panel>
)
:
null;
}
What am I doing wrong?
Thanks
From the React docs:
Accepts a value prop to be passed to consuming components that are descendants of this Provider.
So this means that <ButtonContext.Provider> has to wrap <ButtonContext.Consumer> or it has to be higher up in the component hierarchy.
So based on your use case, you could do:
// This app component is the div that wraps both Toolbar and Content. You can name it as you want
class App extends Component {
state = {
showPanel: false,
}
handleTogglePanel = () => this.setState(prevState => ({ togglePanel: !prevState.togglePanel }));
render() {
return (
<ButtonContext.Provider value={{ showPanel: this.state.showPanel, handleTogglePanel: this.handleTogglePanel }}>
<Toolbar>
<Button />
</Toolbar>
<Content>
<ButtonPanel />
</Content>
</ButtonContext.Provider>
);
}
}
class Button extends Component {
...
<ButtonContext.Consumer>
{({ handleTogglePanel }) => <a onClick={handleTogglePanel} />}
</ButtonContext.Consumer>
}
class ButtonPanel extends Component {
...
<ButtonContext.Consumer>
{({ showPanel }) => showPanel && <Panel>...</Panel>}
</ButtonContext.Consumer>
}
I have created a dialog that opens up on each row in a table, I can edit and send within that dialog info about a person. I close the dialog and need to refresh the page before I see it updated. What I need to do is update the parent component on dialog close.
I have put together a fluff free version of my parent component and how I am calling the data below -
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import { Link, withRouter } from 'react-router-dom';
import PeopleEditDialog from './PeopleEditDialog';
class EnhancedTable extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
openPeopleEditDialog: false
};
this.handlePeopleEditDialog = this.handlePeopleEditDialog.bind(this);
}
handlePeopleEditDialog() {
this.setState({
openPeopleEditDialog: !this.state.openPeopleEditDialog
});
render() {
const { openPeopleEditDialog } = this.state;
const { loader, people, peopleListError } = this.props;
return (
<React.Fragment>
<Toolbar>
<div className="actions">
<Tooltip title="Edit">
<IconButton aria-label="Edit" onClick={this.handlePeopleEditDialog}>
<Edit />
</IconButton>
</Tooltip>
<PeopleEditDialog
open={this.state.openPeopleEditDialog}
onClose={this.handlePeopleEditDialog}
selected={selectedDialog}
/>
</div>
</Toolbar>
<div className="flex-auto">
<div className="table-responsive-material">
<Table>
<TableBody>
//Rows of people data
{people}
</TableBody>
</Table>
</div>
</div>
</React.Fragment>
)
);
}
}
const mapStateToProps = ({ peopleList }) => {
const { loader, people, peopleListError, appliedFilters } = peopleList;
return { loader, people, peopleListError, appliedFilters};
}
export default withRouter(connect(mapStateToProps, {})(withStyles(styles, { withTheme: true })(EnhancedTable)));
peopleList does not update unless I refresh. I need to apply something to get the latest when I close the dialog:
<PeopleEditDialog
open={this.state.openPeopleEditDialog}
onClose={this.handlePeopleEditDialog}
selected={selectedDialog}
/>
So how do I call the latest from mapStateToProps when the component closes so I get a refreshed list?
I am creating a todo List application using reactJS. If I write two different logic in two separate files it works just fine but while combining those two files it gives an error.
RenderRemaining.js file:
import React from 'react';
import { connect } from 'react-redux';
import store from '../store/store';
import RenderRemainingData from './RenderRemainingData';
const RenderRemaining = (props) => {
return (
<div>
<h2>Tasks: </h2>
<hr />
{props.list.map((detail) => {
return <RenderRemainingData key={detail.id} {...detail} />
})}
</div>
);
}
const mapStateToProps = (state) => {
return {
list: state.todoReducer
};
}
export default connect(mapStateToProps)(RenderRemaining);
RenderRemainingData.js file:
import React from 'react';
import { connect } from 'react-redux';
import removeTodo from '../actions/removeTodo';
const RenderRemainingData = ({ dispatch, todo, id, description, isCompleted }) => {
if (!isCompleted) {
return (
<div key={id}>
<h4>{todo}
<span className="float-right">
<a href="#" title="Remove" onClick={() => {
dispatch(removeTodo({todo, description, id}));
}}><i className="fas fa-times"></i></a>
</span>
</h4>
<p>{description}</p>
</div>
);
}
return false;
}
export default connect()(RenderRemainingData);
Now above code works just fine.
After combining above two files as one js file in RenderRemaining.js and deleting RenderRemainingData.js file.
RenderRemaining.js file: (after combining)
import React from 'react';
import { connect } from 'react-redux';
import store from '../store/store';
import removeTodo from '../actions/removeTodo';
const RenderRemainingData = ({ dispatch, todo, id, description, isCompleted }) => {
if (!isCompleted) {
return (
<div key={id}>
<h4>{todo}
<span className="float-right">
<a href="#" title="Remove" onClick={() => {
dispatch(removeTodo({todo, description, id}));
}}><i className="fas fa-times"></i></a>
</span>
</h4>
<p>{description}</p>
</div>
);
}
return false;
}
const RenderRemaining = (props) => {
return (
<div>
<h2>Tasks: </h2>
<hr />
{props.list.map((detail) => {
return <RenderRemainingData key={detail.id} {...detail} />
})}
</div>
);
}
const mapStateToProps = (state) => {
return {
list: state.todoReducer
};
}
connect()(RenderRemainingData);
export default connect(mapStateToProps)(RenderRemaining);
Now when an event of onClick occurs it gives an error as dispatch is not a function in console.
I don't know why is this happening.
This is because when you are rendering the RenderRemainingData component inside RenderRemaining you are not passing the dispatch, but in case of separate file, component will receive the dispatch from connect.
Possible Solutions:
1- Pass the dispatch in props to RenderRemainingData component:
return <RenderRemainingData key={detail.id} {...detail} dispatch={props.dispatch} />
And remove this line:
connect()(RenderRemainingData);
2- Another possible solution is:
Use a wrapper component and instead of rendering RenderRemainingData render that Wrapper component. Like this:
const WrapperRenderRemainingData = connect()(RenderRemainingData);
return <WrapperRenderRemainingData key={detail.id} {...detail} />
Calling connect()(SomeRandomComponent) means you are calling a function which will return you a value, a new Component that you can use.
So in the case of two separate files, first you create a new Component with connect()(RenderRemainingData), then you export the return value.
These two are equivalent.
export default connect()(SomeRandomComponent)
and
const newComponent = connect()(SomeRandomComponent)
export default newComponent
Now, if we look at bottom of your file containing the combined code.
connect()(RenderRemainingData);
export default connect(mapStateToProps)(RenderRemaining);
First expression, creates a newComponent by wrapping connect around RenderRemainingData. But since you didn't assign the return value to a new identifier or RenderRemainingData( you can't because latter is a const , by the way). Also, when you pass a function as a parameter, it is passed by value, so altering the parameter function inside the calling function will not affect its usage outside the calling function.
Easiest Solution for you will be the one mentioned below.
const RenderRemainingData = connect()(props => {
///Add the implementation here
})
There you go, you have a connected component in the same file, with dispatch available.