1

I Try not to Rerender Persons Component When ShowCockpit State Changes In MainAssignment Component. Like when i do in Cockpit Component, it doesn't rerender When Persons state change. In This Case We Have 3 Components MainAssignment Component [parnt] , Cockpit Component [child] , Persons Component [child].

/********************************************************/
/*** MainAssignment Component ***/

import React, { useCallback, useState } from 'react';
import Persons from './persons';
import Coockpit from './cockpit';

const MainAssignment = () => {
  // All State
  const [persons, setPersons] = useState([
    { id: '1', name: 'mustafa', age: 24 },
    { id: '2', name: 'ahmed', age: 25 },
    { id: '3', name: 'saad', age: 26 },
  ]);
  const [showPersons, setShowPersons] = useState(true);
  const [showCoockpit, setShowCoockpit] = useState(true);

  const togglePersonHandler = useCallback(() => {
    setShowPersons(!showPersons);
  }, [showPersons]);

  // change name in specific object in persons state
  const nameChangeHandler = (e, id, personIndex) => {
    let newPersons = [...persons];
    let person = { ...newPersons[personIndex] };
    person.name = e.target.value;

    newPersons[personIndex] = person;

    setPersons(newPersons);
  };

  // delete object from persons state
  const deletePersonHandler = (personIndex) => {
    let newPersons = [...persons];
    newPersons.splice(personIndex, 1);
    setPersons(newPersons);
  };

  // Main Render
  return (
    <>
      <button
        onClick={() => {
          setShowCoockpit((prev) => !prev);
        }}
      >
        remove Coockpit
      </button>
      {showCoockpit ? (
        <div style={{ border: '1px solid' }}>
          <Coockpit clicked={togglePersonHandler} personsLength={persons.length} showPersons={showPersons} />
        </div>
      ) : null}
      {showPersons ? <Persons persons={persons} clicked={deletePersonHandler} changed={nameChangeHandler} /> : null}
    </>
  );
};

export default MainAssignment;


/********************************************************/
/*** Cockpit Component ***/

/********************************************************/
/*** Cockpit Component ***/

import React, { useRef } from 'react';

const Cockpit = ({ clicked }) => {
  let toggleBtnRef = useRef(null);

  console.log('render => Cockpit');

  return (
    <div>
      <h1>hi i'm a main assin from cockpit</h1>
      <button className="toggle-persons" onClick={clicked} ref={toggleBtnRef}>
        toggle persons
      </button>
    </div>
  );
};

// in Cockpit i use React.memo and it work
export default React.memo(Cockpit);


/********************************************************/
/*** Persons Component ***/

import React, { useEffect, useRef } from 'react';
import Person from './person';

const Persons = ({ persons, clicked, changed }) => {
  console.log('render => personssss');

  const mainRef = {
    allInputPersonRef: useRef([]),
  };

  return (
    <>
      {persons?.map((person, idx) => (
        <Person
          key={idx}
          name={person.name}
          age={person.age}
          position={idx}
          index={idx}
          ref={mainRef}
          click={() => {
            clicked(idx);
          }}
          changed={(e) => {
            changed(e, person.id, idx);
          }}
        />
      ))}
    </>
  );
};

// in Persons i use React.memo and it doesn't work
export default React.memo(Persons);


/********************************************************/
/*** Person Component ***/

import React from 'react';

const Person = React.forwardRef((props, ref) => {
  const { allInputPersonRef } = ref;

  // value of props
  const { name, age, click, changed, children, index } = props;

  return (
    <div>
      <p onClick={click}>
        i'm {name} and i'm {age} years old
      </p>
      <p> i'am props children: {children}</p>
      <input type="text" onChange={changed} value={name} ref={(el) => (allInputPersonRef.current[index] = el)} />
      <button onClick={click}>delete this person</button>
    </div>
  );
});

export default Person;
1
  • Can you post how the Cockpit component renders the Persons and where the props passed to Persons come from Commented Dec 22, 2021 at 12:59

2 Answers 2

2

React.memo can prevent children from rerendering when the parent component rerenders.

It compares (by reference) each previous and next prop. When one of them is different React will rerender the child normally.

In your case you are always passing new function to changed prop

 const nameChangeHandler = (e, personIndex) => {
    let newPersons = [...persons];
    let person = { ...newPersons[personIndex] };
    person.name = e.target.value;

    newPersons[personIndex] = person;
    setPersons(newPersons);
  };

How to avoid this?

Make sure that nameChangeHandler is the same function each time you need to rerender and you don't want to rerender the Person component. https://reactjs.org/docs/hooks-reference.html#usecallback

 const nameChangeHandler = useCallback((e, personIndex) => {
    setPersons((persons) => {
        let newPersons = [...persons];
        let person = { ...newPersons[personIndex] };
        person.name = e.target.value;

        newPersons[personIndex] = person;
        return newPersons
    });
  }, []);

Similarly you should memorize deletePersonHandler function

const deletePersonHandler = useCallback((personIndex) => {
    setPersons((persons)=>{
        let newPersons = [...persons];
        newPersons.splice(personIndex, 1);
        return newPersons
    });
}, []);
Sign up to request clarification or add additional context in comments.

6 Comments

thank you for your answer. after try your solution prevent persons component to rerender when showCockpit state change but nameChangeHandler doesn't work as expected. nameChangeHandler should run when value of input in Person Component chenge and take this value then update specific object in persons state.
what happen now when value of input change nameChangeHandler take this value and updat next object not specific object. ex: when focus on input in first person component and click on "k" => persons state = { id: '1', name: 'mustafa', age: 24 }, { id: '2', name: 'mustafak', age: 25 }, { id: '3', name: 'mustafa3', age: 26 }, then when click on "v" in first person component too => persons state = { id: '1', name: 'mustafa', age: 24 }, { id: '2', name: 'mustafav', age: 25 }, { id: '3', name: 'mustafa3', age: 26 },
i think this happens because persons state don't change in nameChangeHandler after using useCallback but i don't know what should i do
I checked and code you provided without useCallback just works that way codesandbox.io/s/falling-snowflake-ncpv4?file=/src/App.js
I really thank you but try the above code again
|
0

using useCallback with togglePersonHandler and deletePersonHandler

const nameChangeHandler = useCallback((e, id, personIndex) => {
    let newPersons = [...persons];
    let person = { ...newPersons[personIndex] };
    person.name = e.target.value;
    newPersons[personIndex] = person;
    setPersons(newPersons);
  }, []);
const deletePersonHandler = useCallback((personIndex) => {
    let newPersons = [...persons];
    newPersons.splice(personIndex, 1);
    setPersons(newPersons);
  }, []);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.