Repository: symfony/ux
Pull request #2730 (2025-05-13 09:31:42)

[LiveComponent] fix: `LiveComponentHydrator::hydrateValue()` cannot hydrate null values
Hello,

such live prop cannot currently be hydrated by `LiveComponentHydrator::hydrateValue()` because nullability is not handled and the serializer is asked to deserialize `null` into `MyDTO`

```php
    #[LiveProp(writable: true, useSerializerForHydration: true)]
    public ?MyDTO $myDTO = null;
```

this PR fixes this problem

NB: all the failures in the CI are unrelated to this fix

nikophil: @Kocal 

will you release it in 2.24.1 or 2.25.0?
Kocal: 2.25.0 

Patch:
diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md
index a05311bb..692612be 100644
--- a/src/LiveComponent/CHANGELOG.md
+++ b/src/LiveComponent/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 -   Add support for [Symfony UID](https://symfony.com/doc/current/components/uid.html) hydration/dehydration
 -   `ComponentWithFormTrait` now correctly checks for a `TranslatableInterface` placeholder for `<select>` elements
+-   Fix `LiveComponentHydrator::hydrateValue()` to hydrate null values
 
 ## 2.23.0
 
diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php
index ce55e9f8..1a1b01fa 100644
--- a/src/LiveComponent/src/LiveComponentHydrator.php
+++ b/src/LiveComponent/src/LiveComponentHydrator.php
@@ -282,6 +282,10 @@ final class LiveComponentHydrator
                 throw new \LogicException(\sprintf('The "%s::%s" object should be hydrated with the Serializer, but no type could be guessed.', $parentObject::class, $propMetadata->getName()));
             }
 
+            if (null === $value && $propMetadata->allowsNull()) {
+                return null;
+            }
+
             return $this->serializer->denormalize($value, $type, 'json', $propMetadata->serializationContext());
         }
 
diff --git a/src/Toolkit/kits/shadcn/docs/components/Alert.md b/src/Toolkit/kits/shadcn/docs/components/Alert.md
index 53179414..14de9f32 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Alert.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Alert.md
@@ -1,6 +1,6 @@
 # Alert
 
-A notification component that displays important messages with an icon, title, and description.
+Displays a callout for user attention.
 
 ```twig {"preview":true}
 <twig:Alert class="max-w-lg">
@@ -44,4 +44,4 @@ A notification component that displays important messages with an icon, title, a
         Your session has expired. Please log in again.
     </twig:Alert:Description>
 </twig:Alert>
-```
+``` 
diff --git a/src/Toolkit/kits/shadcn/docs/components/AspectRatio.md b/src/Toolkit/kits/shadcn/docs/components/AspectRatio.md
index 4c8e2d32..43128d86 100644
--- a/src/Toolkit/kits/shadcn/docs/components/AspectRatio.md
+++ b/src/Toolkit/kits/shadcn/docs/components/AspectRatio.md
@@ -1,11 +1,11 @@
 # AspectRatio
 
-A container that maintains a specific width-to-height ratio for its content.
+The AspectRatio component is a component that allows you to display an element with a specific aspect ratio.
 
 ```twig {"preview":true,"height":"400px"}
 <twig:AspectRatio ratio="1 / 1" class="max-w-[300px]">
-    <img
-        src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&amp;dpr=2&amp;q=80"
+    <img 
+        src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&amp;dpr=2&amp;q=80" 
         alt="Landscape photograph by Tobias Tullius"
         class="h-full w-full rounded-md object-cover"
     />
@@ -26,8 +26,8 @@ A container that maintains a specific width-to-height ratio for its content.
 
 ```twig {"preview":true,"height":"400px"}
 <twig:AspectRatio ratio="1 / 1" class="max-w-[350px]">
-    <img
-        src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&amp;dpr=2&amp;q=80"
+    <img 
+        src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&amp;dpr=2&amp;q=80" 
         alt="Landscape photograph by Tobias Tullius"
         class="h-full w-full rounded-md object-cover"
     />
@@ -38,8 +38,8 @@ A container that maintains a specific width-to-height ratio for its content.
 
 ```twig {"preview":true,"height":"400px"}
 <twig:AspectRatio ratio="16 / 9" class="max-w-[350px]">
