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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lui",
"version": "2.3.0",
"version": "2.3.1",
"description": "web framework",
"homepage": "https://l3p3.de/dok/lui.html",
"repository": {
Expand Down
35 changes: 24 additions & 11 deletions src/lui.js
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ const instance_render = (dom_parent, dom_first) => {
DEBUG &&
thrown !== dom_cache
) throw thrown;
if (instance.dirty) return;
}

const {dom} = instance;
Expand Down Expand Up @@ -1475,10 +1476,15 @@ export const hook_state = initial => {
EXTENDED ? instance_current_get(current_slots_) : current_
), value);

slot[0] !== value && (
slot[0] = value,
EXTENDED ? dirtify_slots(current_slots_) : dirtify_instance(current_)
);
if (slot[0] !== value) {
slot[0] = value;
EXTENDED ? dirtify_slots(current_slots_) : dirtify_instance(current_);
if (
EXTENDED
? current_slots === current_slots_
: current === current_
) throw dom_cache;
}
return value;
},
() => slot[0]
Expand Down Expand Up @@ -1754,7 +1760,8 @@ export const hook_sub = (getter, deps) => {
catch (thrown) {
if (
DEBUG &&
thrown !== dom_cache
thrown !== dom_cache ||
current.dirty
) throw thrown;
}

Expand Down Expand Up @@ -1928,7 +1935,8 @@ export const hook_map = (getter, list_data, deps) => {
catch (thrown) {
if (
DEBUG &&
thrown !== dom_cache
thrown !== dom_cache ||
current.dirty
) throw thrown;
}

Expand Down Expand Up @@ -2063,12 +2071,17 @@ export const hook_model = mutations => {
? callback_wrap(mutations[key], [slot[0], ...args], stack + ' -> #' + key)
: (0, mutations[key])(slot[0], ...args)
);
slot[0] !== value && (
if (slot[0] !== value) {
DEBUG &&
callback_wrap(state_check, [value], stack + ' -> #' + key),
slot[0] = value,
EXTENDED ? dirtify_slots(current_slots_) : dirtify_instance(current_)
);
callback_wrap(state_check, [value], stack + ' -> #' + key);
slot[0] = value;
EXTENDED ? dirtify_slots(current_slots_) : dirtify_instance(current_);
if (
EXTENDED
? current_slots === current_slots_
: current === current_
) throw dom_cache;
}
return value;
};
}
Expand Down
29 changes: 29 additions & 0 deletions test/hook-state.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,32 @@ test('hook_state: can manage component state', () => {

expect(h1.textContent).toBe('Count: 1');
});

test('hook_state: aborts render and re-renders when setter called during render', () => {
const root = root_create();
let render_count = 0;
let after_set_count = 0;

init(() => {
render_count++;
const [value, value_set] = hook_state(false);

if (!value) value_set(true);

after_set_count++;

return [
node_dom('p', {
textContent: String(value),
}),
];
}, root);

// The component should have rendered twice:
// 1st render: value=false, setter called with true → aborts and re-renders
// 2nd render: value=true, setter not called again → stable
expect(render_count).toBe(2);
// Code after the setter was only reached once (the 2nd render, since the 1st aborted)
expect(after_set_count).toBe(1);
expect(root.querySelector('p').textContent).toBe('true');
});