From cf1d4c06e237961eec56d78f6a71379523336c04 Mon Sep 17 00:00:00 2001 From: Prev Wong Date: Fri, 31 May 2024 11:17:33 +0800 Subject: [PATCH] fix: ssr hydration error in strict mode --- .changeset/grumpy-shrimps-call.md | 5 + packages/core/src/nodes/Element.tsx | 43 +++--- .../core/src/nodes/tests/Element.test.tsx | 122 ------------------ 3 files changed, 22 insertions(+), 148 deletions(-) create mode 100644 .changeset/grumpy-shrimps-call.md delete mode 100644 packages/core/src/nodes/tests/Element.test.tsx diff --git a/.changeset/grumpy-shrimps-call.md b/.changeset/grumpy-shrimps-call.md new file mode 100644 index 000000000..3471cf5fc --- /dev/null +++ b/.changeset/grumpy-shrimps-call.md @@ -0,0 +1,5 @@ +--- +'@craftjs/core': patch +--- + +Fix hydration errror in react strict mode diff --git a/packages/core/src/nodes/Element.tsx b/packages/core/src/nodes/Element.tsx index 8db7f4f6e..22b369e1f 100644 --- a/packages/core/src/nodes/Element.tsx +++ b/packages/core/src/nodes/Element.tsx @@ -40,42 +40,33 @@ export function Element({ }; const { query, actions } = useInternalEditor(); - const { node, inNodeContext } = useInternalNode((node) => ({ - node: { - id: node.id, - data: node.data, - }, - })); + const { id: nodeId, inNodeContext } = useInternalNode(); const [linkedNodeId] = useState(() => { invariant(!!id, ERROR_TOP_LEVEL_ELEMENT_NO_ID); - const { id: nodeId, data } = node; + const node = query.node(nodeId).get(); if (inNodeContext) { - let linkedNodeId; - - const existingNode = - data.linkedNodes && - data.linkedNodes[id] && - query.node(data.linkedNodes[id]).get(); + const existingNode = node.data.linkedNodes[id] + ? query.node(node.data.linkedNodes[id]).get() + : null; // Render existing linked Node if it already exists (and is the same type as the JSX) if (existingNode && existingNode.data.type === is) { - linkedNodeId = existingNode.id; - } else { - // otherwise, create and render a new linked Node - const linkedElement = React.createElement( - Element, - elementProps, - children - ); + return existingNode.id; + } - const tree = query.parseReactElement(linkedElement).toNodeTree(); + // otherwise, create and render a new linked Node + const linkedElement = React.createElement( + Element, + elementProps, + children + ); - linkedNodeId = tree.rootNodeId; - actions.history.ignore().addLinkedNodeFromTree(tree, nodeId, id); - } - return linkedNodeId; + const tree = query.parseReactElement(linkedElement).toNodeTree(); + + actions.history.ignore().addLinkedNodeFromTree(tree, nodeId, id); + return tree.rootNodeId; } return null; }); diff --git a/packages/core/src/nodes/tests/Element.test.tsx b/packages/core/src/nodes/tests/Element.test.tsx deleted file mode 100644 index a1d72f1b3..000000000 --- a/packages/core/src/nodes/tests/Element.test.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { render } from '@testing-library/react'; -import React from 'react'; - -import { createTestNode } from '../../utils/createTestNode'; -import { Element } from '../Element'; - -let parentNode; -let existingLinkedNode; -let newLinkedNode = createTestNode('newLinkedNode'); - -let toNodeTree = jest.fn().mockImplementation(() => ({ - rootNodeId: newLinkedNode.id, -})); - -let addLinkedNodeFromTree = jest.fn(); -let parseReactElement = jest.fn().mockImplementation(() => ({ - toNodeTree, -})); - -jest.mock('../../editor/useInternalEditor', () => ({ - useInternalEditor: () => ({ - actions: { - history: { - ignore: jest.fn().mockImplementation(() => ({ - addLinkedNodeFromTree, - })), - }, - }, - query: { - parseReactElement, - node: jest.fn().mockImplementation((id) => ({ - get() { - if (id === 'parent-node') return parentNode; - else return existingLinkedNode; - }, - })), - }, - }), -})); - -jest.mock('../useInternalNode', () => ({ - useInternalNode: () => ({ - node: parentNode, - inNodeContext: true, - }), -})); - -const NodeElementTest = jest.fn().mockImplementation(() => null); - -jest.mock('../NodeElement', () => ({ - NodeElement: jest.fn().mockImplementation((props) => NodeElementTest(props)), -})); - -describe('', () => { - beforeEach(() => { - parentNode = createTestNode('test'); - }); - - describe('when no id is passed', () => { - it('should throw error', () => { - expect(() => render()).toThrow(); - }); - }); - - describe('when there is no existing node', () => { - let elementProps, children; - - beforeEach(() => { - elementProps = { - color: '#fff', - }; - - children =

Child

; - render( - - {children} - - ); - }); - - it('should call query.parseReactElement()', () => { - const arg0 = parseReactElement.mock.calls[0][0]; - const arg0WithoutOwner = { ...arg0, _owner: null }; - expect(arg0WithoutOwner).toEqual( - {children} - ); - }); - it('should call actions.addLinkedNodeFromTree()', () => { - expect(addLinkedNodeFromTree).toHaveBeenCalled(); - }); - it('should render a new linked Node', () => { - expect(NodeElementTest).toHaveBeenCalledWith({ - id: newLinkedNode.id, - }); - }); - }); - - describe('when there is an existing node', () => { - beforeEach(() => { - existingLinkedNode = createTestNode('existing-linked-node', { - type: 'div', - props: { - background: '#000', - color: '#fff', - }, - }); - - parentNode = createTestNode('parent-node', { - linkedNodes: { - test: existingLinkedNode.id, - }, - }); - - render(); - }); - it('should render existing Node', () => { - expect(NodeElementTest).toHaveBeenCalledWith({ - id: existingLinkedNode.id, - }); - }); - }); -});