I have an array of strings to display const array = ["one", "two", "three"];
.
The UI initially shows the first item in the array i.e. "one"
. From there I have a button right
when clicked it shows the next item or string which is two
, and then three
, after three
it should go back to one
and start from there again.
I also have a left
button, when clicked it shows the previous item or string, if the current string is two
, the previous string is one
, and then after one
it starts from three
and walks backward.
I am using generator to do it. Here is my attempt
function* stepGen(steps) {
let index = 0;
while (true) {
const direction = yield steps[index];
index = (index + (direction === "forward" ? 1 : -1)) % steps.length;
}
}
const array = ["one", "two", "three"];
let gen = stepGen(array);
const getNext = () => gen.next("forward").value;
const getPrev = () => gen.next("backward").value;
export default function App() {
const [current, setCurrent] = useState(() => getNext());
const onRight = () => {
const next = getNext();
setCurrent(next);
};
const onLeft = () => {
const prev = getPrev();
setCurrent(prev);
};
return (
<div className="App">
<h1>{current}</h1>
<button onClick={onLeft}>left</button>
<button onClick={onRight}>right</button>
</div>
);
}
Here is a live demo you can play with https://codesandbox.io/s/cyclethrough1-deh8p?file=/src/App.js
Apparently the current behavior is buggy. There are multiple issues that I don't know the causes and the solutions:
two
not one
. I guess it has something to do with how I initiate my state current
const [current, setCurrent] = useState(() => getNext());
I thought () => getNext()
is only to get called once when the component first mounts so current
should be one
from the start.
And I tried to initiated the state with
const [current, setCurrent] = useState(array[0]);
It indeed starts with the first item in the array which is one
but you have to click right
button twice to make it go to two
. Here is the live demo for this variation https://codesandbox.io/s/cyclethrough2-5gews?file=/src/App.js
left
button, which should walk backward the loop doesn't work. It is broken completely. the right
button works though. Not sure why.The problem with getPrev
is the remainder (%
) operator, which unlike the modulo operation returns a negative result when the remainder is negative. To solve that use a modulo function instead:
// modulo function
const mod = (n, r) => ((n % r) + r) % r;
To solve the problem on the 1st render create the initial value outside of the component. This is a workaround, since I can't find the reason for that bug.
const init = getNext(); // get the initial value
export default function App() {
const [current, setCurrent] = useState(init); // use init value
I would also save the need for a ternary to determine the increment by passing 1
and -1
in getNext
and getPrev
respectively.
Full code example (sandbox):
// modulo function
const mod = (n, r) => ((n % r) + r) % r;
function* stepGen(steps) {
let index = 0;
while (true) {
const dir = yield steps[index];
index = mod(index + dir, steps.length); // use mod function instead of remainder operator
}
}
const array = ['one', 'two', 'three'];
const gen = stepGen(array);
const getPrev = () => gen.next(-1).value; // dec directly
const getNext = () => gen.next(1).value; // inc directly
const init = getNext(); // get the initial value
export default function App() {
const [current, setCurrent] = useState(init); // use init value
const onLeft = () => {
const next = getPrev();
setCurrent(next);
};
const onRight = () => {
const prev = getNext();
setCurrent(prev);
};
return (
<div className="App">
<h1>{current}</h1>
<button onClick={onLeft}>left</button>
<button onClick={onRight}>right</button>
</div>
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With