adrianthedev
adrianthedev

Reputation: 646

React state wrongly updated in nested components with CodeMirror 6 Editor

I have a few components nested inside a larger "controller" component.

The whole demo app is below. There's also a StackBlitz.

import React, { useState } from 'react';
import CodeEditor from './CodeEditor';
import './style.css';

const PostEditor = ({ value, onChange }) => {
  return (
    <>
      {value && (
        <>
          <CodeEditor value={value} onChange={value => onChange(value)} />
        </>
      )}
    </>
  );
};

const Wrapper = ({ post, updatePostProperty }) => {
  return (
    <>
      <h2>Selected post: {post && post.title}</h2>
      {post && post.title && (
        <PostEditor
          value={post.title}
          onChange={value => {
            console.log('update title->', value);
            updatePostProperty(post.id, 'title', value);
          }}
        />
      )}
      {post && post.subTitle && (
        <PostEditor
          value={post.subTitle}
          onChange={value => {
            console.log('update subTitle->', value);
            updatePostProperty(post.id, 'subTitle', value);
          }}
        />
      )}
    </>
  );
};

export default function App() {
  const [posts, setPosts] = useState([
    { id: 1, title: 'post no 1', subTitle: 'subtitle no 1' },
    { id: 2, title: 'post no 2', subTitle: 'subtitle no 2' },
    { id: 3, title: 'post no 3', subTitle: 'subtitle no 3' }
  ]);
  const [post, setPost] = useState();

  const updatePostProperty = (id, property, value) => {
    const newPosts = [...posts];
    const index = newPosts.findIndex(post => (post.id === id));
    newPosts[index] = {
      ...newPosts[index],
      [property]: value
    };
    setPosts(newPosts);
  };
  return (
    <div>
      <ul>
        {posts &&
          posts.length > 0 &&
          posts.map((post, index) => (
            <li
              style={{ cursor: 'pointer' }}
              onClick={() => {
                setPost(post);
              }}
            >
              {post.title} - {post.subTitle}
            </li>
          ))}
      </ul>

      <Wrapper post={post} updatePostProperty={updatePostProperty} />
    </div>
  );
}

The App component hosts the updatePostProperty that is passed on to the Wrapper component which uses it when the PostEditor component triggers onChange the CodeEditor which is a wrapper for CodeMirror.

The issue here is that after you click one of the posts and edit the title and then the subtitle, the title gets reverted to the initial value.

Scenario:
Click on the first post and try to edit the title. Add an ! to the title. You'll see the post on the list gets updated.
After you edit the subtitle by adding a character to it, you'll see the title gets reverted to the previous state (without the !) in the App component.

Why is react doing this "revert" update?

gif demonstrating bad react behaviour


Update:

New StackBlitz.

However, I'd love someone's input on why does the issue still persists with the way CodeMirror is wired up.

Upvotes: 0

Views: 269

Answers (2)

monesul haque
monesul haque

Reputation: 369

  1. it needs to be newPosts instead of posts
  2. no need to destructuring
  3. you are using 1 = here newPosts.findIndex(post => (post.id = id)); there suppose to be 2 == like newPosts.findIndex(post => (post.id == id));

checkout this code

  const updatePostProperty = (id, property, value) => {
    const newPosts = [...posts]; 
    const index = newPosts.findIndex(post => (post.id == id)); 
    newPosts[index][property] = value
    setPosts(newPosts);
  };

Upvotes: 0

Marco Nisi
Marco Nisi

Reputation: 1241

Inside updatePostProperty you're updating the wrong object.

You're updating:

posts[index] = {
  ...newPosts[index],
  [property]: value
};

But you want to update newPosts instead, so you have to do:

newPosts[index] = {
  ...newPosts[index],
  [property]: value
};

Upvotes: 1

Related Questions