Search code examples
javascriptreactjsjsxreact-propsreact-usememo

Passing a value up in react without writing state in parent


What I am trying to do: I need price * quantity = total. I have a table with price as one , quantity as one and total as one . Quantity is a counter and needs to have its own state as rows will be added with more counters. But I need that value in the parent table so I can use it to calculate total.

Normally I would put the counter value in the parent, but if I do that, when I change one counter they all change. So how else can I pass the value back up to the parent? Or what other option do I have here.

//parent
const memoData = useMemo(() => {
    return productList.map((product) => (
      <tr key={product.productNumber} className='productTable__row'>
        <td>
          <input
            placeholder='Product name'
            className='productTable__input productTable__input_product'
            defaultValue={product.productName}
          />
        </td>

        <td>
          <input
            placeholder='Id'
            className='productTable__input productTable__input_id'
            disabled
            defaultValue={product.productNumber}
          />
        </td>
        <td>
          <input
            placeholder='Price'
            className='productTable__input productTable__input_price'
            defaultValue={product.price}
          />
        </td>
        <td>
          <Input prouct={product} />
        </td>
        <td>
          <input
            placeholder='Total'
            className='productTable__input productTable__input_price'
            defaultValue={? * product.price}
          />
        </td>
        <td>
          <button
            className='productTable__btn_close'
            onClick={() => onProductDelete(product)}
          >
            <RiCloseFill fill='red' size='30px' />
          </button>
        </td>
      </tr>
    ));
  }, [onProductDelete, currentProduct, productList]);

//child
function Input() {
  const [counter, setCounter] = useState(1);

  const addOneClick = () => {
    setCounter(counter + 1);
  };

  const minusOneClick = () => {
    if (counter > 0) {
      setCounter(counter - 1);
    }
  };

  return (
    <div style={{ width: '88px' }}>
      <button
        className='productTable__btn_minus'
        style={{ borderRight: 'none' }}
        onClick={minusOneClick}
      >
        <FaMinus fill='var(--color-primary-blue)' size='14px' />
      </button>
      <input
        placeholder='0'
        className='productTable__input productTable__input_quantity'
        //   defaultValue={0}
        value={counter}
      />

      <button
        className='productTable__btn_plus'
        style={{ borderLeft: 'none' }}
        onClick={addOneClick}
      >
        <FaPlus fill='var(--color-primary-blue)' size='14px' />
      </button>
    </div>
  );
}

Visual ref: enter image description here


Solution

  • You should consider productList as "the state" in the parent, instead of counter, and you should have counter as a property of product:

    // productList
    [
        {
            id: 123,
            counter: 0,           // <-- add a counter property
            productNumber: '...',
            // ...
        },
    ]
    
    // parent
    return productList.map( product => (
        <tr key={ product.productNumber } >
            <td>
                {/* add onSetCounter() callback */}
                <Input
                    product={ product }
                    onSetCounter={ id => { setCounter( id, count ); } }
                />
            </td>
        </tr>
    ));
    
    // child
    function Input() {
        const counter = props.product.counter;         // <-- use props
        const setCounter = props.setCounter;           // <-- use props
        // const [counter, setCounter] = useState(1);  // <-- don't use local state
    
        const addOneClick = () => {
            setCounter(counter + 1);
        };
    
        return (
            <button onClick={ addOneClick } >add one</button>
        );
    }
    
    // update the counter
    const setCounter = function( id, count ){
    
        // ... I don't know how you are managing your productList.
        // I would put this code into my redux-reducer. ...
    
        const newProductList = productList.reduce( (acc, product) => {
            if( product.id === id ){ // current product
                acc.push({
                    ...product,
                    counter: count
                })
            } else {                 // other products
                acc.push( product );
            };
        }, []);
    
    };
    

    (I wrote props.product here, despite you are passing <Input prouct={...} />, I assume that was a typing mistake)