Skip to content

Commit cc85e05

Browse files
bug #743 [Live] Fix nested props persisting (weaverryan)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [Live] Fix nested props persisting | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Tickets | None | License | MIT A fix to the new, unreleased hydration system upgrade. Previously, if a nested value changed, we sent that value via Ajax on `updated` (e.g. `product.name: 'foo'`), but the that new value was not part of the `props` returned back. And so, on the next request, the new value (`foo`) was missing. Fortunately, this clarified that the system needed to be a bit simpler. Cheers! Commits ------- f36514fd [Live] Fix nested props persisting
2 parents ea2f349 + 0d07b04 commit cc85e05

21 files changed

+187
-247
lines changed

src/LiveComponent/assets/dist/Component/ElementDriver.d.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
export interface ElementDriver {
22
getModelName(element: HTMLElement): string | null;
3-
getComponentProps(rootElement: HTMLElement): {
4-
props: any;
5-
nestedProps: any;
6-
};
3+
getComponentProps(rootElement: HTMLElement): any;
74
findChildComponentElement(id: string, element: HTMLElement): HTMLElement | null;
85
getKeyFromElement(element: HTMLElement): string | null;
96
}
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
export default class {
22
private props;
3-
private nestedProps;
43
private dirtyProps;
54
private pendingProps;
6-
constructor(props: any, nestedProps: any);
5+
constructor(props: any);
76
get(name: string): any;
87
has(name: string): boolean;
98
set(name: string, value: any): boolean;
109
getOriginalProps(): any;
11-
getOriginalNestedProps(): any;
1210
getDirtyProps(): any;
1311
flushDirtyPropsToPending(): void;
14-
reinitializeAllProps(props: any, nestedProps: any): void;
12+
reinitializeAllProps(props: any): void;
1513
pushPendingPropsBackToDirty(): void;
1614
reinitializeProvidedProps(props: any): boolean;
1715
}

