1

Here's my current code : (2 Files & Classes : "FoodStandComponent.jsx")

/* ************************************* */
/* ********       IMPORTS       ******** */
/* ************************************* */
import uuid from 'uuid/v4';
import { Card, CardBlock, Button, InputGroup, Input } from 'reactstrap';
import React, { Component } from 'react';
import ProviderInfos from '../ProviderInfos/ProviderInfos';
import InputType from './InputType/InputType';

/* ************************************* */
/* ********      VARIABLES      ******** */
/* ************************************* */

const propTypes = {
    newInput: React.PropTypes.array,
    exportInput: React.PropTypes.func,
};

/* ************************************* */
/* ********      COMPONENT      ******** */
/* ************************************* */
class FoodStandComponent extends Component {

    constructor(props) {
        super(props);
        this.state = {
            newInput: [
                { name: 'Pretzel', id: uuid() },
                { name: 'Curry', id: uuid() },
                { name: 'Wurst', id: uuid() },
            ],
            InValue: '',
        };
        this.add = this.add.bind(this);
        this.remove = this.remove.bind(this);
    }

    add = (name) => {
        const ninput = this.state.newInput.concat({ name, id: uuid(), value: this.state.InValue });
        this.setState({
            newInput: ninput,
            InValue: '',
        });
    };

    remove = (id, name) => {
        const toBeRemoved = this.state.newInput.filter(x => x.name === name).pop();
        if (toBeRemoved) {
            this.setState({
                newInput: this.state.newInput.filter(x => x.name !== name).concat(
                    this.state.newInput.filter(x => x.name === name && x.id !== toBeRemoved.id),
                ),
            });
        }
    };

    render() {
        console.log();
        const cubeFifteenOrUnder = this.state.newInput.filter(x => x.name === 'Pretzel')
            && this.state.newInput.filter(x => x.name === 'Pretzel').length <= 13;
        const dsoFifteenOrUnder = this.state.newInput.filter(x => x.name === 'Curry')
            && this.state.newInput.filter(x => x.name === 'Curry').length <= 13;
        const multiFifteenOrUnder = this.state.newInput.filter(name => name === 'Wurst')
            && this.state.newInput.filter(x => x.name === 'Wurst').length <= 13;

        return (
            <Card>
                <CardBlock className="main-table">
                    <fieldset>
                        <legend>Pretzels</legend>
                        <InputGroup>
                            <Input placeholder="Pretzel" />
                            <ProviderInfos type="Pretzel" />
                            { cubeFifteenOrUnder && (
                                <div className="plus" onClick={() => this.add('Pretzel')}>
                                    <i className="fa fa-plus" aria-hidden="true" />
                                </div>
                            ) }
                            { !cubeFifteenOrUnder && (
                                <div className="plus-off">
                                    <i className="fa fa-plus" aria-hidden="true" />
                                </div>
                            ) }
                        </InputGroup>
                        {this.state.newInput.map((mapStorageVariable) => {
                            if (mapStorageVariable.name === 'Pretzel') {
                                return (<InputType
                                    id={mapStorageVariable.id}
                                    placeholder={mapStorageVariable.name}
                                    value={mapStorageVariable.value}
                                    onRemove={() => this.remove(mapStorageVariable.id, mapStorageVariable.name)}
                                />);
                            }
                            return null;
                        })}
                    </fieldset>
                    <fieldset>
                        <legend>Curries</legend>

                        <InputGroup>
                            <Input placeholder="Curry" />
                            <ProviderInfos type="Curry" />
                            { dsoFifteenOrUnder && (
                                <div className="plus" onClick={() => this.add('Curry')}>
                                    <i className="fa fa-plus" aria-hidden="true" />
                                </div>
                            ) }
                            { !dsoFifteenOrUnder && (
                                <div className="plus-off">
                                    <i className="fa fa-plus" aria-hidden="true" />
                                </div>
                            ) }
                        </InputGroup>
                        {this.state.newInput.map((mapStorageVariable) => {
                            if (mapStorageVariable.name === 'Curry') {
                                return (<InputType
                                    id={mapStorageVariable.id}
                                    placeholder={mapStorageVariable.name}
                                    value={mapStorageVariable.value}
                                    onRemove={() => this.remove(mapStorageVariable.id, mapStorageVariable.name)}
                                />);
                            }
                            return null;
                        })}
                    </fieldset>
                    <fieldset>
                        <legend>Wursts</legend>
                        <InputGroup>
                            <Input placeholder="Wurst" />
                            <ProviderInfos type="Wurst" />
                            { multiFifteenOrUnder && (
                                <div className="plus" onClick={() => this.add('Wurst')}>
                                    <i className="fa fa-plus" aria-hidden="true" />
                                </div>
                            ) }
                            { !multiFifteenOrUnder && (
                                <div className="plus-off">
                                    <i className="fa fa-plus" aria-hidden="true" />
                                </div>
                            ) }
                        </InputGroup>
                        {this.state.newInput.map((mapStorageVariable) => {
                            if (mapStorageVariable.name === 'Wurst') {
                                return (<InputType
                                    id={mapStorageVariable.id}
                                    placeholder={mapStorageVariable.name}
                                    value={mapStorageVariable.value}
                                    onRemove={() => this.remove(mapStorageVariable.id, mapStorageVariable.name)}
                                />);
                            }
                            return null;
                        })}
                    </fieldset>
                    <Button color="secondary">Options</Button>{' '}
                    <Button id="btn">Exécuter</Button>
                </CardBlock>
            </Card>
        );
    }
}
SearchExtendedComponent.propTypes = propTypes;

