@@ -317,6 +317,25 @@ public function dehydrateState(array &$state, bool $isDehydrated = true): void
317317 $ statePath = $ this ->getStatePath ();
318318
319319 if (! $ this ->getRootContainer ()->hasDehydratedComponent ($ statePath )) {
320+ // When another component in the same scope shares this
321+ // `statePath`, removing the entire key would destroy that
322+ // sibling's data. Instead, only remove the state paths
323+ // owned by *this* component's descendants.
324+ if ($ this ->hasComponentWithStatePath ($ statePath )) {
325+ $ descendantStatePathsToForget = $ this ->getDescendantStatePathsToForget ($ statePath );
326+
327+ Arr::forget ($ state , $ descendantStatePathsToForget ); /** @phpstan-ignore parameterByRef.type */
328+
329+ // Clean up the parent key when nothing meaningful
330+ // remains (e.g. all siblings sharing this path were
331+ // also non-dehydrated and removed their descendants).
332+ if (blank (Arr::get ($ state , $ statePath ))) {
333+ Arr::forget ($ state , $ statePath ); /** @phpstan-ignore parameterByRef.type */
334+ }
335+
336+ return ;
337+ }
338+
320339 Arr::forget ($ state , $ statePath ); /** @phpstan-ignore parameterByRef.type */
321340
322341 return ;
@@ -347,6 +366,63 @@ public function dehydrateState(array &$state, bool $isDehydrated = true): void
347366 }
348367 }
349368
369+ /**
370+ * Check whether another component in the same schema scope shares the
371+ * given absolute `statePath`. The scope is determined by walking up
372+ * through parent containers that don't introduce their own `statePath`
373+ * (e.g. `Section`, `Tabs`) until a `statePath`-bearing ancestor is
374+ * found — that ancestor's container defines the boundary.
375+ */
376+ protected function hasComponentWithStatePath (string $ statePath ): bool
377+ {
378+ $ container = $ this ->getContainer ();
379+
380+ while ($ parentComponent = $ container ->getParentComponent ()) {
381+ if ($ parentComponent ->hasStatePath ()) {
382+ break ;
383+ }
384+
385+ $ container = $ parentComponent ->getContainer ();
386+ }
387+
388+ foreach ($ container ->getFlatComponents (withActions: false , withHidden: true ) as $ component ) {
389+ if ($ component === $ this ) {
390+ continue ;
391+ }
392+
393+ if ($ component ->hasStatePath () && $ component ->getStatePath () === $ statePath ) {
394+ return true ;
395+ }
396+ }
397+
398+ return false ;
399+ }
400+
401+ /**
402+ * @return array<string>
403+ */
404+ protected function getDescendantStatePathsToForget (string $ statePath ): array
405+ {
406+ $ descendantStatePathPrefix = "{$ statePath }. " ;
407+ $ paths = [];
408+
409+ foreach ($ this ->getChildSchemas (withHidden: true ) as $ childSchema ) {
410+ foreach ($ childSchema ->getFlatComponents (withActions: false , withHidden: true ) as $ component ) {
411+ if (! $ component ->hasStatePath ()) {
412+ continue ;
413+ }
414+
415+ $ childStatePath = $ component ->getStatePath ();
416+
417+ if (filled ($ childStatePath ) && str_starts_with ($ childStatePath , $ descendantStatePathPrefix )) {
418+ $ paths [] = $ childStatePath ;
419+ }
420+ }
421+ }
422+
423+ return $ paths ;
424+ }
425+
350426 public function dehydrateStateUsing (?Closure $ callback ): static
351427 {
352428 $ this ->dehydrateStateUsing = $ callback ;
0 commit comments