-    <img
-        src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&amp;dpr=2&amp;q=80"
+    <img 
+        src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&amp;dpr=2&amp;q=80" 
         alt="Landscape photograph by Tobias Tullius"
         class="h-full w-full rounded-md object-cover"
     />
diff --git a/src/Toolkit/kits/shadcn/docs/components/Avatar.md b/src/Toolkit/kits/shadcn/docs/components/Avatar.md
index 022831b0..08b085b3 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Avatar.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Avatar.md
@@ -1,6 +1,6 @@
 # Avatar
 
-A circular element that displays a user's profile image or initials as a fallback.
+A component for displaying user profile images with a fallback for when the image is not available.
 
 ```twig {"preview":true}
 <twig:Avatar>
@@ -53,4 +53,4 @@ A circular element that displays a user's profile image or initials as a fallbac
         <twig:Avatar:Text class="bg-red-500 text-red-50">FP</twig:Avatar:Text>
     </twig:Avatar>
 </div>
-```
+``` 
diff --git a/src/Toolkit/kits/shadcn/docs/components/Badge.md b/src/Toolkit/kits/shadcn/docs/components/Badge.md
index 68c13efb..492401d1 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Badge.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Badge.md
@@ -1,6 +1,6 @@
 # Badge
 
-A small element that displays status, counts, or labels with optional icons.
+A component for displaying short pieces of information, such as counts, labels, or status indicators.
 
 ```twig {"preview":true}
 <twig:Badge>Badge</twig:Badge>
@@ -53,4 +53,4 @@ A small element that displays status, counts, or labels with optional icons.
     <twig:ux:icon name="lucide:check" class="mr-1 h-3 w-3" />
     Verified
 </twig:Badge>
-```
+``` 
diff --git a/src/Toolkit/kits/shadcn/docs/components/Breadcrumb.md b/src/Toolkit/kits/shadcn/docs/components/Breadcrumb.md
index 680e7b85..7b720e9a 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Breadcrumb.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Breadcrumb.md
@@ -1,6 +1,6 @@
 # Breadcrumb
 
-A navigation element that shows the current page's location in the site hierarchy with clickable links.
+A navigation component that displays the current page's location within a website's hierarchy.
 
 ```twig {"preview":true}
 <twig:Breadcrumb>
diff --git a/src/Toolkit/kits/shadcn/docs/components/Button.md b/src/Toolkit/kits/shadcn/docs/components/Button.md
index 8dd9530b..82a7c655 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Button.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Button.md
@@ -1,6 +1,6 @@
 # Button
 
-A clickable element that triggers actions or events, supporting various styles and states.
+A button component that can be used to trigger actions or events.
 
 ```twig {"preview":true}
 <twig:Button>
@@ -30,37 +30,37 @@ A clickable element that triggers actions or events, supporting various styles a
 
 ```twig {"preview":true}
 <twig:Button>Button</twig:Button>
-```
+``` 
 
 ### Secondary
 
 ```twig {"preview":true}
 <twig:Button variant="outline">Outline</twig:Button>
-```
+``` 
 
 ### Destructive
 
 ```twig {"preview":true}
 <twig:Button variant="destructive">Destructive</twig:Button>
-```
+``` 
 
 ### Outline
 
 ```twig {"preview":true}
 <twig:Button variant="outline">Outline</twig:Button>
-```
+``` 
 
 ### Ghost
 
 ```twig {"preview":true}
 <twig:Button variant="ghost">Ghost</twig:Button>
-```
+``` 
 
 ### Link
 
 ```twig {"preview":true}
 <twig:Button variant="link">Link</twig:Button>
-```
+``` 
 
 ### Icon
 
@@ -68,7 +68,7 @@ A clickable element that triggers actions or events, supporting various styles a
 <twig:Button variant="outline" size="icon">
     <twig:ux:icon name="lucide:chevron-right" />
 </twig:Button>
-```
+``` 
 
 ### With Icon
 
@@ -76,7 +76,7 @@ A clickable element that triggers actions or events, supporting various styles a
 <twig:Button>
     <twig:ux:icon name="lucide:mail" /> Login with Email
 </twig:Button>