export default SearchExtendedComponent;

(and InputTypeComponent.jsx )

/* ************************************* */
/* ********       IMPORTS       ******** */
/* ************************************* */
import ProviderInfos from '../../ProviderInfos/ProviderInfos';
import React, { Component } from 'react';
import { Card, CardBlock, Button, InputGroup, Input } from 'reactstrap';
/* ************************************* */
/* ********      VARIABLES      ******** */
/* ************************************* */

/* ************************************* */
/* ********      COMPONENT      ******** */
/* ************************************* */

export default class InputTypeComponent extends Component {

    constructor(props) {
        super(props);
        this.state = {
            type: '',
        };
    }

    onRemove = () => {
        this.props.onRemove(this.props.id);
    }

    onChange = () => {
        this.props.onChange(this.props.id);
    }

    render() {
        const { placeholder, id, value } = this.props;
        const { type } = this.state;
        this.type = placeholder;
        return (
            <InputGroup key={id}>
                <Input placeholder={placeholder} />{value}
                <ProviderInfos type={this.type} />
                <div className="minus" onClick={this.onRemove}>
                    <i className="fa fa-minus" aria-hidden="true" />
                </div>
            </InputGroup>
        );
    }
}

I'm trying to get a list with "add" and "remove" buttons refactored into one function.

as you can see above thanks to @Jacky Choo's answer & code I'm almost there but the issue is that I've lost the functionality I previously had of having the line I want deleted removed when I click on it's own remove button. this is what my app looks like

When I click on this minus sign the line with the text and changed checkboxes stays. and the very last line dissapears.

UPDATE :

Fixed it! By changing the remove to this I get my intended result. yes the lines below the deleted one are reset but that is for Redux to handle. A big shoutout to @Jacky Choo who basically figured it out for me!

remove = (id, name) => {
        this.setState({
            newInput: this.state.newInput.filter(x => x.name === name && x.id !== id),
        });
    };
tatsu
  • 2,316
  • 7
  • 43
  • 87
  • I think you're doing it wrong if you're placing HTML into your state to begin with. State should ideally just be that. That HTML is rendered based on data received from the state, – dcodesmith Aug 10 '17 at 08:39
  • I know that's why i'm trying to fix it.... help me! – tatsu Aug 10 '17 at 08:40
  • Well you could return different functions that return html based on a handlerVariable – Nocebo Aug 10 '17 at 08:42
  • @tatsu does any of the solution below works? – Jacky Choo Aug 13 '17 at 02:41
  • @Jacky would you mind joining me in a chat : https://chat.stackoverflow.com/rooms/151869/refactor-code-to-not-have-setstate-html I've already implemented this answer since them : https://stackoverflow.com/questions/45585895/delete-remove-specific-collection-item and merging your answer has become complicated. – tatsu Aug 14 '17 at 08:54
  • @tatsu I joined the chat, you can just drop the questions there – Jacky Choo Aug 15 '17 at 10:41
  • thanks you it's solved now. :) – tatsu Aug 16 '17 at 09:05

3 Answers3

1

You could do smth like this:

export default class PretzelStandComponent extends Component {

