My code was a bit messy before in terms of having a method for each kind of food and using each of those methods to display which type of food the user selected upon clicking which ever </FoodButton> the user desires.
Now, I want to optimize it in a way where it's only one method that handles it all.
There error I'm getting says:
'item' is not defined
(referring to <FoodButton clicked={() => this.selectItem(item)} label={"Chicken Taco"}/>)
I've been trying so many different ways of fixing this but can't seem to find a solution.
How come what I'm doing isn't working?
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Modal from 'react-modal';
import Aux from '../../../../hoc/Aux';
import FoodButton from '../FoodButtons/FoodButton';
import CheckoutButton from '../CheckoutButton/CheckoutButton';
import axios from '../../../../axios-foodChosen';
import { CLOSE_MODAL, OPEN_MODAL } from "../../../../store/action/NoNameAction";
class TacoTypes extends Component {
state = {
selectedItem: ''
};
constructor(props) {
super(props);
this.items = {
chickenTaco: 'Chicken Taco',
beefTaco: 'Beef Taco',
chickenBurrito: 'Chicken Burrito'
};
}
componentWillMount() {
// for modal
Modal.setAppElement('body');
}
selectItem(item) {
this.setState({selectedItem: item})
}
render() {
return (
<Aux>
<FoodButton clicked={() => this.selectItem(item)} label={"Chicken Taco"}/>
<FoodButton clicked={() => this.beefTaco()} label={"Beef Taco"}/>
<FoodButton clicked={() => this.chickenBurrito()} label={"Chicken Burrito"}/>
<CheckoutButton clicked={() => this.props.openModalRedux()}/>
<Modal isOpen={this.props.isOpen}>
<p>
{this.items.map(item => (
<p key={item}>{item}</p>
))}
</p>
<button onClick={() => this.props.closeModalRedux()}>Close</button>
</Modal>
</Aux>
);
}
}
const mapStateToProps = state => {
return {
// props for modal
isOpen: state.global.isModalOpen,
}
};
const mapDispatchToProps = dispatch => {
return {
// Modal handlers
openModalRedux: () => dispatch({type: OPEN_MODAL}),
closeModalRedux: () => dispatch({type: CLOSE_MODAL})
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TacoTypes);
Removing the Redux fluff for the time being, this should work:
import React, { Component } from "react";
import Modal from "react-modal";
import FoodButton from "../FoodButtons/FoodButton";
import CheckoutButton from "../CheckoutButton/CheckoutButton";
class TacoTypes extends Component {
constructor(props) {
super(props);
this.state = {
selectedItem: undefined,
modalIsOpen: false,
};
this.items = {
chickenTaco: "Chicken Taco",
beefTaco: "Beef Taco",
chickenBurrito: "Chicken Burrito",
};
}
componentWillMount() {
Modal.setAppElement("body");
}
selectItem(item) {
this.setState({ selectedItem: item });
}
render() {
return (
<Aux>
{Object.entries(this.items).map(([item, label]) => (
<FoodButton
clicked={() => this.selectItem.call(this, item)}
label={label}
key={item}
/>
))}
<CheckoutButton clicked={() => this.setState({ modalIsOpen: true })} />
<Modal isOpen={this.state.modalIsOpen}>
<p>
{Object.entries(this.items).map(([item, label]) => (
<p key={item}>
{item}
{this.state.selectedItem === item
? " (You chose this one!)"
: null}
</p>
))}
</p>
<button onClick={() => this.setState({ modalIsOpen: false })}>
Close
</button>
</Modal>
</Aux>
);
}
}
(I'm using this.selectItem.call(this, ...)) because I'm not sure if your environment is set up for functions as class properties. If it is, you can do
selectItem = (item) => {
this.setState({ selectedItem: item });
}
// ...
clicked={() => this.selectItem(item)}
instead.
)
If you want to pass item you need to specify it as a function parameter:
<FoodButton clicked={item => this.selectItem(item)} label={"Chicken Taco"}/>
As the error message indicates, the variable item is not defined. This should work:
<FoodButton clicked={() => this.selectItem('Chicken Taco')} label={"Chicken Taco"}/>
Or
<FoodButton clicked={() => this.selectItem(this.items.chickenTaco)} label={"Chicken Taco"}/>
UPDATE:
I want to display in the modal only the buttons that user clicked on
There are multiple ways to do that depending on how you arrange your data. I'll show one option here as a proof of concept:
There are three buttons. Click on either one of them will add corresponding item to the selected list (or remove from the list if it is already selected). You might do something similar in your specific setup.
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
items: {
chicken: false,
beef: false,
other: false
}
}
}
onSelect (name) {
this.setState((prevState) => {
return {
items: {
...prevState.items,
[name]: !prevState.items[name]
}
}
})
}
getSelected () {
return Object.entries(this.state.items).filter(entry => entry[1]).map(entry => entry[0]).join(',')
}
render() {
return (
<div>
{Object.keys(this.state.items).map(name => (
<button onClick={() => this.onSelect(name)} key={name}>{name}</button>
))}
<div>
Selected: {this.getSelected()}
</div>
</div>
)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Related
I have a simple todo app in which i add my "todos" and if they are done i just simply click done. Although after clicking the state is updated and the payload is being printed to the console with proper actions "TODO_DONE", done field still remains false.
my case for "TODO_DONE" in Reducer:
case "TODO_DONE":
return state.map((todo) => {
if (todo.id === action.payload) {
return {
...todo,
done: true,
};
}
return todo;
});
i use it here:
<button onClick={() => doneTodo(todo)}>Done</button>
in the TodoList component:
import { deleteTodoAction, doneTodo } from "../actions/TodoActions";
import { connect } from "react-redux";
const TodoList = ({ todoss, deleteTodoAction, doneTodo }, props) => {
return (
<div>
<h3>Director list</h3>
{todoss.map((todo) => {
return (
<div>
<div> {todo.name} </div>
<button onClick={() => deleteTodoAction(todo)}>UsuĊ</button>
<button onClick={() => doneTodo(todo)}>Done</button>
</div>
);
})}
</div>
);
};
const mapStateToProps = (state) => {
return {
todoss: state.todoss,
};
};
const mapDispatchToProps = {
deleteTodoAction,
doneTodo,
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
Ofc, the "done" value is my initial value inside TodoForm with Formik:
<Formik
initialValues={{
id: uuidv4(),
name: "",
date: "",
done: false,
}}
onSubmit={(values) => handleSubmit(values)}
enableReinitialize={true}
>
Anyone knows why this doest not work?
Check your doneTodo action. Since you are passing todo object to it. It should be action.payload.id instead of action.payload.
todo.id === action.payload.id
after onclick event occurs in backpackList.js, fetch data in context.js and then through setState I want to update noneUserCart . After that i want to get data from context.js to backpackList.js to show web page. but the data is inital data []. How can I solve this problem?!
I think this is a Asynchronous problem, but I'm new react, so I don't know how to write code for this. or do I use async, await.
Help me please!
import React, { Component } from 'react';
const ProductContext = React.createContext();
const ProductConsumer = ProductContext.Consumer;
class ProductProvider extends Component {
constructor() {
super();
this.state = {
totalProducts: 0,
isLogin: false,
cartList: [],
isNavOpen: false,
isCartOpen: false,
noneUserCart: [],
};
}
noneUserAddCart = bagId => {
fetch('/data/getdata.json', {
method: 'GET',
})
.then(res => res.json())
.catch(err => console.log(err))
.then(data => {
this.setState(
{
noneUserCart: [...this.state.noneUserCart, data],
},
() => console.log(this.state.noneUserCart)
);
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
handleCart: this.handleCart,
getToken: this.getToken,
addNoneUserCart: this.addNoneUserCart,
hanldeCheckout: this.hanldeCheckout,
openNav: this.openNav,
showCart: this.showCart,
habdleCartLsit: this.habdleCartLsit,
deleteCart: this.deleteCart,
noneUserAddCart: this.noneUserAddCart,
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
export { ProductProvider, ProductConsumer };
import React, { Component } from 'react';
import { ProductConsumer } from '../../context';
export default class BackpackList extends Component {
render() {
const {
backpackdata,
backdescdata,
isdescOpen,
showDesc,
descClose,
rangenumone,
rangenumtwo,
} = this.props;
return (
<div>
{backdescdata.map((bag, inx) => {
return (
<>
{isdescOpen && bag.id > rangenumone && bag.id < rangenumtwo && (
<div className="listDescContainer" key={inx}>
<div className="listDescBox">
<ProductConsumer>
{value => (
<div
className="cartBtn"
onClick={() => {
const token = value.getToken();
if (token) {
value.handleCart(bag.id, token);
} else {
value.noneUserAddCart(bag.id);
console.log(value.noneUserCart);
// this part. value.noneUserCart is undefined
}
}}
>
add to cart.
</div>
)}
</ProductConsumer>
<span className="descClosebtn" onClick={descClose}>
X
</span>
</div>
</div>
</div>
)}
</>
);
})}
</div>
);
}
}
fetch is asynchronous, this.setState is yet called when console.log
<div
className="cartBtn"
onClick={() => {
const token = value.getToken();
if (token) {
value.handleCart(bag.id, token);
} else {
value.noneUserAddCart(bag.id);
console.log(value.noneUserCart);
// this part. value.noneUserCart is undefined
}
}}
>
add to cart.
{value.noneUserCart}
{/* when finished, result should show here */}
</div>
I am trying to bring popup based on clicking of the respective links. Here I could see onClick is being triggered along with the state change But no modal is appearing.
Can some one tell me what is it that I am doing wrong. I am using semantic-ui-react modal for the same puropse
Sandbox: https://codesandbox.io/s/semantic-ui-example-seg91?file=/Modal.js
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true });
return (
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false });
}}
items={value}
/>
);
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
</>
);
}
}
export default LoaderExampleText;
import * as React from "react";
import { Modal } from "semantic-ui-react";
class NestedTableViewer extends React.Component {
render() {
return (
<>
<Modal closeIcon={true} open={this.props.modalOpen}>
<Modal.Header>Modal</Modal.Header>
<Modal.Content>
<h1> {this.props.items}</h1>
</Modal.Content>
</Modal>
</>
);
}
}
export default NestedTableViewer;
Save the value into state and move the modal into the render return. All renderable JSX needs to be returned as a single node tree.
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
value: null
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true, value });
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false, value: null });
}}
items={this.state.value}
/>
</>
);
}
}
export default LoaderExampleText;
In setModal is a function , so you cannot do some think like that, but if you want return you must add in render {setModal}.
So in this case :
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
value: ""
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true });
this.setState({ value: value });
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false });
}}
items={this.state.value}
/>
</>
);
}
}
export default LoaderExampleText;
it will work;
I'm having troubles updating the header class so it updates it's className whenever displaySection() is called. I know that the parent state changes, because the console log done in displaySection() registers the this.state.headerVisible changes but nothing in my children component changes, i don't know what I'm missing, I've been trying different solutions for some hours and I just can't figure it out what i'm doing wrong, the header headerVisible value stays as TRUE instead of changing when the state changes.
I don't get any error code in the console, it's just that the prop headerVisible from the children Header doesn't get updated on it's parent state changes.
Thank you!
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected }, () => {
this.sectionRef.current.changeSection(this.state.section)
})
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<Layout>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</Layout>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { this.props.selectSection("projects")}}>
{" "}
Projects
</span>
</header>
)
There seemed to be a couple of issues with your example code:
Missing closing div in Header
Using this.props instead of props in onclick in span in Header
The below minimal example seems to work. I had to remove your call to this.sectionRef.current.changeSection(this.state.section) as I didn't know what sectionRef was supposed to be because it's not in your example.
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected })
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<div>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</div>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { props.selectSection("projects")}}>
{" "}
Projects
</span>
</div>
</header>
)
ReactDOM.render(
<IndexPage />,
document.getElementsByTagName('body')[0]
);
.visible {
opacity: 1
}
.invisible {
opacity: 0
}
<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>
There is a markup error in your code in Header component - div tag is not closed.
Also, I suppose, you remove some code to make example easy, and there is artifact of this.sectionRef.current.changeSection(this.state.section) cause this.sectionRef is not defined.
As #Felix Kling said, when you change the state of the component depending on the previous state use function prevState => ({key: !prevState.key})
Any way here is a working example of what you trying to achieve:
// #flow
import * as React from "react";
import Header from "./Header";
type
Properties = {};
type
State = {
section: string,
headerVisible: boolean,
};
class IndexPage extends React.Component<Properties, State> {
static defaultProps = {};
state = {};
constructor(props) {
super(props);
this.state = {
section: "",
headerVisible: true,
};
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
setTimeout(
() => this.setState(
prevState => ({
section: sectionSelected,
headerVisible: !prevState.headerVisible
}),
() => console.log("Debug log: \n", this.state)
),
325
);
}
render(): React.Node {
const {section, headerVisible} = this.state;
return (
<React.Fragment>
<Header selectSection={this.displaySection} headerVisible={headerVisible} />
<br/>
<div>{`IndexPage state: headerVisible - ${headerVisible} / section - ${section}`}</div>
</React.Fragment>
)
}
}
export default IndexPage;
and Header component
// #flow
import * as React from "react";
type Properties = {
headerVisible: boolean,
selectSection: (section: string) => void
};
const ComponentName = ({headerVisible, selectSection}: Properties): React.Node => {
const headerRef = React.useRef(null);
return (
<React.Fragment>
<header ref={headerRef} className={headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => selectSection("projects")}>Projects</span>
</div>
</header>
<br/>
<div>Header class name: {headerRef.current && headerRef.current.className}</div>
</React.Fragment>
);
};
export default ComponentName;
I was trying to create a react component. If a user clicks on the edit button on a to-do item, it should replace the specific clicked todo area with a text field.
What do I expect to see
When a user clicks on one of the to-do items, that item becomes into a text field, which can then be edited.
What do I actually see
When a user clicks on one of the to-do items, all items become into text fields.
Here is my code:
PARENT
handleEditClick = (e,id,text) => {
this.setState({val: !this.state.val})
}
render() {
return (
<div style={{display: "flex"}}>
<div>
{this.props.todos.map(todo => (
<div key={todo.id}>
<EditButton todo={todo} val={this.state.val} text={this.state.text}
handleEditClick={(e, id, text) => this.handleEditClick(e, id, text)}/>
</div>
))}
</div>
</div>
)
}
}
CHILD(EDITBUTTON):
const EditButton = (props) => {
if(!props.val) {
return(
<div>
<List>
<ListItem
role={undefined}
dense
button
onClick={() => this.updateTodo(props.todo)}
><Checkbox
checked={props.todo.complete}
/>
<ListItemText primary={props.todo.text} />
<ListItemSecondaryAction>
<Button mini color="secondary" variant="fab"
disabled={props.todo.complete}
onClick={(e) => props.handleEditClick(e, props.todo.id, props.todo.text)}
>
<Icon>edit_icon</Icon>
</Button>
<IconButton onClick={() => this.removeTodo(props.todo)}>
<CloseIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
</List>
</div>
)
}else {
return(
<TextField
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
label=""
margin="normal"
fullWidth
value={props.text}
color="secondary"
/>
)
}
}
This example is not complete, but it does show how to implement what you descript. The program with your code was when you update the status of edit, it is updating every single item's status.
https://codesandbox.io/s/x7rvqm6xz4
import React from "react";
import { Button, TextField } from "#material-ui/core";
class Todos extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: null
};
}
componentDidMount() {
this.setState({
todos: this.props.todos.map(todo => ({ name: todo, isEdit: false }))
});
}
handleEdit(name) {
const { todos } = this.state;
const updateTodos = todos.map(todo => {
if (todo.name === name) {
return { name: todo.name, isEdit: !todo.isEdit };
}
return todo;
});
this.setState({ todos: updateTodos });
}
_renderTodos() {
if (!this.state.todos) {
return null;
}
return this.state.todos.map(todo => {
return (
<div key={todo.name}>
{todo.isEdit ? <TextField /> : <span>{todo.name}</span>}
<Button onClick={() => this.handleEdit(todo.name)}>Click</Button>
</div>
);
});
}
render() {
if (!this.props.todos.length > 0) {
return null;
}
return <div>{this._renderTodos()}</div>;
}
}
export default Todos;
Basically, this component takes a list of todos. Put them into a state, and this state tracks whether to display them as todos or TextField.
I've done this before. See in CODESANDBOX.
There is TodoList class that can accept todos and saveEditedTodo as props. It has editedTodo state which dinamically change when "Edit" button is clicked. Of course, it contains id and text.
import React from "react";
export default class extends React.Component {
state = {
editedTodo: null
};
toggleEditTodo = (todo = null) => {
this.setState({ editedTodo: todo });
};
onChangeTodoText = text => {
this.setState(prevState => ({
editedTodo: Object.assign(prevState.editedTodo, { text })
}));
};
submitTodoForm = e => {
e.preventDefault();
this.props.saveEditedTodo(this.state.editedTodo);
this.setState({
editedTodo: null
});
};
render() {
const { editedTodo } = this.state;
const { todos } = this.props;
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo === editedTodo ? (
// Show text field
) : (
// Show todo
)}
</li>
))}
</ul>
);
}
}