0

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">&larr; 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">&euro;{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

enter image description here

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;

2 Answers 2

2

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);
}
Sign up to request clarification or add additional context in comments.

8 Comments

Hi, Tried that but that will not uncheck the checkbox. So yes, the value become 0, but stays zero because the checkbox is checked. Is there a way to re-render it? based on the changes? I think the checkbox is not changing because I just adjust the object and not the prop or have a state..
Ah, I see. Can you show how the parent passes the project props to the component?
There you go, the state is at the parent, we have to change it. Can you show a bit more of the parent's code? The JavaScript part, where it initializes the state.
Alright. I think you opened my eyes but I am curious what you think. Normally I will do an ajax call instead of that setTimeout to fake it with data.js
Hmm... I think what you posted and my updated asnwer are logically the same thing. Your toggleIngredient is almost the same the two handleIngredientToggle possible implementations of mine. I see a problem, though. This part let products = this.state.products.filter((product) => { return product != product; });, I think it is clearing the array. Because you used the same name for the local argument and the outer product, so it will remove everything because nothing is different from itself (in other words product != product is always false).
|
0

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
    });

  }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.