    constructor(props) {
        super(props);
        this.state = { 
           handler: 1
            };
         ...
    }

    handleHTML = () => {
      switch(this.state.handler){
         case 1:
            return this.returnHTML();
      }
    }

    //Set handlerVariable in your functions instead of setting html

    //Return html
    returnHTML = () => {
      return (<div/>);
    }

    render(){
      return(<div>{this.handleHTML()}</div>); 
    }
Nocebo
  • 1,927
  • 3
  • 15
  • 26
1

The arguably best and easiest solution is to have an array responsible of storing for each of the ingredients, and then map through each of the arrays in the render.

What's more is that you can use just one function for incrementing or decrementing your arrays because all they do is just create a new uuid, but they return the same JSX more or less.

Because of this similarity you can use just these two functions and the only parameter is just the name of the ingredient to add/remove from.


Here's a working demo. I have replaced some of the components, such as <Input /> and <ProviderInfos /> with a <span> just for the demo.

I also replaced your uuid() with a fake key to get it working.

class PretzelStandComponent extends React.Component {

    constructor(props) {
        super(props);
        this.state = { 
            inputPretzel: [],
            inputCurry: [],
            inputWurst: []
            };
        this.increment = this.increment.bind(this);
        this.decrement = this.decrement.bind(this);
    }
    
    increment = (name) => {
      //const uuid = require('uuid/v1');
      //uuid();
      let uuid = this.state['input' + name].length+1;

      let n = this.state['input' + name].slice();
      n.push(uuid);
      this.setState({['input' + name]: n});          
    }

    decrement = (name) => {
      let n = this.state['input' + name];
      n.pop();
      this.setState({['input' + name]: n});
    }

    render() {
        return (
            <div>
                <div className="main-table">
                    <fieldset>
                        <legend>Pretzels</legend>
                        {this.state.inputPretzel.map(
                          key => {
                            return <span>{key}</span>;
                          })
                        }
                        <button onClick={this.increment.bind(this, "Pretzel")}>Add a Pretzel</button>
                        <button onClick={this.decrement.bind(this, "Pretzel")}>Remove a Pretzel</button>
                    </fieldset>
                    <fieldset>
                        <legend>Curry</legend>
                        {this.state.inputCurry.map(
                          key => {
                            return <span>{key}</span>;
                          })
                        }
                        <button onClick={this.increment.bind(this, "Curry")}>Add Curry</button>
                        <button onClick={this.decrement.bind(this, "Curry")}>Remove Curry</button>
                    </fieldset>
                    <fieldset>
                        <legend>Wurst</legend>
                        {this.state.inputWurst.map(
                          key => {
                            return <span>{key}</span>;
                          })
                        }
                        <button onClick={this.increment.bind(this, "Wurst")}>Add Wurst</button>
                        <button onClick={this.decrement.bind(this, "Wurst")}>Remove Wurst</button>
                    </fieldset>
                    <button color="secondary">Options</button>{' '}
                    <button id="btn">Exécuter</button>
                </div>
            </div>
        );
    }
}

ReactDOM.render(<PretzelStandComponent />, document.getElementById("app"));
<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="app"></div>
Chris
  • 57,622
  • 19
  • 111
  • 137
  • nah each line has their own remove button which should remove the correct line this solution doesn't account for that. thanks though. – tatsu Aug 14 '17 at 09:24
  • @tatsu, your code doesn't seem to have that and my solution is based on your code. Your question was *"I'm trying to get a single function called by all "increment" functions, that will be responsible for handling the html that goes into the collections"*, which I think I have provided here - there is a single function to add and remove elements respectively. – Chris Aug 14 '17 at 09:27
  • yeah I know should I update the question to reflect my current code? while this answer is technically correct given the question, ...I can't imagine a use for such a bit of code in a real-life context. – tatsu Aug 14 '17 at 09:53
  • @tatsu yes, that would be great. You can't? If I have a shop where you can add pretzels to a basket, I wouldn't care **which** pretzel is removed from my basket... Only that I get the right amount ordered. ;) – Chris Aug 14 '17 at 10:02
  • yes I realise that I'm sorry but this whole pretzel gig is a replacement for the actual variable names which are for a company app but the inputs hold text and it matters which bit of text you delete. – tatsu Aug 14 '17 at 10:16
  • @tatsu, alright. Go ahead and update your question though. – Chris Aug 14 '17 at 11:20
  • I've done that now. Do you have any ideas? I think I'm missing something obvious. the new code is much more clear as to what it is I'm doing. – tatsu Aug 14 '17 at 12:42
