{"record":{"id":"ab8eb4c660b0a335","repo":"livewire/livewire","slug":"you-must-define-a-hydrate-method","errorCode":null,"errorMessage":"You must define a \"hydrate\" method","messagePattern":"You must define a \"hydrate\" method","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/Mechanisms/HandleComponents/Synthesizers/Synth.php","lineNumber":43,"sourceCode":"    {\n        return false;\n    }\n\n    function get(&$target, $key) {\n        if (is_array($target)) {\n            return $target[$key] ?? null;\n        }\n\n        return $target->$key;\n    }\n\n    function __call($method, $params) {\n        if ($method === 'dehydrate') {\n            throw new \\Exception('You must define a \"dehydrate\" method');\n        }\n\n        if ($method === 'hydrate') {\n            throw new \\Exception('You must define a \"hydrate\" method');\n        }\n\n        if ($method === 'hydrateFromType') {\n            throw new \\Exception('You must define a \"hydrateFromType\" method');\n        }\n\n        if ($method === 'get') {\n            throw new \\Exception('This synth doesn\\'t support getting properties: '.get_class($this));\n        }\n\n        if ($method === 'set') {\n            throw new \\Exception('This synth doesn\\'t support setting properties: '.get_class($this));\n        }\n\n        if ($method === 'unset') {\n            throw new \\Exception('This synth doesn\\'t support unsetting properties: '.get_class($this));\n        }\n","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/livewire/livewire/blob/1a3fde34c5789366411c450cf4e630356f18b584/src/Mechanisms/HandleComponents/Synthesizers/Synth.php#L25-L61","documentation":"The abstract Synth base class marks only match() as abstract - hydrate() is not defined, so a subclass that omits it gets the call routed through Synth::__call(), which throws on the first hydration. This happens on the request after render: dehydration succeeds, then the snapshot comes back and the synth cannot rebuild the value. It is a developer-error guard against half-implemented synthesizers.","triggerScenarios":"A registered custom synth defines $key, match() and dehydrate() but no hydrate(); the component renders fine, and the very next update request (any action or wire:model commit) throws when Livewire tries to reconstitute the property from the snapshot.","commonSituations":"Synths written and tested only against the initial render path; refactors that drop the method; copying a dehydrate-only example from docs or an AI suggestion.","solutions":["Define hydrate($value, $meta, $hydrateChild) on the synth that reconstructs the target from the dehydrated data","If you only need serialization to the client, switch to a one-way approach (computed property or toArray in dehydrate) instead of a synth","Add a CI test that performs a full round trip (render then update) for components using each custom synth"],"exampleFix":"// before - render works, but the next request cannot rebuild the value\nclass CurrencySynth extends Synth {\n    public static $key = 'cur';\n    static function match($target) { return $target instanceof Currency; }\n    function dehydrate($target, $dehydrateChild) {\n        return [['amount' => $target->amount, 'code' => $target->code], []];\n    }\n}\n\n// after - hydration added\nclass CurrencySynth extends Synth {\n    public static $key = 'cur';\n    static function match($target) { return $target instanceof Currency; }\n    function dehydrate($target, $dehydrateChild) {\n        return [['amount' => $target->amount, 'code' => $target->code], []];\n    }\n    function hydrate($value, $meta, $hydrateChild) {\n        return new Currency($value['amount'], $value['code']);\n    }\n}","handlingStrategy":"validation","validationCode":"foreach ([CurrencySynth::class /* ... */] as $synthClass) {\n    foreach (['dehydrate', 'hydrate'] as $method) {\n        if (! method_exists($synthClass, $method)) {\n            throw new \\LogicException(\"{$synthClass} lacks {$method}() and will throw on the round trip\");\n        }\n    }\n}","typeGuard":"function synthCanHydrate(string $synthClass): bool\n{\n    return method_exists($synthClass, 'hydrate');\n}","tryCatchPattern":"try {\n    Livewire::test(ComponentUsingCustomSynth::class)\n        ->call('someAction'); // triggers hydration from the snapshot\n} catch (\\Exception $e) {\n    if (str_contains($e->getMessage(), 'must define a \"hydrate\" method')) {\n        // add the missing hydrate($value, $meta, $hydrateChild) to the custom synth\n    }\n    throw $e;\n}","preventionTips":["Test the full round trip (render then call/update), not just the first render","Implement dehydrate and hydrate in the same commit when writing a synth","If a value only needs to be sent to the client, prefer a computed property over a synth"],"tags":["livewire","synthesizer","custom-synth","hydrate"],"backgroundTag":"abstract-method-not-implemented","analyzedSha":"1a3fde34c5789366411c450cf4e630356f18b584","analyzedAt":"2026-08-17T02:02:20.412Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}