Ketika ada pembaruan baru di komponen induk saya, yang terjadi dalam function updateItemValue() , status items hanya akan diperbarui di induk. Komponen anak tidak akan diperbarui dalam sebuah instance. Itu hanya diperbarui ketika updateItemValue() dipicu dua kali.

Ini seperti:

  • Putaran pertama, orang tua memperbarui item, anak tidak menerima pembaruan.
  • Putaran ke-2, orang tua memperbarui item, anak hanya menerima pembaruan dari putaran pertama

Komponen Induk

const SelectItem = () => {
    const { items } = useSelector(state => state.itemsReducer);
    const [ itemUpdate, setItemUpdate ] = useState(false);
    const [ group_id, setGroupId ] = useState(false);

    useEffect(() => {
        updateItemValue()
    }, [itemUpdate])
     
    function updateItemValue(){
        // original items value
        /*
            [
                {"_id":1, "name":"Item 1", "description":"Item 1 description"},
                {"_id":2, "name":"Item 2", "description":"Item 2 description"},
                {"_id":3, "name":"Item 3", "description":"Item 3 description"}
            ]
        */
    
        // step 3 : now this function get called because the group_id is updated
        items.forEach((item, i) => {
            if (group_id == item.group_id) {
                // step 4 : update the value of items state, add extra data, once this updated, I expect the new items value to be passed to child component
                items[i].extra = true;
            }
        });
    }
    
    // step 2 : update the group_id and set the itemUpdate to true to trigger useEffect
    function selectGroup(group_id){
        setItemUpdate(true)
        setGroupId(group_id)
    }
    
    return(
        <div>
            {/* step 1: select option */}
            <button onClick={() => selectGroup(1)}>Option 1</button>
            <button onClick={() => selectGroup(2)}>Option 2</button>
            {
                items.map((item) => (
                    <ItemCard
                      key={item._id}
                      id={item._id}
                      name={item.name}
                      description={item.description}
                      onClick={(value) => updateSelectedItem(value)}
                    />
                ))
            }
        </div>
    )
}

Komponen Anak

export default function ItemCard({id, name, description, onClick, extra}){
    console.log(extra)  // the value is undefined when selectGroup() 1st triggered from parent
    return (
        <div onClick={() => onClick(id)}>
          <div>
            <p>{name}</p>
            <p>{description}</p>
            { extra ? '<p>Extra item is required</p>' : null }
          </div>
        </div>
    )
}

P/s : status items berasal dari status redux. Jadi pembaruan status di updateItemValue() adalah menambahkan beberapa nilai tambahan yang ingin saya gunakan pada komponen anak.

1
Dragon 19 November 2020, 10:56

1 menjawab

Jawaban Terbaik

Isu

Anda mengubah referensi objek

function updateItemValue(){
  items.forEach((item, i) => {
    if (group_id == item.group_id) {
      items[i].extra = true; // <-- state object mutation!
    }
  });
}

Larutan

Sepertinya Anda menggunakan Redux karena Anda menggunakan kait reaksi useSelector.

const { items } = useSelector(state => state.itemsReducer);

Saya akan menganggap Anda memiliki akses ke kait useDispatch dan tindakan untuk dikirim untuk memperbarui status Anda juga.

Anda mungkin dapat sedikit menyederhanakan kode komponen Anda, dan memindahkan logika pembaruan ke peredam sehingga stat diperbarui dengan benar.

const SelectItem = () => {
  const { items } = useSelector((state) => state.itemsReducer);
  const dispatch = useDispatch();

  // step 2: dispatch action to update state via the group_id
  function selectGroup(groupId) {
    dispatch({ type: "UPDATE_GROUP", groupId });
  }

  return (
    <div>
      {/* step 1: select option */}
      <button onClick={() => selectGroup(1)}>Option 1</button>
      <button onClick={() => selectGroup(2)}>Option 2</button>
      {items.map((item) => (
        <ItemCard
          key={item._id}
          id={item._id}
          name={item.name}
          description={item.description}
          onClick={(value) => updateSelectedItem(value)}
        />
      ))}
    </div>
  );
};

Saya hanya akan menebak fungsi peredam sekarang, mungkin mirip dengan yang berikut ini.

const initialState = {
  items: [],
}

const itemsReducer = (state = initialState, action) => {
  switch (action.type) {

    ...

    // step 3: update items array by matching item group id
    case "UPDATE_GROUP":
      return {
        ...state,
        items: state.items.map((item) =>
          item.group_id === action.groupId
            ? {
                ...item,
                extra: true
              }
            : item
        )
      };

    ...

    default:
      return state;
  }
};

Additional Suggestion

Anda juga dapat menyederhanakan komponen turunan ItemCard sedikit. Lampirkan langsung pengendali klik dan sederhanakan rendering bersyarat. Juga, saya tidak tahu apakah itu disengaja (Saya pikir tidak), tetapi Anda mungkin tidak ingin string literal "<p>Extra item is required</p>" dirender.

function ItemCard({ name, description, onClick, extra }) {
  return (
    <div onClick={onClick}>
      <div>
        <p>{name}</p>
        <p>{description}</p>
        {extra && <p>Extra item is required</p>}
      </div>
    </div>
  );
}

Dan perbarui pemetaan di induknya. Jangan meneruskan prop id, tetapi teruskan item._id ke updateSelectedItem callback.

{items.map((item) => (
  <ItemCard
    key={item._id}
    name={item.name}
    description={item.description}
    onClick={() => updateSelectedItem(item._id)}
  />
))}
1
Drew Reese 19 November 2020, 09:24