1

Tested working on my side (replaced some customized class to normal input box which is not provided)

Adding jsx into the state doesn't seem right, I've amended the code to store food as an array in the state, each of them is mapped to the component that renders the input field.

Hope it helps

import React, { Component } from 'react';

const FoodInput = ({ foodName, id }) => {
    return (
        <input placeholder={foodName} key={id} />
    );
}

export default class PretzelStandComponent extends Component {
    constructor(props) {
        super(props);
        const uuid = require('uuid/v1');
        this.state = {
            Foods: [
                {name: "Pretzel", id: uuid()},
                {name: "Curry", id: uuid()},
                {name: "Wurst", id: uuid()}
            ]
        }
    }

    componentDidMount() {
    }

    addFood(name) {
        const uuid = require('uuid/v1');
        this.setState({
            Foods: this.state.Foods.concat({ name, id: uuid() })
        });
    }

    removeFood(name) {
        var foodToBeRemoved = this.state.Foods.filter(x => x.name === name).pop()
        if (foodToBeRemoved){
            this.setState({
            Foods: this.state.Foods.filter(x => x.name !== name).concat(
                this.state.Foods.filter(x => x.name === name && x.id !== foodToBeRemoved.id)
            )
        });
        }    
    }

    render() {
        return (
            <div>
                    <fieldset>
                        <legend>Pretzels</legend>
                        {this.state.Foods.map(food => {
                            if (food.name === "Pretzel") {
                                return (<FoodInput foodName={food.name} key={food.id} {...food} />)
                            }
                            else
                            {
                                return null
                            }
                        })}
                        <button onClick={() => this.addFood("Pretzel")}>Add a Pretzel</button>
                        <button onClick={() => this.removeFood("Pretzel")}>Remove a Pretzel</button>
                    </fieldset>
                    <fieldset>
                        <legend>Curry</legend>
                        {this.state.Foods.map(food => {
                            if (food.name === "Curry") {
                                return (<FoodInput foodName={food.name} key={food.id} {...food} />)
                            }
                            else
                            {
                                return null
                            }
                        })}
                        <button onClick={() => this.addFood("Curry")}>Add a Curry</button>
                        <button onClick={() => this.removeFood("Curry")}>Remove a Curry</button>
                    </fieldset>
                    <fieldset>
                        <legend>Wurst</legend>
                        {this.state.Foods.map(food => {
                            if (food.name === "Wurst") {
                                return (<FoodInput foodName={food.name} key={food.id} {...food} />)
                            }
                            else
                            {
                                return null
                            }
                        })}
                        <button onClick={() => this.addFood("Wurst")}>Add a Wurst</button>
                        <button onClick={() => this.removeFood("Wurst")}>Remove a Wurst</button>
                    </fieldset>
                </div>
        );
    }
}
Jacky Choo
  • 144
  • 6
  • yeah perfect! thanks alot! this Solution worked for me! Sorry I added a spec from https://stackoverflow.com/questions/45585895/delete-remove-specific-collection-item in the meantime and thus edited my code but I was able to implement your answer regardless (in the end ^^' ) – tatsu Aug 14 '17 at 13:21
  • @tatsu, glad it worked for you, but I'm curious; how is this solution different from mine? – Chris Aug 14 '17 at 13:44
  • the one in my edited OP? it deletes the row from the clicked delete button instead of always removing the bottom one. But it is wierd indeed your delete function looks like it does the same thing except being a little more cautious about inderOutOfBounds and other. – tatsu Aug 14 '17 at 13:48
  • @tatsu, no not yours. The solution we're commenting on. It seems similar to mine and posted some 2hrs after, the only fundamental difference I can see is that I use separate states for each ingredient, whereas this solution uses one, but with `if`s to check the type in each loop. Regardless, my code won't throw any errors nor warnings. `pop()` returns `undefined` if the array is empty, and doing `map()` on an empty array also returns an empty array. – Chris Aug 14 '17 at 14:05
  • ok but what's this syntax `state['input' + name]`? also I tried `pop()` and also `slice()` but only `filter()` gave me a result where it wasn't the last item that was deleted but the one with the corresponding delete button. – tatsu Aug 14 '17 at 14:14