I'm building a text editor using React with Typescript. The component hierarchy looks like this: TextEditor -> Blocks -> Block -> ContentEditable.
The ContentEditable is an npm package https://www.npmjs.com/package/react-contenteditable.
What i want it to do
The behavior I'm after is similar to Medium or Notions text editor. When a user writes in a block and hits enter on their keyboard, a new block should be created after the current block.
What it does
The behavior right now is strange to me. If I press enter and add one block, it works fine. But if I press enter again it overrides the previous block instead of creating a new one. However, if I press enter and add a block, then puts the carrot (focusing) on the new block and press enter again, a new block is added after as expected.
Sandbox
Here is a sandbox with the complete code: https://codesandbox.io/s/texteditor-mxgbey?file=/src/components/Block.tsx:81-557
TextEditor
export default function TextEditor(props) {
const [blocks, setBlocks] = useState([
{ id: "1", tag: "h1", html: "Title1" },
{ id: "2", tag: "p", html: "Some text" }
]);
function handleAddBlock(id: string) {
const index = blocks.findIndex((b) => b.id === id);
let copiedBlocks = [...blocks];
let newBlock = { id: nanoid(), tag: "p", html: "New block..." };
copiedBlocks.splice(index 1, 0, newBlock);
setBlocks(copiedBlocks);
}
return <Blocks injectedBlocks={blocks} handleAddBlock={handleAddBlock} />;
}
Blocks
export default function Blocks(props) {
const { injectedBlocks, handleAddBlock } = props;
return (
<>
{injectedBlocks.map((b) => {
return (
<Block
key={b.id}
id={b.id}
tag={b.tag}
html={b.html}
handleAddBlock={handleAddBlock}
/>
);
})}
</>
);
}
Block
export default function Block(props) {
const { id, tag, html, handleAddBlock } = props;
function handleChange(e: React.SyntheticEvent) {}
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === "Enter") {
console.log("Enter pressed on: ", id);
e.preventDefault();
handleAddBlock(id);
}
}
return (
<ContentEditable
tagName={tag}
html={html}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
);
}
CodePudding user response:
State value not give the updated value while handleAddBlock function calls. So use like this,
setBlocks((p) => {
let copiedBlocks = [...p];
let newBlock = { id: nanoid(), tag: "p", html: "New block..." };
copiedBlocks.splice(index 1, 0, newBlock);
return copiedBlocks;
});
This will gives the updated state value immediately.