-1

Please consider the following code: https://codepen.io/kyxey/pen/XWEWBRY

const { useRef, useState } = React;

function App() {
  const inputRef = useRef(null);
  const [jobs, setJobs] = useState([]);

  const addJob = () => {
    const newJob = inputRef.current.value;

    if (newJob) {
      setJobs((prevJobs) => [...prevJobs, newJob]);
    }
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={addJob}>Add</button>
      <ul>
        {jobs.map((job) => {
          const listItem = job + " " + Math.random();
          return <li key={listItem}>{listItem}</li>;
        })}
      </ul>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

In this example, Whenever a new item gets added to the list, the entire list re-renders. You can tell this by looking at the random number in front of each item which will change with every newly items that are added to the list.

Now I know there are some duplications of this very question which I read them all. But none of them could solve my issue completely.

My question is: How can I prevent the re-render of the entire list each time a new item is added to it, WITHOUT using memo and useMemo in any shapes and forms? Meaning that whenever a new item is added to the list, only the new item is rendered and the other items on the list remain completely untouched. Again, I'm not able to use memo or useMemo to solve this.

For example:

Current behavior is like this:

  • I type Test in the input
  • Click the Add button
  • A new item gets added to the list below with a random number in front of it:
     • Test 0.8025874545033296
    
  • I type AnotherTest in the input
  • Click the Add button
  • A new item gets added to the list below with a random number in front of it, BUT the random number in the front of the first item is also modified:
     • Test 0.4454662757698613
     • AnotherTest 0.16319305763152014
    

Expected behavior should be like this:

  • I type Test in the input
  • Click the Add button
  • A new item gets added to the list below with a random number in front of it:
     • Test 0.8025874545033296
    
  • I type AnotherTest in the input
  • Click the Add button
  • A new item gets added to the list below with a random number in front of it, AND the random number in the front of the first item is NOT modified. Meaning that it's NOT re-rendered:
     • Test 0.8025874545033296
     • AnotherTest 0.16319305763152014
    

UPDATE:

This question was asked from me in a coding interview. They explicitly mentioned that I'm not allowed to use memo or useMemo in the sense that these are considered cheating! Now I don't know exactly why they think in such way but I'm sure there's an specific answer in their mind that is not how React is supposed to behave.

KYXEY
  • 98
  • 8
  • 1
    *"Again, I'm not able to use memo or useMemo to solve this."* That's an unreasonable limitation, which needs explaining. **Why** can't you use [`memo`](https://reactjs.org/docs/react-api.html#reactmemo) (or I'm guessing an equivalant, such as `shouldComponentUpdate` in a `class` component)? That **is** how you do this. (You could reinvent it with a ref, but...) I ask because my suspicion is that you think you can't use `memo` for some reason but in fact that's a misunderstanding or similar, and being able to explain the misunderstanding (if so) would be helpful to you. – T.J. Crowder Jun 29 '22 at 07:31
  • Why _wouldn't_ you want to re-render the whole list? You've just added a new item to the state and so that new state _has_ to be reflected in the render. Am I missing something? – Andy Jun 29 '22 at 07:37
  • @Andy - I'm guessing they mean not re-rendering the existing *items* in the list. (The random number thing makes sense in that context.) – T.J. Crowder Jun 29 '22 at 07:39
  • It's making my React spidey-sense tingle. If the random number is changing that's not a random number the OP is adding, that's what React is adding to manage the list. @T.J.Crowder – Andy Jun 29 '22 at 07:42
  • 1
    @Andy - My impression was they're adding it, but we'll see if/when the OP puts the code in the question (and adds an explanation of the requirement). – T.J. Crowder Jun 29 '22 at 07:45
  • Please also explain why the random number is added during rendering, rather than when adding the job. Rendering should be (some would say *must* be) [idempotent](https://en.wikipedia.org/wiki/Idempotence). – T.J. Crowder Jun 29 '22 at 09:04
  • 1
    Thank you for your comments. @T.J.Crowder I guess the reason behind the random number thing was to demonstrate that the items are all getting re-rendered with every new one added to the list. I don't exactly know but this was the question in the coding interview. – KYXEY Jun 29 '22 at 09:14

1 Answers1

1

You've said:

This question was asked from me in a coding interview. They explicitly mentioned that I'm not allowed to use memo or useMemo in the sense that these are considered cheating! Now I don't know exactly why they think in such way but I'm sure there's an specific answer in their mind that is not how React is supposed to behave.

A reasonable answer to that — quite possibly the one they were expecting — is something along the lines of: "You can do that, but it would just be reinventing memo for no good reason, and by doing something so non-standard and unusual, it would make the code hard to understand and maintain for the next person." If they actually wanted to see a solution without using those things, I would suggest you cross them off the list of places you might consider working if you have any choice (I respect the fact you may not have a choice; I remember that vividly early in my career). That's a terrible interview question if they really wanted anything other than pushback (arguably even if they were looking for pushback), which can be indicative of a bad place to work.

But again, technically, you can it with a ref by storing the rendered li elements in it (perhaps in a Map). To me, that's much more "cheating" than doing it with memo as you should, but... Here's an example:

const { useRef, useState } = React;

// *** A means of having unique keys for jobs
let nextJobId = 1;
function App() {
    const inputRef = useRef(null);
    const [jobs, setJobs] = useState([]);
    const renderedJobs = useRef(new Map());
    const jobLiElements = renderedJobs.current;

    const addJob = () => {
        // *** Make the jobs objects, not just strings, so the
        // same string can be used by more than one job and
        // so we can give the job a unique ID.
        const newJob = {
            id: nextJobId++,
            value: inputRef.current.value,
        };

        if (newJob) {
            setJobs((prevJobs) => [...prevJobs, newJob]);
        }
    };

    return (
        <div>
            <input ref={inputRef} type="text" />
            <button onClick={addJob}>Add</button>
            <ul>
                {jobs.map((job) => {
                    // *** Reuse li elements you already have if you have them,
                    // adding new ones if you don't
                    let li = jobLiElements.get(job);
                    if (!li) {
                        const listItem = job.value + " " + Math.random();
                        console.log(`Rendering li for ${job.value}`);
                        // *** You can't use `listItem` as the key, since there's
                        // _some_ chance of the same `li` having the same text and
                        // random number. Use the job object instead.
                        li = <li key={job.id}>{listItem}</li>;
                        jobLiElements.set(job, li);
                    }
                    return li;
                })}
            </ul>
        </div>
    );
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

But even if I went so far as to show them that, I'd also show them a solution using memo and putting the random value in the state of the job, talking about the strengths of doing it that way (not least that it's the normal, expected way to do this):

const { useRef, useState } = React;

const JobItem = React.memo(({ job: { value, rand } }) => {
    const text = `${value} ${rand}`;
    console.log(`Rendering JobItem for "${text}"`);
    return <li>{text}</li>;
});

// *** A means of having unique keys for jobs
let nextJobId = 1;

function App() {
    const inputRef = useRef(null);
    const [jobs, setJobs] = useState([]);

    const addJob = () => {
        // *** Make the jobs objects, not just strings, so the
        // same string can be used by more than one job, and so
        // we can assign it a unique ID to use as a key.
        // *** Assign the random number once, as part of the job.
        const newJob = {
            id: nextJobId++,
            value: inputRef.current.value,
            rand: Math.random(),
        };

        if (newJob) {
            setJobs((prevJobs) => [...prevJobs, newJob]);
        }
    };

    return (
        <div>
            <input ref={inputRef} type="text" />
            <button onClick={addJob}>Add</button>
            <ul>
                {/* *** Now just use JobItem*/}
                {jobs.map((job) => (
                    <JobItem key={job.id} job={job} />
                ))}
            </ul>
        </div>
    );
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875