Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix setValueOnMove state on Select not syncing between connected stores #2858

Merged
merged 2 commits into from
Sep 21, 2023
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
15 changes: 15 additions & 0 deletions .changeset/2858-select-sync-set-value-on-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@ariakit/react-core": patch
"@ariakit/react": patch
---

Fixed the [`setValueOnMove`](https://ariakit.org/reference/use-select-store#setvalueonmove) state on the [Select](https://ariakit.org/components/select) module not syncing between multiple stores.

The following now works as expected:

```js
const store1 = useSelectStore();
const store2 = useSelectStore({ store: store1, setValueOnMove: true });

store1.useState("setValueOnMove") === store2.useState("setValueOnMove"); // true
```
19 changes: 10 additions & 9 deletions examples/select-autofill/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import * as Ariakit from "@ariakit/react";
import "./style.css";
import * as Ariakit from "@ariakit/react";

export default function Example() {
const select = Ariakit.useSelectStore({ defaultValue: "Student" });
return (
<form className="wrapper">
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" className="input" />
<Ariakit.SelectLabel store={select}>Role</Ariakit.SelectLabel>
<Ariakit.Select store={select} name="role" className="button" />
<Ariakit.SelectPopover store={select} sameWidth className="popover">
<Ariakit.SelectItem className="select-item" value="Student" />
<Ariakit.SelectItem className="select-item" value="Tutor" />
<Ariakit.SelectItem className="select-item" value="Parent" />
</Ariakit.SelectPopover>
<Ariakit.SelectProvider defaultValue="Student">
<Ariakit.SelectLabel>Role</Ariakit.SelectLabel>
<Ariakit.Select name="role" className="button" />
<Ariakit.SelectPopover sameWidth className="popover">
<Ariakit.SelectItem className="select-item" value="Student" />
<Ariakit.SelectItem className="select-item" value="Tutor" />
<Ariakit.SelectItem className="select-item" value="Parent" />
</Ariakit.SelectPopover>
</Ariakit.SelectProvider>
</form>
);
}
51 changes: 51 additions & 0 deletions examples/select-combobox-store/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import "./style.css";
import { useDeferredValue, useMemo } from "react";
import * as Ariakit from "@ariakit/react";
import { matchSorter } from "match-sorter";
import list from "../select-combobox/list.js";

export default function Example() {
const combobox = Ariakit.useComboboxStore({ resetValueOnHide: true });
const select = Ariakit.useSelectStore({ combobox, defaultValue: "Apple" });

const value = combobox.useState("value");
const deferredValue = useDeferredValue(value);

const matches = useMemo(() => {
return matchSorter(list, deferredValue, {
baseSort: (a, b) => (a.index < b.index ? -1 : 1),
});
}, [deferredValue]);

return (
<div className="wrapper">
<Ariakit.SelectLabel store={select}>Favorite fruit</Ariakit.SelectLabel>
<Ariakit.Select store={select} className="button" />
<Ariakit.SelectPopover
store={select}
gutter={4}
sameWidth
className="popover"
>
<div className="combobox-wrapper">
<Ariakit.Combobox
store={combobox}
autoSelect
placeholder="Search..."
className="combobox"
/>
</div>
<Ariakit.ComboboxList store={combobox}>
{matches.map((value) => (
<Ariakit.ComboboxItem
key={value}
focusOnHover
className="select-item"
render={<Ariakit.SelectItem value={value} />}
/>
))}
</Ariakit.ComboboxList>
</Ariakit.SelectPopover>
</div>
);
}
1 change: 1 addition & 0 deletions examples/select-combobox-store/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import url("../select-combobox/style.css");
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async function expectSelected(page: Page, name: string) {
}

test.beforeEach(async ({ page }) => {
await page.goto("/previews/select-combobox");
await page.goto("/previews/select-combobox-store");
});

test("auto select first option", async ({ page }) => {
Expand Down
1 change: 1 addition & 0 deletions examples/select-combobox-store/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "../select-combobox/test.js";
69 changes: 35 additions & 34 deletions examples/select-combobox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,52 @@
import "./style.css";
import { useDeferredValue, useMemo } from "react";
import { startTransition, useMemo, useState } from "react";
import * as Ariakit from "@ariakit/react";
import { matchSorter } from "match-sorter";
import list from "./list.js";

export default function Example() {
const combobox = Ariakit.useComboboxStore({ resetValueOnHide: true });
const select = Ariakit.useSelectStore({ combobox, defaultValue: "Apple" });

const value = combobox.useState("value");
const deferredValue = useDeferredValue(value);
const [searchValue, setSearchValue] = useState("");

const matches = useMemo(() => {
return matchSorter(list, deferredValue, {
return matchSorter(list, searchValue, {
baseSort: (a, b) => (a.index < b.index ? -1 : 1),
});
}, [deferredValue]);
}, [searchValue]);

return (
<div className="wrapper">
<Ariakit.SelectLabel store={select}>Favorite fruit</Ariakit.SelectLabel>
<Ariakit.Select store={select} className="button" />
<Ariakit.SelectPopover
store={select}
gutter={4}
sameWidth
className="popover"
<Ariakit.ComboboxProvider
resetValueOnHide
setValue={(value) => {
startTransition(() => {
setSearchValue(value);
});
}}
>
<div className="combobox-wrapper">
<Ariakit.Combobox
store={combobox}
autoSelect
placeholder="Search..."
className="combobox"
/>
</div>
<Ariakit.ComboboxList store={combobox}>
{matches.map((value) => (
<Ariakit.ComboboxItem
key={value}
focusOnHover
className="select-item"
render={<Ariakit.SelectItem value={value} />}
/>
))}
</Ariakit.ComboboxList>
</Ariakit.SelectPopover>
<Ariakit.SelectProvider defaultValue="Apple">
<Ariakit.SelectLabel>Favorite fruit</Ariakit.SelectLabel>
<Ariakit.Select className="button" />
<Ariakit.SelectPopover gutter={4} sameWidth className="popover">
<div className="combobox-wrapper">
<Ariakit.Combobox
autoSelect
placeholder="Search..."
className="combobox"
/>
</div>
<Ariakit.ComboboxList>
{matches.map((value) => (
<Ariakit.SelectItem
key={value}
value={value}
className="select-item"
render={<Ariakit.ComboboxItem />}
/>
))}
</Ariakit.ComboboxList>
</Ariakit.SelectPopover>
</Ariakit.SelectProvider>
</Ariakit.ComboboxProvider>
</div>
);
}
2 changes: 1 addition & 1 deletion examples/select-combobox/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ tags:

<div data-description>

Combining <a href="/components/select">Select</a> and <a href="/components/combobox">Combobox</a> to create a dropdown with a search field that can be used to filter items.
Combining [Select](/components/select) and [Combobox](/components/combobox) to create a dropdown with a search field that can be used to filter items.

</div>

Expand Down
60 changes: 60 additions & 0 deletions examples/select-grid-store/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as Ariakit from "@ariakit/react";
import Square from "../select-grid/square.jsx";
import "./style.css";

export default function Example() {
const select = Ariakit.useSelectStore({
defaultValue: "Center",
placement: "bottom",
setValueOnMove: true,
});
const value = select.useState("value");

const renderItem = (value: string) => (
<Ariakit.SelectItem
value={value}
className="select-item"
focusOnHover={(event) => {
// When the mouse leaves the item, we don't want to unset the active
// item.
if (event.type === "mouseleave") return false;
// By default, hovering over an item doesn't focus it, nor does it set
// the value. So we need to manually "move" to the item so it gets
// focused and the value is set.
select.move(event.currentTarget.id);
return true;
}}
>
<Ariakit.VisuallyHidden>{value}</Ariakit.VisuallyHidden>
</Ariakit.SelectItem>
);

return (
<div className="wrapper">
<Ariakit.SelectLabel store={select}>Position</Ariakit.SelectLabel>
<Ariakit.Select store={select} showOnKeyDown={false} className="button">
<Square value={value} />
{value}
<Ariakit.SelectArrow />
</Ariakit.Select>
<Ariakit.SelectPopover store={select} role="grid" className="popover">
<Ariakit.PopoverArrow className="arrow" />
<Ariakit.SelectRow className="row">
{renderItem("Top Left")}
{renderItem("Top Center")}
{renderItem("Top Right")}
</Ariakit.SelectRow>
<Ariakit.SelectRow className="row">
{renderItem("Center Left")}
{renderItem("Center")}
{renderItem("Center Right")}
</Ariakit.SelectRow>
<Ariakit.SelectRow className="row">
{renderItem("Bottom Left")}
{renderItem("Bottom Center")}
{renderItem("Bottom Right")}
</Ariakit.SelectRow>
</Ariakit.SelectPopover>
</div>
);
}
1 change: 1 addition & 0 deletions examples/select-grid-store/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import url("../select-grid/style.css");
1 change: 1 addition & 0 deletions examples/select-grid-store/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "../select-grid/test.js";
61 changes: 30 additions & 31 deletions examples/select-grid/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import "./style.css";
import { useState } from "react";
import * as Ariakit from "@ariakit/react";
import Square from "./square.jsx";
import "./style.css";

export default function Example() {
const select = Ariakit.useSelectStore({
defaultValue: "Center",
placement: "bottom",
setValueOnMove: true,
});
const value = select.useState("value");
const [value, setValue] = useState("Center");
const select = Ariakit.useSelectStore({ value, setValue });

const renderItem = (value: string) => (
<Ariakit.SelectItem
Expand All @@ -31,30 +28,32 @@ export default function Example() {

return (
<div className="wrapper">
<Ariakit.SelectLabel store={select}>Position</Ariakit.SelectLabel>
<Ariakit.Select store={select} showOnKeyDown={false} className="button">
<Square value={value} />
{value}
<Ariakit.SelectArrow />
</Ariakit.Select>
<Ariakit.SelectPopover store={select} role="grid" className="popover">
<Ariakit.PopoverArrow className="arrow" />
<Ariakit.SelectRow className="row">
{renderItem("Top Left")}
{renderItem("Top Center")}
{renderItem("Top Right")}
</Ariakit.SelectRow>
<Ariakit.SelectRow className="row">
{renderItem("Center Left")}
{renderItem("Center")}
{renderItem("Center Right")}
</Ariakit.SelectRow>
<Ariakit.SelectRow className="row">
{renderItem("Bottom Left")}
{renderItem("Bottom Center")}
{renderItem("Bottom Right")}
</Ariakit.SelectRow>
</Ariakit.SelectPopover>
<Ariakit.SelectProvider store={select} placement="bottom" setValueOnMove>
<Ariakit.SelectLabel>Position</Ariakit.SelectLabel>
<Ariakit.Select showOnKeyDown={false} className="button">
<Square value={value} />
{value}
<Ariakit.SelectArrow />
</Ariakit.Select>
<Ariakit.SelectPopover role="grid" className="popover">
<Ariakit.PopoverArrow className="arrow" />
<Ariakit.SelectRow className="row">
{renderItem("Top Left")}
{renderItem("Top Center")}
{renderItem("Top Right")}
</Ariakit.SelectRow>
<Ariakit.SelectRow className="row">
{renderItem("Center Left")}
{renderItem("Center")}
{renderItem("Center Right")}
</Ariakit.SelectRow>
<Ariakit.SelectRow className="row">
{renderItem("Bottom Left")}
{renderItem("Bottom Center")}
{renderItem("Bottom Right")}
</Ariakit.SelectRow>
</Ariakit.SelectPopover>
</Ariakit.SelectProvider>
</div>
);
}
Loading
Loading