-```
+``` 
 
 ### Loading
 
@@ -84,4 +84,4 @@ A clickable element that triggers actions or events, supporting various styles a
 <twig:Button disabled>
     <twig:ux:icon name="lucide:loader-2" class="animate-spin" /> Please wait
 </twig:Button>
-```
+``` 
diff --git a/src/Toolkit/kits/shadcn/docs/components/Card.md b/src/Toolkit/kits/shadcn/docs/components/Card.md
index f482b75f..3c6bd140 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Card.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Card.md
@@ -1,6 +1,6 @@
 # Card
 
-A container that groups related content and actions into a box with optional header, content, and footer sections.
+A container component for displaying content in a clear, structured format with optional header, content, and footer sections.
 
 ```twig {"preview":true,"height":"300px"}
 <twig:Card class="w-[350px]">
@@ -71,14 +71,14 @@ A container that groups related content and actions into a box with optional hea
                         {{ notification.description }}
                     </p>
                 </div>
-            </div>
+            </div>  
         {%- endfor -%}
     </twig:Card:Content>
     <twig:Card:Footer>
         <twig:Button class="w-full">
-            <twig:ux:icon name="lucide:check" />
+            <twig:ux:icon name="lucide:check" /> 
             Mark all as read
         </twig:Button>
     </twig:Card:Footer>
 </twig:Card>
-```
+``` 
diff --git a/src/Toolkit/kits/shadcn/docs/components/Input.md b/src/Toolkit/kits/shadcn/docs/components/Input.md
index de0b816a..8a01c3c4 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Input.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Input.md
@@ -1,6 +1,6 @@
 # Input
 
-A form control that allows users to enter text, numbers, or select files.
+A form input component for text, email, password, and other input types.
 
 ```twig {"preview":true}
 <twig:Input type="email" placeholder="Email" class="max-w-sm" />
diff --git a/src/Toolkit/kits/shadcn/docs/components/Label.md b/src/Toolkit/kits/shadcn/docs/components/Label.md
index ea41f854..5280ab98 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Label.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Label.md
@@ -1,6 +1,6 @@
 # Label
 
-A text element that identifies form controls and other content.
+A component for labeling form elements and other content.
 
 ```twig {"preview":true}
 <div class="flex items-center space-x-2">
diff --git a/src/Toolkit/kits/shadcn/docs/components/Pagination.md b/src/Toolkit/kits/shadcn/docs/components/Pagination.md
index 0efbea51..71135b10 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Pagination.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Pagination.md
@@ -1,6 +1,6 @@
 # Pagination
 
-A navigation component that displays page numbers and controls for moving between pages.
+A component for navigating through paginated content with page numbers and navigation controls.
 
 ```twig {"preview":true}
 <twig:Pagination>
diff --git a/src/Toolkit/kits/shadcn/docs/components/Progress.md b/src/Toolkit/kits/shadcn/docs/components/Progress.md
index c2c89234..aa769256 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Progress.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Progress.md
@@ -1,6 +1,6 @@
 # Progress
 
-A visual indicator that shows the completion status of a task or operation.
+A component for displaying progress of a task or operation.
 
 ```twig {"preview":true}
 <twig:Progress value="33" />
diff --git a/src/Toolkit/kits/shadcn/docs/components/Select.md b/src/Toolkit/kits/shadcn/docs/components/Select.md
index 91941c0f..7fe10102 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Select.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Select.md
@@ -1,6 +1,6 @@
 # Select
 
-A dropdown control that allows users to choose from a list of options.
+A form component for selecting an option from a dropdown list.
 
 ```twig {"preview":true}
 <twig:Select class="max-w-sm">
diff --git a/src/Toolkit/kits/shadcn/docs/components/Separator.md b/src/Toolkit/kits/shadcn/docs/components/Separator.md
index 038197b4..3117d58b 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Separator.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Separator.md
@@ -1,6 +1,6 @@
 # Separator
 