src/LiveComponent/assets/dist/Component/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default class Component {
2323
private children;
2424
private parent;
2525
private externalMutationTracker;
26-
constructor(element: HTMLElement, props: any, nestedProps: any, fingerprint: string | null, id: string | null, backend: BackendInterface, elementDriver: ElementDriver);
26+
constructor(element: HTMLElement, props: any, fingerprint: string | null, id: string | null, backend: BackendInterface, elementDriver: ElementDriver);
2727
_swapBackend(backend: BackendInterface): void;
2828
addPlugin(plugin: PluginInterface): void;
2929
connect(): void;

src/LiveComponent/assets/dist/live_controller.d.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
1616
static values: {
1717
url: StringConstructor;
1818
props: ObjectConstructor;
19-
nestedProps: {
20-
type: ObjectConstructor;
21-
default: {};
22-
};
2319
csrf: StringConstructor;
2420
debounce: {
2521
type: NumberConstructor;
@@ -30,7 +26,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
3026
};
3127
readonly urlValue: string;
3228
readonly propsValue: any;
33-
readonly nestedPropsValue: any;
3429
readonly csrfValue: string;
3530
readonly hasDebounceValue: boolean;
3631
readonly debounceValue: number;

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -354,13 +354,11 @@ const parseDeepData = function (data, propertyPath) {
354354
};
355355

356356
class ValueStore {
357-
constructor(props, nestedProps) {
357+
constructor(props) {
358358
this.props = {};
359-
this.nestedProps = {};
360359
this.dirtyProps = {};
361360
this.pendingProps = {};
362361
this.props = props;
363-
this.nestedProps = nestedProps;
364362
}
365363
get(name) {
366364
const normalizedName = normalizeModelName(name);
@@ -370,8 +368,8 @@ class ValueStore {
370368
if (this.pendingProps[normalizedName] !== undefined) {
371369
return this.pendingProps[normalizedName];
372370
}
373-
if (this.nestedProps[normalizedName] !== undefined) {
374-
return this.nestedProps[normalizedName];
371+
if (this.props[normalizedName] !== undefined) {
372+
return this.props[normalizedName];
375373
}
376374
return getDeepData(this.props, normalizedName);
377375
}
@@ -390,19 +388,15 @@ class ValueStore {
390388
getOriginalProps() {
391389
return Object.assign({}, this.props);
392390
}
393-
getOriginalNestedProps() {
394-
return Object.assign({}, this.nestedProps);
395-
}
396391
getDirtyProps() {
397392
return Object.assign({}, this.dirtyProps);
398393
}
399394
flushDirtyPropsToPending() {
400395
this.pendingProps = Object.assign({}, this.dirtyProps);
401396
this.dirtyProps = {};
402397
}
403-
reinitializeAllProps(props, nestedProps) {
398+
reinitializeAllProps(props) {
404399
this.props = props;
405-
this.nestedProps = nestedProps;
406400
this.pendingProps = {};
407401
}
408402
pushPendingPropsBackToDirty() {
@@ -1727,7 +1721,7 @@ class ChildComponentWrapper {
17271721
}
17281722
}
17291723
class Component {
1730-
constructor(element, props, nestedProps, fingerprint, id, backend, elementDriver) {
1724+
constructor(element, props, fingerprint, id, backend, elementDriver) {
17311725
this.defaultDebounce = 150;
17321726
this.backendRequest = null;
17331727
this.pendingActions = [];
@@ -1740,7 +1734,7 @@ class Component {
17401734
this.elementDriver = elementDriver;
17411735
this.id = id;
17421736
this.fingerprint = fingerprint;
1743-
this.valueStore = new ValueStore(props, nestedProps);
1737+
this.valueStore = new ValueStore(props);
17441738
this.unsyncedInputsTracker = new UnsyncedInputsTracker(this, elementDriver);
17451739
this.hooks = new HookManager();
17461740
this.resetPromise();
@@ -1833,7 +1827,7 @@ class Component {
18331827
return children;
18341828
}
18351829
updateFromNewElement(toEl) {
1836-
const { props } = this.elementDriver.getComponentProps(toEl);
1830+
const props = this.elementDriver.getComponentProps(toEl);
18371831
if (props === null) {
18381832
return false;
18391833
}
@@ -1934,8 +1928,8 @@ class Component {
19341928
console.error('There was a problem with the component HTML returned:');
19351929
throw error;
19361930
}
1937-
const { props: newProps, nestedProps: newNestedProps } = this.elementDriver.getComponentProps(newElement);
1938-
this.valueStore.reinitializeAllProps(newProps, newNestedProps);
1931+
const newProps = this.elementDriver.getComponentProps(newElement);
1932+
this.valueStore.reinitializeAllProps(newProps);
19391933
this.externalMutationTracker.handlePendingChanges();
19401934
this.externalMutationTracker.stop();
19411935
executeMorphdom(this.element, newElement, this.unsyncedInputsTracker.getUnsyncedInputs(), (element) => getValueFromElement(element, this.valueStore), Array.from(this.getChildren().values()), this.elementDriver.findChildComponentElement, this.elementDriver.getKeyFromElement, this.externalMutationTracker);
@@ -2154,13 +2148,9 @@ class StandardElementDriver {
21542148
return modelDirective.action;
21552149
}
21562150
getComponentProps(rootElement) {
2157-
var _a, _b;
2151+
var _a;
21582152
const propsJson = (_a = rootElement.dataset.livePropsValue) !== null && _a !== void 0 ? _a : '{}';
2159-
const nestedPropsJson = (_b = rootElement.dataset.liveNestedPropsValue) !== null && _b !== void 0 ? _b : '{}';
2160-
return {
2161-
props: JSON.parse(propsJson),
2162-
nestedProps: JSON.parse(nestedPropsJson),
2163-
};
2153+
return JSON.parse(propsJson);
21642154
}
21652155
findChildComponentElement(id, element) {
21662156
return element.querySelector(`[data-live-id=${id}]`);
@@ -2585,7 +2575,7 @@ class LiveControllerDefault extends Controller {
25852575
initialize() {
25862576
this.handleDisconnectedChildControllerEvent = this.handleDisconnectedChildControllerEvent.bind(this);
25872577
const id = this.element.dataset.liveId || null;
2588-
this.component = new Component(this.element, this.propsValue, this.nestedPropsValue, this.fingerprintValue, id, new Backend(this.urlValue, this.csrfValue), new StandardElementDriver());
2578+
this.component = new Component(this.element, this.propsValue, this.fingerprintValue, id, new Backend(this.urlValue, this.csrfValue), new StandardElementDriver());
25892579
this.proxiedComponent = proxifyComponent(this.component);
25902580
this.element.__component = this.proxiedComponent;
25912581
if (this.hasDebounceValue) {
@@ -2744,7 +2734,6 @@ class LiveControllerDefault extends Controller {
27442734
LiveControllerDefault.values = {
27452735
url: String,
27462736
props: Object,
2747-
nestedProps: { type: Object, default: {} },
27482737
csrf: String,
27492738
debounce: { type: Number, default: 150 },
27502739
id: String,

src/LiveComponent/assets/src/Component/ElementDriver.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {getModelDirectiveFromElement} from '../dom_utils';
33
export interface ElementDriver {
44
getModelName(element: HTMLElement): string|null;
55

6-
getComponentProps(rootElement: HTMLElement): { props: any, nestedProps: any };
6+
getComponentProps(rootElement: HTMLElement): any;
77

88
/**
99
* Given an HtmlElement and a child id, find the root element for that child.
@@ -29,12 +29,8 @@ export class StandardElementDriver implements ElementDriver {
2929

3030
getComponentProps(rootElement: HTMLElement): any {
3131
const propsJson = rootElement.dataset.livePropsValue ?? '{}';
32-
const nestedPropsJson = rootElement.dataset.liveNestedPropsValue ?? '{}';
3332

34-
return {
35-
props: JSON.parse(propsJson),
36-
nestedProps: JSON.parse(nestedPropsJson),
37-
}
33+
return JSON.parse(propsJson);
3834
}
3935

4036
findChildComponentElement(id: string, element: HTMLElement): HTMLElement|null {

src/LiveComponent/assets/src/Component/ValueStore.ts

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@ export default class {
99
*/
1010
private props: any = {};
1111

12-
/**
13-
* A list of extra, nested props added to make them available as models.
14-
*
15-
* @private
16-
*/
17-
private nestedProps: any = {};
18-
1912
/**
2013
* A list of props that have been "dirty" (changed) since the last request to the server.
2114
*/
@@ -27,9 +20,8 @@ export default class {
2720
*/
2821
private pendingProps: {[key: string]: any} = {};
2922

30-
constructor(props: any, nestedProps: any) {
23+
constructor(props: any) {
3124
this.props = props;
32-
this.nestedProps = nestedProps;
3325
}
3426

3527
/**
@@ -50,8 +42,8 @@ export default class {
5042
return this.pendingProps[normalizedName];
5143
}
5244

53-
if (this.nestedProps[normalizedName] !== undefined) {
54-
return this.nestedProps[normalizedName];
45+
if (this.props[normalizedName] !== undefined) {
46+
return this.props[normalizedName];
5547
}
5648

5749
return getDeepData(this.props, normalizedName);
@@ -86,10 +78,6 @@ export default class {
8678
return { ...this.props };
8779
}
8880

89-
getOriginalNestedProps(): any {
90-
return { ...this.nestedProps };
91-
}
92-
9381
getDirtyProps(): any {
9482
return { ...this.dirtyProps };
9583
}
@@ -105,9 +93,8 @@ export default class {
10593
/**
10694
* Called when an update request finishes successfully.
10795
*/
108-
reinitializeAllProps(props: any, nestedProps: any): void {
96+
reinitializeAllProps(props: any): void {
10997
this.props = props;
110-
this.nestedProps = nestedProps;
11198
this.pendingProps = {};
11299
}
113100

src/LiveComponent/assets/src/Component/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,19 @@ export default class Component {
6464
/**
6565
* @param element The root element
6666
* @param props Readonly component props
67-
* @param nestedProps Extra nested prop values that can be used as models
6867
* @param fingerprint
6968
* @param id Some unique id to identify this component. Needed to be a child component
7069
* @param backend Backend instance for updating
7170
* @param elementDriver Class to get "model" name from any element.
7271
*/
73-
constructor(element: HTMLElement, props: any, nestedProps: any, fingerprint: string|null, id: string|null, backend: BackendInterface, elementDriver: ElementDriver) {
72+
constructor(element: HTMLElement, props: any, fingerprint: string|null, id: string|null, backend: BackendInterface, elementDriver: ElementDriver) {
7473
this.element = element;
7574
this.backend = backend;
7675
this.elementDriver = elementDriver;
7776
this.id = id;
7877
this.fingerprint = fingerprint;
7978

80-
this.valueStore = new ValueStore(props, nestedProps);
79+
this.valueStore = new ValueStore(props);
8180
this.unsyncedInputsTracker = new UnsyncedInputsTracker(this, elementDriver);
8281
this.hooks = new HookManager();
8382
this.resetPromise();
@@ -230,7 +229,7 @@ export default class Component {
230229
* @param toEl
231230
*/
232231
updateFromNewElement(toEl: HTMLElement): boolean {
233-
const { props } = this.elementDriver.getComponentProps(toEl);
232+
const props = this.elementDriver.getComponentProps(toEl);
234233

235234
// if no props are on the element, use the existing element completely
236235
// this means the parent is signaling that the child does not need to be re-rendered
@@ -399,8 +398,8 @@ export default class Component {
399398
throw error;
400399
}
401400

402-
const { props: newProps, nestedProps: newNestedProps } = this.elementDriver.getComponentProps(newElement);
403-
this.valueStore.reinitializeAllProps(newProps, newNestedProps);
401+
const newProps = this.elementDriver.getComponentProps(newElement);
402+
this.valueStore.reinitializeAllProps(newProps);
404403

405404
// make sure we've processed all external changes before morphing
406405
this.externalMutationTracker.handlePendingChanges();

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
3737
static values = {
3838
url: String,
3939
props: Object,
40-
nestedProps: { type: Object, default: {} },
4140
csrf: String,
4241
debounce: { type: Number, default: 150 },
4342
id: String,
@@ -46,7 +45,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
4645

4746
declare readonly urlValue: string;
4847
declare readonly propsValue: any;
49-
declare readonly nestedPropsValue: any;
5048
declare readonly csrfValue: string;
5149
declare readonly hasDebounceValue: boolean;
5250
declare readonly debounceValue: number;
@@ -72,7 +70,6 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
7270
this.component = new Component(
7371
this.element,
7472
this.propsValue,
75-
this.nestedPropsValue,
7673
this.fingerprintValue,
7774
id,
7875
new Backend(this.urlValue, this.csrfValue),

src/LiveComponent/assets/test/Component/index.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ const makeTestComponent = (): { component: Component, backend: MockBackend } =>
2828
const component = new Component(
2929
document.createElement('div'),
3030
{ firstName: '' },
31-
{},
3231
null,
3332
null,
3433
backend,

0 commit comments

Comments
 (0)