简体   繁体   中英

React hooks update state adding object to array

I'm trying to add a new object to my array when I click on a button to add a new row to my form.

import React, { useState, useEffect } from 'react';
import NumberFormat from 'react-number-format';
import { Button, Form, Label, Input } from 'reactstrap';

import RctCollapsibleCard from './RctCollapsibleCard/RctCollapsibleCard';
import LinearProgress from '../util/LinearProgress';
import * as tenantAPI from '../api/tenants';

const Test = (props) => {
    const tenantName = props.tenantName;
    const tenantTransactionID = props.selectedTransaction;
    const propertyID = props.propertyID;

    const [ loading, setLoading ] = useState(true);
    const [ updated, setUpdated ] = useState(false);
    const [ payments, setPayments ] = useState([]);
    const [ categories, setCategories ] = useState([]);
    const [ paymentAmount, setPaymentAmount ] = useState(0);
    const [ currentTotal, setCurrentTotal ] = useState(0);
    const [ showSave, setShowSave ] = useState(false);

    useEffect(() => {
        async function fetchData() {
                console.log('load');
            setLoading(true);
            setPaymentAmount(parseFloat(await tenantAPI.getTransactionAmount(tenantTransactionID)));
            //const allocated = await tenantAPI.getAllocatedPayments(tenantTransactionID);
            //setPayments(allocated);
            setCategories(await tenantAPI.getPaymentCategories(propertyID));
            //let total = 0;
            //for(const a of allocated) {
                //total += parseFloat(a.PaymentAmount);
            //}
            //setCurrentTotal(total);
            setLoading(false);
        }
        fetchData();
    }, [tenantTransactionID, propertyID])

    useEffect(() => {
            console.log(payments);
        if(parseFloat(paymentAmount).toFixed(2) === parseFloat(currentTotal).toFixed(2)) {
            setShowSave(true);
        } else {
            setShowSave(false);
        }
    }, [updated]);

    const handleCategoryChange = (val, index) => {
    }

    const handleAmountChange = (val, index) => {
    }

    const handleDelete = (index) => {
    }

    const addPayment = () => {
        setPayments([
            ...payments,
            {
                PaymentAmount: 0,
                CategoryID: 0
            }
        ]);
        console.log(payments);
        setUpdated(!updated);
    }

    const save = () => {
        alert('Save BTN');
    }

    const render = () => {
        if(loading) {
            return (
                <RctCollapsibleCard
                colClasses="col-xs-12 col-sm-12 col-md-12"
                heading={"Loading Allocate Payment..."}
                >
                    <LinearProgress />
                </RctCollapsibleCard>
            );
        } else {
            const showSaveBTN = () => {
                if(showSave) {
                    return (
                        <>
                        <Button type="button" color="primary" onClick={save}>Save</Button>
                        {' '}
                        <Button color="warning" onClick={() => props.setOpenAllocate(false)}>Cancel</Button>
                        </>
                    );
                } else {
                    return <Button color="warning" onClick={() => props.setOpenAllocate(false)}>Cancel</Button>;
                }
            }
            const heading = `Allocating ${tenantName} payment of ${<NumberFormat displayType={'text'} value={paymentAmount} thousandSeparator={true} prefix={'$'} />}`;
            return (
                <>
                    <div className="row">
                        <div className="col-sm-12 col-md-12 col-xl-12">
                            <RctCollapsibleCard heading={heading}>
                                <Form>
                                    {payments.map((element, index) => {
                                            //console.log(element);
                                            //console.log(index);
                                        return (
                                            <div className="row">
                                                <div className="col-sm-4">
                                                    <Label className="mr-sm-10">Payment Category</Label>
                                                    <Input type="select" 
                                                        value={element.CategoryID} onChange={(e) => handleCategoryChange(e.target.value, index)}
                                                    >
                                                        <option value="0">Select</option>
                                                        {categories.map((obj) => {
                                                            return (
                                                                <option 
                                                                    key={obj.PaymentsCategoryID} 
                                                                    value={obj.PaymentsCategoryID}
                                                                >
                                                                    {obj.Category}
                                                                </option>
                                                            );
                                                        })}
                                                    </Input>
                                                </div>
                                                <div className="col-sm-4">
                                                    <Label className="mr-sm-10">Amount</Label>
                                                    <NumberFormat value={parseFloat(element.PaymentAmount).toFixed(2)} thousandSeparator={true} prefix={'$'} 
                                                        onChange={(e) => handleAmountChange(e.value, index)} className="form-group"
                                                    />
                                                </div>
                                                <div className="col-sm-3">
                                                    <Button type="button" color="danger" size="sm" className="w-auto" style={{marginTop: '10px'}}
                                                        onClick={handleDelete(index)}
                                                    >
                                                        Delete
                                                    </Button>
                                                </div>
                                            </div>
                                        );
                                    })}
                                    <div className="row">
                                        <div className="col-sm-4">
                                            <Label className="mr-sm-10">Payment Category</Label>
                                            <Input type="select" onChange={(e) => handleCategoryChange(e.target.value, -1)}>
                                                <option value="0">Select</option>
                                                {categories.map((obj) => {
                                                    return (
                                                        <option 
                                                            key={obj.PaymentsCategoryID} 
                                                            value={obj.PaymentsCategoryID}
                                                        >
                                                            {obj.Category}
                                                        </option>
                                                    );
                                                })}
                                            </Input>
                                        </div>
                                        <div className="col-sm-4">
                                            <Label className="mr-sm-10">Amount</Label>
                                            <NumberFormat thousandSeparator={true} prefix={'$'} className="forms-group"
                                                onChange={(e) => handleAmountChange(e.value, -1)}
                                            />
                                        </div>
                                    </div>
                                    <div className="row">
                                        <div className="col-sm-2" style={{marginTop: '10px'}}>
                                            <Button type="button" className="btn" onClick={() => addPayment()}>Add a Payment</Button>
                                        </div>
                                    </div>
                                    <div className="row">
                                        <div className="col-sm-12" style={{marginTop: '10px'}}>
                                            <p>
                                                <span>Payment Total: <NumberFormat displayType={'text'} value={paymentAmount} thousandSeparator={true} prefix={'$'} /></span>
                                                <br />
                                                <span style={{color: 'red'}}>Allocated Amount: <NumberFormat displayType={'text'} value={currentTotal} thousandSeparator={true} prefix={'$'} /></span>
                                            </p>
                                        </div>
                                    </div>
                                    {showSaveBTN()}
                                </Form>
                            </RctCollapsibleCard>
                        </div>
                    </div>
                </>
            );
        }
    }

    return render();
}