-A visual divider that creates space between content elements, available in horizontal and vertical orientations.
+A component for creating visual separators between content.
 
 ```twig {"preview":true}
 <div class="max-w-sm">
diff --git a/src/Toolkit/kits/shadcn/docs/components/Skeleton.md b/src/Toolkit/kits/shadcn/docs/components/Skeleton.md
index 4c16a2c6..b01bf5f9 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Skeleton.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Skeleton.md
@@ -1,6 +1,6 @@
 # Skeleton
 
-A placeholder element that displays a loading state with an animated background.
+A component for displaying a loading state with a placeholder animation.
 
 ```twig {"preview":true}
 <div class="flex items-center space-x-4">
diff --git a/src/Toolkit/kits/shadcn/docs/components/Switch.md b/src/Toolkit/kits/shadcn/docs/components/Switch.md
index 17fa899f..e143138a 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Switch.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Switch.md
@@ -1,6 +1,6 @@
 # Switch
 
-A toggle control that switches between on and off states.
+A toggle switch component for boolean input.
 
 ```twig {"preview":true}
 <div class="flex items-center space-x-2">
diff --git a/src/Toolkit/kits/shadcn/docs/components/Table.md b/src/Toolkit/kits/shadcn/docs/components/Table.md
index 302d6bc9..24c8b16c 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Table.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Table.md
@@ -1,6 +1,6 @@
 # Table
 
-A structured grid element that organizes data into rows and columns, supporting headers, captions, and footers.
+A component for displaying structured data in rows and columns with support for headers, captions, and customizable styling.
 
 ```twig {"preview":true,"height":"400px"}
 {%- set invoices = [
@@ -80,4 +80,4 @@ A structured grid element that organizes data into rows and columns, supporting
         </twig:Table:Row>
     </twig:Table:Footer>
 </twig:Table>
-```
+``` 
diff --git a/src/Toolkit/kits/shadcn/docs/components/Textarea.md b/src/Toolkit/kits/shadcn/docs/components/Textarea.md
index b9babc36..bf6a752d 100644
--- a/src/Toolkit/kits/shadcn/docs/components/Textarea.md
+++ b/src/Toolkit/kits/shadcn/docs/components/Textarea.md
@@ -1,6 +1,6 @@
 # Textarea
 
-A form control for entering multiple lines of text.
+A multi-line text input component for longer text content.
 
 ```twig {"preview":true}
 <twig:Textarea placeholder="Type your message here." class="max-w-sm" />


Test patch:
diff --git a/src/LiveComponent/tests/Unit/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Unit/LiveComponentHydratorTest.php
index f14ff4a7..594e8dae 100644
--- a/src/LiveComponent/tests/Unit/LiveComponentHydratorTest.php
+++ b/src/LiveComponent/tests/Unit/LiveComponentHydratorTest.php
@@ -14,8 +14,12 @@ namespace Symfony\UX\LiveComponent\Tests\Unit;
 use PHPUnit\Framework\TestCase;
 use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
 use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
+use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+use Symfony\Component\Serializer\Serializer;
+use Symfony\UX\LiveComponent\Attribute\LiveProp;
 use Symfony\UX\LiveComponent\LiveComponentHydrator;
 use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
+use Symfony\UX\LiveComponent\Metadata\LivePropMetadata;
 
 final class LiveComponentHydratorTest extends TestCase
 {
@@ -32,4 +36,30 @@ final class LiveComponentHydratorTest extends TestCase
             '',
         );
     }
+
+    public function testItCanHydrateWithNullValues()
+    {
+        $hydrator = new LiveComponentHydrator(
+            [],
+            $this->createMock(PropertyAccessorInterface::class),
+            $this->createMock(LiveComponentMetadataFactory::class),
+            new Serializer(normalizers: [new ObjectNormalizer()]),
+            'foo',
+        );
+
+        $hydratedValue = $hydrator->hydrateValue(
+            null,
+            new LivePropMetadata('foo', new LiveProp(useSerializerForHydration: true), typeName: Foo::class, isBuiltIn: false, allowsNull: true, collectionValueType: null),
+            parentObject: new \stdClass() // not relevant in this test
+        );
+
+        self::assertNull($hydratedValue);
+    }
+}
+
+class Foo
+{
+    public function __construct(private int $id)
+    {
+    }
 }