Find next element depending on the previous ones

Viewed 45

i have some tabs and i want to select the next if i swipe left. Not all of my tabs are selectable since they can be disabled temorarily.

I try to solve this problem the "functional" way, but I can't do it.

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 },
];

function nextTab() {
  let result;

  const selectedIndex = tabs.findIndex(t => t.selected);

  if (selectedIndex > -1) {
    result = tabs
    .filter((t, i) => i > selectedIndex)
    .find(t => t.selectable);
  }
  
  return result;
}

console.log(nextTab())

Is there a way without declate a variable and add a condition?

Thank you, Christian

2 Answers

You can use slice()

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 },
];

function nextTab() {
  return tabs
    .slice(tabs.findIndex(t => t.selected) + 1)
    .find(t => t.selectable);
}

console.log(nextTab())

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

Related