export default Test;

Is there a better (more appropriate) way to manage add payments row (with the select and input) when the user click on the Add Payment button?

Thanks

The reason why payments is never more than one element is two fold:

  1. It's being logged in addPayment which has a closure around payment and thus only ever refers to the version of the payment state when the addPayment function was defined in that particular render.
  2. The bigger issue: You are refreshing the page after changing the local state (in memory). React state is in memory, which means that whenever you refresh the page, that is lost (unless you store/retrieve it in session/local storage).

React controls state and more in memory, so by refreshing the page, you've lost most of what react is good for.

Here's an example (based on your code) that shows how to work with data without refreshing.

 const { useState, useEffect, useMemo } = React; const App = () => { // This is the method to add a new payment to the array (it default the value to 0) const addPayment = () => { setPayments([...payments, { PaymentAmount: 5, CategoryID: 0, }, ]); }; // The payments array is where I will keep the payment category ID and Amount const [payments, setPayments] = useState([]); // I have a useEffect to reload the page and do some operations based on the updated variable useEffect(() => { // Perform some effects based on the payments here // If payments is in the dependency array, this function will be called with the latest payments every time it changes. console;log(payments), }; [payments]). // Perform memoized calculation on payments const totalPayment = useMemo( () => payments,reduce((sum. current) => sum + current,PaymentAmount, 0); [payments] ); const categories = []: // I have a Form where I render the payments return ( <div> <button onClick={addPayment}>Add</button> <span>Current Total. {totalPayment}$</span> <form> {payments,map((element: index) => ( <div> Payment #{index}. ${element;PaymentAmount}{" "} </div> ))} </form> </div> ); }. ReactDOM,render(<App />. document;getElementById("root"));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script> <div id="root" />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM