Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update a react state (array of objects) from a child component

I would like to update the parent state from child component, which renders each object of the array of objects. The main goal of the child component is to update the original value from the array of objects.

I've the following code

Parent:

import { useState } from 'react';
import ExpenseItem from './expenseItem';

function Update({ data }) {
    const [ expenses, setExpenses ] = useState(data);
    return (
        <div>
            {expenses.map((expense, index) => {
                return <ExpenseItem key={index} {...expense} />;
            })}
            <button>Save</button>
        </div>
    );
}

export default Update;

child:

import { useState, useRef } from 'react';

function ExpenseItem({ description, date, credit, debit }) {
    const [ edit, setEdit ] = useState(false);
    const [ expenseDescription, setExpenseDescription ] = useState(description);
    const textInput = useRef();
    const renderDefaultView = () => {
        return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
    };
    const renderEditView = () => {
        return (
            <div>
                <input
                    type="text"
                    ref={textInput}
                    defaultValue={expenseDescription}
                    onDoubleClick={() => setEdit(true)}
                />
                <button onClick={() => setEdit(false)}>X</button>
                <button onClick={() => updateValue()}>OK</button>
            </div>
        );
    };
    const updateValue = () => {
        const value = textInput.current.value;
        setExpenseDescription(value);
        textInput.current.defaultValue = value;
        setEdit(false);
    };
    return (
        <div>
            {edit ? renderEditView() : renderDefaultView()}
            <span>{date}</span>
            <p>{debit}</p>
            <p>{credit}</p>
        </div>
    );
}

export default ExpenseItem;
like image 297
asotos Avatar asked Oct 16 '22 08:10

asotos


1 Answers

Once way, is to pass the parent state property (expenses) and the function that updates it (setExpenses) to the child Component via the props:

Parent:

import React from 'react';
import ReactDOM from 'react-dom';

import { useState } from 'react';
import ExpenseItem from './ExpenseItem';

function Update({ data }) {
    const [ expenses, setExpenses ] = useState(data);
    return (
        <div>
            Checking: { expenses[0].description } | { expenses[1].description }
            <hr/>
            {expenses.map((expense, index) => {
                return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
            })}
            <button>Save</button>
        </div>
        );
}

export default Update;

Child:

import React from 'react';
import { useState, useRef } from 'react';

function ExpenseItem( props ) {
    let { description, date, credit, debit } = props.expenses[props.index];
    const setExpenses = props.setExpenses;
    const [ edit, setEdit ] = useState(false);
    const [ expenseDescription, setExpenseDescription ] = useState(description);
    const textInput = useRef();
    const renderDefaultView = () => {
        return <h3 onDoubleClick={() => setEdit(true)}>{expenseDescription}</h3>;
    };
    const renderEditView = () => {
        return (
            <div>
                <input
                    type="text"
                    ref={textInput}
                    defaultValue={expenseDescription}
                    onDoubleClick={() => setEdit(true)}
                />
                <button onClick={() => setEdit(false)}>X</button>
                <button onClick={() => updateValue()}>OK</button>
            </div>
        );
    };
    const updateValue = () => {
        const value = textInput.current.value;
        setExpenseDescription(value);
        textInput.current.defaultValue = value;
        setEdit(false);
        const expenses = [ ...props.expenses ]; // Get a copy of the expenses array
        // Replace the current expense item
        expenses.splice( props.index, 1, {
            description: value, date, credit, debit
        }); 
       // Update the parent state
        setExpenses( expenses );
        
    };
    return (
        <div>
            {edit ? renderEditView() : renderDefaultView()}
            <span>{date}</span>
            <p>{debit}</p>
            <p>{credit}</p>
        </div>
    );
}

export default ExpenseItem;

Working demo

  • This can get really complicated as you move along, so the best option is to look for some sort of State Management solution, like using the Context API.

  • Also, take a look at this interesting post that talks about using the map index value as a key value: Index as a key is an anti-pattern

like image 76
Kostas Minaidis Avatar answered Nov 12 '22 18:11

Kostas Minaidis