I believe nextTab should return one type, not a union. In current representation, it may return an object or undefined.
Since we don't have Maybe/Option monad data structure, I believe it worth returning just index: -1 ... 4.
So nextTab will always return a number. I think it will be easier to compose.
type Tab = {
text: string;
selectable: boolean;
selected: boolean;
}
const tabs = [
{ text: 'foo', selectable: false, selected: false },
{ text: 'bar', selectable: true, selected: true },
{ text: 'baz', selectable: false, selected: false },
{ text: 'hello', selectable: false, selected: false },
{ text: 'world', selectable: true, selected: false },
];
const selected = (tabs: Tab[]) => tabs.findIndex(t => t.selected);
const find = (tabs: Tab[], selectedIndex: number) =>
tabs.reduce((acc, elem, index) =>
index > selectedIndex && elem.selectable ? index : acc,
-1
)
const nextTabIndex = (tabs: Tab[]) => find(tabs, selected(tabs)) // 4
If you have a lot more validation, you can use compose function:
type Tab = {
text: string;
selectable: boolean;
selected: boolean;
}
const tabs = [
{ text: 'foo', selectable: false, selected: false },
{ text: 'bar', selectable: true, selected: true },
{ text: 'baz', selectable: false, selected: false },
{ text: 'hello', selectable: false, selected: false },
{ text: 'world', selectable: true, selected: false },
];
type Fn = (...args: any[]) => any
const compose = (...fns: Fn[]) => <T,>(data: T) => fns.reduceRight((acc, fn) => fn(acc), data)
const selected = (tabs: Tab[]) => tabs.findIndex(t => t.selected);
const find = (tabs: Tab[])=>(selectedIndex: number) =>
tabs.reduce((acc, elem, index) =>
index > selectedIndex && elem.selectable ? index : acc,
-1
)
const result = compose(find(tabs), selected)(tabs)
But, in this particular case, I don't think it worth using