diff --git a/.changeset/real-glasses-cross.md b/.changeset/real-glasses-cross.md new file mode 100644 index 000000000..279c898a8 --- /dev/null +++ b/.changeset/real-glasses-cross.md @@ -0,0 +1,5 @@ +--- +'@craftjs/core': patch +--- + +SSR support diff --git a/packages/core/src/nodes/Element.tsx b/packages/core/src/nodes/Element.tsx index b53196f93..8db7f4f6e 100644 --- a/packages/core/src/nodes/Element.tsx +++ b/packages/core/src/nodes/Element.tsx @@ -1,4 +1,4 @@ -import { ERROR_TOP_LEVEL_ELEMENT_NO_ID, useEffectOnce } from '@craftjs/utils'; +import { ERROR_TOP_LEVEL_ELEMENT_NO_ID } from '@craftjs/utils'; import React, { useState } from 'react'; import invariant from 'tiny-invariant'; @@ -47,9 +47,7 @@ export function Element({ }, })); - const [linkedNodeId, setLinkedNodeId] = useState(null); - - useEffectOnce(() => { + const [linkedNodeId] = useState(() => { invariant(!!id, ERROR_TOP_LEVEL_ELEMENT_NO_ID); const { id: nodeId, data } = node; @@ -77,9 +75,9 @@ export function Element({ linkedNodeId = tree.rootNodeId; actions.history.ignore().addLinkedNodeFromTree(tree, nodeId, id); } - - setLinkedNodeId(linkedNodeId); + return linkedNodeId; } + return null; }); return linkedNodeId ? : null; diff --git a/packages/core/src/nodes/tests/Element.test.tsx b/packages/core/src/nodes/tests/Element.test.tsx index ea8401693..a1d72f1b3 100644 --- a/packages/core/src/nodes/tests/Element.test.tsx +++ b/packages/core/src/nodes/tests/Element.test.tsx @@ -79,7 +79,9 @@ describe('', () => { }); it('should call query.parseReactElement()', () => { - expect(parseReactElement).toHaveBeenCalledWith( + const arg0 = parseReactElement.mock.calls[0][0]; + const arg0WithoutOwner = { ...arg0, _owner: null }; + expect(arg0WithoutOwner).toEqual( {children} ); }); diff --git a/packages/core/src/render/Frame.tsx b/packages/core/src/render/Frame.tsx index 5679850bf..96e1813da 100644 --- a/packages/core/src/render/Frame.tsx +++ b/packages/core/src/render/Frame.tsx @@ -1,5 +1,5 @@ import { deprecationWarning, ROOT_NODE } from '@craftjs/utils'; -import React, { useEffect, useRef } from 'react'; +import React, { useRef } from 'react'; import { useInternalEditor } from '../editor/useInternalEditor'; import { SerializedNodes } from '../interfaces'; @@ -39,41 +39,28 @@ export const Frame: React.FC> = ({ }); } - const initialState = useRef({ - initialChildren: children, - initialData: data || json, - }); + const isLoaded = useRef(false); - const isInitialChildrenLoadedRef = useRef(false); - - useEffect(() => { - const { initialChildren, initialData } = initialState.current; + if (!isLoaded.current) { + const initialData = data || json; if (initialData) { actions.history.ignore().deserialize(initialData); - return; - } + } else { + const rootNode = React.Children.only(children) as React.ReactElement; - // Prevent recreating Nodes from child elements if we already did it the first time - // Usually an issue in React Strict Mode where this hook is called twice which results in orphaned Nodes - const isInitialChildrenLoaded = isInitialChildrenLoadedRef.current; + const node = query.parseReactElement(rootNode).toNodeTree((node, jsx) => { + if (jsx === rootNode) { + node.id = ROOT_NODE; + } + return node; + }); - if (!initialChildren || isInitialChildrenLoaded) { - return; + actions.history.ignore().addNodeTree(node); } - const rootNode = React.Children.only(initialChildren) as React.ReactElement; - - const node = query.parseReactElement(rootNode).toNodeTree((node, jsx) => { - if (jsx === rootNode) { - node.id = ROOT_NODE; - } - return node; - }); - - actions.history.ignore().addNodeTree(node); - isInitialChildrenLoadedRef.current = true; - }, [actions, query]); + isLoaded.current = true; + } return ; };