Kex
Kex

Reputation: 8579

Passing a function as a prop, when calling the function it doesn't get the correct values

I have a strange issue with passing a function as a prop in my React app. Code as follows:

const NewPage = () => {
    const [blocks, setBlocks] = useState([]);
    const [needsShowImageModal, setNeedsShowImageModal] = useState(false);

    const textButtonHandler = () => {
        const key = randomInt(0, 1000000000);
        const array = blocks.concat({ key, deleteButtonHandler: deleteButtonHandler });
        setBlocks(array);
    };

    function deleteButtonHandler(blockKey) {
        // Test functionality, if one text field was added arrray size should
        // be 1
        console.log(blocks.length);
    }
    return (
        <div>
            <ImageModal 
                show={needsShowImageModal}
                onHide={() => setNeedsShowImageModal(false)}
                insertButtonHandler={insertImageHandler}
            />
            <div className="d-flex">
                <NewPageSidebar
                    textButtonHandler={textButtonHandler}
                    imageButtonHandler={imageButtonHandler}
                    spacingButtonHandler={spacingButtonHandler}
                />
                <NewPageContent blocks={blocks} />
            </div>
        </div>
    );
};

export default NewPage;

When text button handler is called (a button press) I add a new data model to the blocks array. I have another button handler deleteButtonHandler that is passed to the NewPageContent component (inside the data model). NewPageContent:

const NewPageContent = ({ blocks }) => {
    return (
        <div className="new-page-content-container border mr-5 ml-5 p-3">
            {blocks.map(block =>
                <TextBlock 
                    key={block.key} 
                    blockKey={block.key}
                    deleteButtonHandler={block.deleteButtonHandler}
                />
            )}
        </div>
    );
};

NewPageContent.propTypes = {
    blocks: PropTypes.arrayOf(PropTypes.element)
};

export default NewPageContent;

And finally this handler is passed to TextBlock:

const TextBlock = ({ deleteButtonHandler, blockKey }) => {
    const [editorState, setEditorState] = useState(
        () => EditorState.createEmpty(),
    );

    const toolbarClickHander = (buttonType, e) => {
        e.preventDefault();
        switch (buttonType) {
            case 'delete':
                // Delete button handler called here
                deleteButtonHandler(blockKey);
                break;
            default:
                break;
        }
    };

    return(
        <div className='text-block'>
            <TextBlockToolbar clickHandler={toolbarClickHander} />
             <Editor 
                editorState={editorState}
                onChange={setEditorState}
            />
        </div>
    );
    
};

If I add one element to blocks via textButtonHandler the component is rendered on screen as expected. However if I tap the delete button and deleteButtonHandler is called it will log the size of the array as 0, Strangely if I add second element and then tap the delete button for that element if logs the array size as 1. It's almost as if it's taking a snapshot of the blocks state just as the new textButtonHandler is assigned to the prop and not using the actual current state of blocks. Any ideas what I might be doing wrong here? Haven't run into this issue before. Thanks

Upvotes: 0

Views: 267

Answers (1)

Peter
Peter

Reputation: 1249

Okay. What is happening here:

You passing a function in an object. That object might have an own context, and you tryes to use that function in that object context, what confuses react. (I know that ECMAScript simple objects has no own context, but react might process that data, so might work in different way .)
So, pass each function standalone prop in the child component.

Example: https://codesandbox.io/s/broken-waterfall-vgcyj?file=/src/App.js:0-1491

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const [blocks, setBlocks] = useState([
    { key: Math.random(), deleteButtonHandler }
  ]);

  const textButtonHandler = () => {
    const key = Math.random();
    // const array = blocks.concat({
    // key,
    // deleteButtonHandler: deleteButtonHandler
    // });
    setBlocks(prev => prev.concat({ key, deleteButtonHandler }));
  };

  const deleteButtonHandler = blockKey => {
    // Test functionality, if one text field was added arrray size should
    // be 1
    console.log(blocks.length);
  };
  return (
    <div>
      <div className="d-flex">
        <NewPageContent
          deleteButtonHandler={deleteButtonHandler}
          blocks={blocks}
        />
      </div>
      <button onClick={textButtonHandler}>Handler</button>
    </div>
  );
}

const NewPageContent = ({ blocks = [], deleteButtonHandler = () => null }) => {
  return (
    <div className="new-page-content-container border mr-5 ml-5 p-3">
      {blocks.map(block => (
        <TextBlock
          key={block.key}
          blockKey={block.key}
          // deleteButtonHandler={block.deleteButtonHandler}
          deleteButtonHandler={deleteButtonHandler}
        />
      ))}
    </div>
  );
};

const TextBlock = ({ deleteButtonHandler = () => null, blockKey = "" }) => {
  return (
    <div className="text-block">
      {blockKey}
      <span onClick={deleteButtonHandler}>X</span>
    </div>
  );
};

I have consoled out your old solution, to compare it.

Upvotes: 1

Related Questions