0

I have a function component in React called Spec which has a list of Blocks with the below interface. Spec maintains a list of blocks that users an add to, edit, or delete.

Issue: The delete action is not working as intended.

  1. If there's only one block, then it does nothing, and the first console.log() in the function returns an empty array (it should be length 1), and the second console.log() returns an array of length 1 (it should be empty)
  2. If there are more than two blocks in the array, no matter which index I delete, the final (n-1) elements of the array are deleted

Can anyone see what I'm doing wrong?

//types.d.ts

interface Block extends Array {
  type: string,
  block: StoryBlock | MarkdownBlock
}

interface StoryBlock {
  title: string,
  description: string,
  visibility: boolean,
  status: string,
}

interface MarkdownBlock {
  title: string,
  description: string,
  visibility: boolean,
}
const Spec: React.FC<OptSpecProps> = (props) => {
    const [blocks, setBlocks] = useState<Block[]>([]);

...

  const addBlock = (type: string) => {
    let blockSeed;
    switch (type) {
      case "story":
        blockSeed = emptyStoryData;
        break;
      case "markdown":
        blockSeed = emptyMarkdownText
        break;
      default:
        blockSeed = emptyMarkdownText
        break;
    }
    const newBlockArray = blocks.concat({type: type, block: blockSeed})
    setBlocks([...newBlockArray]);
  };

  const removeBlock = (index: number) => {
    console.log(blocks) //This logs an empty array
    const newBlockArray = blocks.splice(index, 1);
    console.log(newBlockArray) // this logs the correct array
    setBlocks([...newBlockArray])
  }

  const updateBlock = (index: number, type: string, block: StoryBlock | MarkdownBlock) => {
    let newBlockArray: Block[] = blocks;
    newBlockArray[index] = {type: type, block: block};
    newBlockArray.forEach((block_itr: Block, i: number) => {
      if (i === index) {
        block_itr.block = block
        block_itr.block.visibility = true
      } else {
        block_itr.block.visibility = false
      }
    })
    setBlocks([...newBlockArray]);
  };

Here is a link to a simplified component sandbox, but it looks from the comments we've identified the issue

12
  • FYI—Calling setBlocks will not immediately update the state. It won't update until the component rerenders. Commented Dec 30, 2020 at 20:07
  • try using useCallback hook for defining removeBlock function Commented Dec 30, 2020 at 20:07
  • @ToddSkelton I'm checking the blocks on each render using useEffect and it's not updating. So I think that means it's not even updating the state? And still unclear to me why blocks first logs to undefined in the function even though I can see it in the view Commented Dec 30, 2020 at 20:15
  • 4
    I can bet dollars to donuts you're using the index as the key when rendering the blocks. You can't do that for mutable lists, each block needs its own unique identifier as the key prop Commented Dec 30, 2020 at 20:25
  • 1
    you are also mutating state directly with splice, this is wrong. you should be doing a copy first from your array, not handling directly Commented Dec 30, 2020 at 20:34

1 Answer 1

1

You are setting the new block to the result of the splice() method, although, that method mutates the array in place and returns the removed elements. Instead, you should clone the array and then splice it

const removeBlock = (index: number) => {
  const newBlockArray = [...blocks];
  newBlockArray.splice(index, 1);
  setBlocks(newBlockArray)
}

P.S. Be aware that the above performs a shallow copy of the array, but it should not be a problem in this case.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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