Giesburts
Giesburts

Reputation: 7648

Change value of object property with checkbox React

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;

Upvotes: 0

Views: 4706

Answers (2)

acdcjunior
acdcjunior

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

Giesburts
Giesburts

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

Related Questions