Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/strange-peas-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@lit/react': patch
---

Wrapped components will now keep track of JSX props from previous render that were set as a property on the element, but are now missing, and set the property to `undefined`. Note, wrapped components still do not have "default props" and missing props will be treated the same as explicitly passing in `undefined`.

This fixes the previously unexpected behavior where the following JSX when first rendered with a truthy condition

```jsx
return condition ? <WrappedInput disabled /> : <WrappedInput />;
```

would leave the `disabled` property and reflected attribute to be `true` even when the condition turns falsey.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@
"./packages/labs/gen-wrapper-vue:test",
"./packages/labs/nextjs:test",
"./packages/labs/preact-signals:test",
"./packages/labs/react:test",
Copy link
Member Author

@augustjk augustjk Feb 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a band-aid. I had to remove it because there's a copy of tests in packages/labs/react that are now stale and fail. I don't think it makes sense to update those files, and I don't think there's reason to keep things like tests and rollup builds in the labs directories for graduated packages.

Copy link
Contributor

@AndrewJakubowicz AndrewJakubowicz Feb 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree. Are there files to delete?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are. I think they can be done as a separate chore task. Nothing super pressing.

"./packages/labs/rollup-plugin-minify-html-literals:test",
"./packages/labs/ssr:test",
"./packages/labs/ssr-dom-shim:test",
Expand Down
26 changes: 16 additions & 10 deletions packages/react/src/create-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export const createComponent = <
type Props = ComponentProps<I, E>;

const ReactComponent = React.forwardRef<I, Props>((props, ref) => {
const prevPropsRef = React.useRef<Props | null>(null);
const prevElemPropsRef = React.useRef(new Map());
const elementRef = React.useRef<I | null>(null);

// Props to be passed to React.createElement
Expand Down Expand Up @@ -274,20 +274,26 @@ export const createComponent = <
if (elementRef.current === null) {
return;
}
for (const prop in elementProps) {
const newElemProps = new Map();
for (const key in elementProps) {
setProperty(
elementRef.current,
prop,
props[prop],
prevPropsRef.current ? prevPropsRef.current[prop] : undefined,
key,
props[key],
prevElemPropsRef.current.get(key),
events
);
prevElemPropsRef.current.delete(key);
newElemProps.set(key, props[key]);
}
// Note, the spirit of React might be to "unset" any old values that
// are no longer included; however, there's no reasonable value to set
// them to so we just leave the previous state as is.

prevPropsRef.current = props;
// "Unset" any props from previous render that no longer exist.
// Setting to `undefined` seems like the correct thing to "unset"
// but currently React will set it as `null`.
// See https://github.com/facebook/react/issues/28203
for (const [key, value] of prevElemPropsRef.current) {
setProperty(elementRef.current, key, undefined, value, events);
}
prevElemPropsRef.current = newElemProps;
});

// Empty dependency array so this will only run once after first render.
Expand Down
17 changes: 6 additions & 11 deletions packages/react/src/test/create-component_test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,7 @@ suite('createComponent', () => {
assert.equal(el.getAttribute('id'), 'foo');
assert.equal(el.id, 'foo');

// We currently require passing `undefined` to "unset" rather than
// omitting the prop
// See https://github.com/lit/lit/issues/4227
render(<Tag id={undefined} />);
render(<Tag />);
assert.equal(el.getAttribute('id'), null);
assert.equal(el.id, '');

Expand Down Expand Up @@ -383,7 +380,7 @@ suite('createComponent', () => {
assert.equal(el.getAttribute('hidden'), '');
assert.equal(el.hidden, true);

render(<Tag hidden={undefined} />);
render(<Tag />);
assert.equal(el.getAttribute('hidden'), null);
assert.equal(el.hidden, false);

Expand Down Expand Up @@ -419,7 +416,7 @@ suite('createComponent', () => {
assert.equal(el.getAttribute('draggable'), 'true');
assert.equal(el.draggable, true);

render(<Tag draggable={undefined} />);
render(<Tag />);
assert.equal(el.getAttribute('draggable'), null);
assert.equal(el.draggable, false);

Expand Down Expand Up @@ -457,7 +454,7 @@ suite('createComponent', () => {
render(<Tag aria-checked />);
assert.equal(el.getAttribute('aria-checked'), 'true');

render(<Tag aria-checked={undefined} />);
render(<Tag />);
assert.equal(el.getAttribute('aria-checked'), null);
});
}
Expand Down Expand Up @@ -486,21 +483,19 @@ suite('createComponent', () => {
barEvent = undefined;

// Clear listener
// Explicitly setting `undefined` or omitting prop will clear listeners
render(<BasicElementComponent onFoo={undefined} />);
el.fire('foo');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line above this can be update to either no have the explicit undefined, or the comment above it can mention that both explicit undefined or omitting property will clear listener.

assert.equal(fooEvent, undefined);
el.fire('bar');
// We do not clear event listeners unless undefined is explicitly passed in
assert.equal(barEvent!.type, 'bar');
assert.equal(barEvent, undefined);
fooEvent = undefined;
barEvent = undefined;

// Reattach listener
render(<BasicElementComponent onFoo={onFoo} />);
el.fire('foo');
assert.equal(fooEvent!.type, 'foo');
el.fire('bar');
assert.equal(barEvent!.type, 'bar');

// Replace listener
render(<BasicElementComponent onFoo={onFoo2} />);
Expand Down