Reputation: 7648
I am pretty new to react and got really stuck on something. I am working on a sort of ordering application. People can order a product and can select all ingredients they want. I was thinking to do this with a checkbox for each ingredient. Unfort. I just don't know how to get this fixed. Also, I am wondering if I have to use a state in my component or just a variable.
So I am mapping through the array of ingredients and for each ingredient I am displaying a checkbox to turn on/off an ingredient. So my main question, How can I adjust my object with these checkboxes, and if I need to have a state in my component to keep up to date with the checkboxes, How will I set the product to my state? Because It's coming from props.
I've tried all sort of things, for instance this from the docs:
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
But then again, How can I add the product to my state? Also, this will be different since the ingredients are in a object, and the example from the docs are just values and not in a specific object.
My component
import React from 'react';
import { Link } from 'react-router-dom';
class Order extends React.Component {
constructor(props) {
super(props);
}
handleToggle(e) {
//Handle the toggle, set the value of the ingredient to 0 or 1
}
getData(e, product) {
e.preventDefault();
console.log(product)
}
render() {
const product = this.props.products.find((product) => {
return product.id == this.props.match.params.id;
});
return (
<form className="container mx-auto px-4 pt-6" onSubmit={(e) => this.getData(e, product) }>
<Link to={`/${this.props.match.params.category}`} className="mb-4 relative block text-brand hover:text-brand-dark">← Terug naar categorie</Link>
<div className="flex flex-row items-center justify-between bg-white rounded px-8 py-8 shadow-sm mb-4">
<div className="">
<h2 className="text-brand uppercase">{product && product.name}</h2>
<div className="ingre">
<p>
{product && product.ingredients.map((item) => {
return <span className="ing text-grey-dark text-sm" key={item.name}>{item.name}</span>
})}
</p>
</div>
</div>
<div className="">
<h3 className="text-brand text-4xl">€{product && product.price}</h3>
</div>
</div>
<div className="flex flex-wrap mb-4 -mx-2">
{product && product.ingredients.map((item) => {
return (
<div className="w-1/2 mb-4 px-2" key={item.name}>
<div className="flex flex-row items-center justify-between bg-white rounded px-8 py-8 shadow-sm">
<div>
<h3 className="text-grey-dark font-normal text-sm">{item.name}</h3>
</div>
<div>
<input type="checkbox" checked={item.value} name={item} onChange={(e) => this.handleToggle(e)}/>
</div>
</div>
</div>
);
})}
</div>
<button type="submit" className="bg-brand hover:bg-brand-dark text-white font-bold py-4 px-4 rounded">
Order this product
</button>
</form>
);
}
}
export default Order;
An example of a product
So actually I need to keep track of the product and bind the value's of the ingredients to the checkbox. If it's not checked the value must become 0 (or false).
Edit:
Parent component passing props
// React deps
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
// Custom components
import Navigation from './components/General/Navigation'
// Pages
import Index from './components/Index'
import Category from './components/Category'
import Order from './components/Order'
// Data
import products from './Data'
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: []
}
}
componentWillMount() {
setTimeout(() => {
this.setState({products});
}, 100);
}
render() {
return (
<main className="App font-sans">
<Router>
<div>
<Navigation logo="Jackies" />
<Switch>
<Route exact path="/" component={Index} />
<Route exact path="/:category" render={(props) => <Category {...props} products={this.state.products} />}/>
<Route exact path="/:category/:id" render={(props) => <Order {...props} products={this.state.products} />}/>
</Switch>
</div>
</Router>
</main>
);
}
}
export default App;
Upvotes: 0
Views: 4706
Reputation: 135772
In the parent, you will pass a handler function in a onIngredientToggle
prop:
<Route exact path="/:category/:id" render={(props) => <Order {...props} products={this.state.products} onIngredientToggle={this.handleIngredientToggle} />}/>
Then define the handleIngredientToggle
function:
function handleIngredientToggle(productId, ingredientIndex, newIngredientValue) {
// basically this goes shallow cloning the objects and arrays up to
// the point it changes the ingredient value property
let products = [...this.state.products];
let modifiedProductIndex = products.findIndex(p => p.id === productId);
let product = {...products[modifiedProductIndex]};
products[modifiedProductIndex] = product;
product.ingredients = [...products[modifiedProductIndex].ingredients];
product.ingredients[ingredientIndex] = {...product.ingredients[ingredientIndex], value: newIngredientValue};
this.setState({products});
}
// If you prefer, the above function can be replaced with:
function handleIngredientToggle(productId, ingredientIndex, newIngredientValue) {
// deep clone products
let products = JSON.parse(JSON.stringify(this.state.products));
// modify just what changed
products.find(p => p.id === productId).ingredients[ingredientIndex].value = newIngredientValue;
this.setState({products});
}
In the Order
child, you will add the index
argument to the map
(you have two of these, just add to the second):
{product && product.ingredients.map((item, index) => {
In the checkbox pass the product
and index
to the handleToggle
function as argument:
<input type="checkbox" checked={item.value} name={item} onChange={(e) => this.handleToggle(e, product, index)}/>
And then in the function implementation, you will call the function received as prop from the parent:
handleToggle(e, product, index) {
this.props.onIngredientToggle(product.id, index, e.target.checked);
}
Upvotes: 2
Reputation: 7648
Thanks to @acdcjunior who opened my (tired) eyes, I've found a solution
Checkbox html
<input type="checkbox" checked={item.value} name={item} onChange={(e) => this.handleToggle(e, item, product)}/>
Function on the child component
handleToggle(e, item, product) {
//Get value from checkbox and set it the opposite
let value = e.target.checked ? 1 : 0;
//Pass down the item (ingredient) and parent product and also the value
this.props.toggleIngredient(item, product, value);
}
Function on parent component to change the state
toggleIngredient(i, p, v) {
// Get the product from the state
var product = this.state.products.find((product) => {
return product.id == p.id
});
// Filter down the state array to remove the product and get new products array
let products = this.state.products.filter((product) => {
return product != product;
});
// Get the ingredient object
var object = product.ingredients.find((product) => {
return product == i;
});
// Set the value for the ingredient, either true or false (depends on checkbox state)
object.value = v;
// Push the edited product the array of products
products.push(product);
// Set the state with the new products array
this.setState({
products: products
});
}
Upvotes: 0