0

I've been battle with this problem for 2 days now, I'm trying to implement a continuous vertical text scrolling, but for some reasons unknown to me react returns a number instead of my array object (after I logged the output to see what was going wrong with my code) at the second iteration (as the array is constantly updated at interval). For the record, this was initially implemented in an Angular JS application, but I'm trying to convert it to react with useEffect() and useState() to update the changes in the array.

Below is what I've done so far:

const Skills = () => {
  let skillWrap = useRef();

  let [activeSkills, setActiveSkills] = useState([]);
  console.log(activeSkills);
    console.log(typeof activeSkills);

  let sMax = totalSkills.length - 1; // 0 -> 19

  let activeStart = Math.floor(Math.random() * (sMax + 1));
  let activeEnd = activeStart === 0 ? sMax : activeStart - 1;

  for (let e = activeStart; e <= sMax; e++) {
    setActiveSkills(activeSkills.push(totalSkills[e]));
  }

  if (activeStart !== 0) {
    for (let s = 0; s <= activeEnd; s++) {
      setActiveSkills(activeSkills.push(totalSkills[s]));
    }
  }

  let scrollDis = 0,
    scrollingDown = false,
    scrollingUp = false,
    scrollingDownSelf = false,
    scrollingUpSelf = false,
    scrollCatchInterval = 40,
    scrollDirection = "up",
    hasScrolledRecently = false;

  const wheelEventHandler = e => {
    let skillFocused = skillWrap.current.childNodes[19];

    skillFocused.classList.remove("active");

    function animateScroll(scrollDis, callback) {
      let curLeftTop = scrollDis * 8,
        curLeftFinal = scrollDis * 4;
      tween(skillWrap.current, -curLeftFinal, curLeftTop, 1, callback);
    }

    function scrollUp() {
      setTimeout(() => {
        for (let su = 0; su < scrollDis; su++) {
          activeEnd--;
          activeStart--;
          if (activeEnd < 0) activeEnd = 19;
          if (activeStart < 0) activeStart = 19;

          /*setActiveSkills(activeSkills.unshift(totalSkills[activeStart]));
                    setActiveSkills(activeSkills.pop());*/
          activeSkills.unshift(totalSkills[activeStart]);
          activeSkills.pop();
        }
        skillFocused.classList.add("active");
        skillWrap.current.style.transform = "none";
        scrollDis = 0;
        scrollingUp = false;
        scrollingUpSelf = false;
        if (e.deltaZ === 0) {
          setTimeout(() => {
            hasScrolledRecently = false;
          }, 3000);
        }
      }, 0);
    }

    function scrollDown() {
      setTimeout(() => {
        for (let sd = 0; sd < Math.abs(scrollDis); sd++) {
          activeEnd++;
          activeStart++;
          if (activeEnd > 19) activeEnd = 0;
          if (activeStart > 19) activeStart = 0;

          /*setActiveSkills(activeSkills.push(totalSkills[activeEnd]));
                    setActiveSkills(activeSkills.shift());*/
          activeSkills.push(totalSkills[activeEnd]);
          activeSkills.shift();
        }
        skillFocused.classList.add("active");
        skillWrap.style.transform = "none";
        scrollDis = 0;
        scrollingDown = false;
        scrollingDownSelf = false;
        if (e.deltaZ === 0) {
          setTimeout(() => {
            hasScrolledRecently = false;
          }, 3000);
        }
      }, 0);
    }

    if (
      (e.deltaY === 100 || e.deltaY === 3) &&
      !scrollingUp &&
      !scrollingDownSelf
    ) {
      // (scroll down) add skill to bottom & remove skill from top
      scrollDirection = "down";
      scrollDis--;
      scrollingDown = true;
      if (e.deltaZ === 0) hasScrolledRecently = true;
      let scd = scrollDis;
      setTimeout(() => {
        if (scrollDis === scd) {
          if (scrollDis < -6) scrollDis = -6;
          scrollingDownSelf = true;
          animateScroll(scrollDis, scrollDown);
        }
      }, scrollCatchInterval);
    } else if (
      (e.deltaY === -100 || e.deltaY === -3) &&
      !scrollingDown &&
      !scrollingUpSelf
    ) {
      // (scroll up) add skill to top & remove skill from bottom
      scrollDirection = "up";
      scrollDis++;
      scrollingUp = true;
      if (e.deltaZ === 0) hasScrolledRecently = true;
      let scu = scrollDis;
      setTimeout(() => {
        if (scrollDis === scu) {
          if (scrollDis > 5) scrollDis = 5;
          scrollingUpSelf = true;
          animateScroll(scrollDis, scrollUp);
        }
      }, scrollCatchInterval);
    }
  };

  function tween(o, x, y, durationSecs, onComplete) {
    let fps = 30,
      count = 0,
      stopAt = fps * durationSecs,
      easef = Quad_easeInOut;
    let f = function() {
      count++;
      if (count >= stopAt) {
        tween_stop(o);
        if (onComplete) onComplete();
      } else {
        tween_setProperty(
          o,
          easef(count, 0, x, stopAt),
          easef(count, 0, y, stopAt)
        );
      }
    };
    clearInterval(o._tween_int);
    o._tween_int = setInterval(f, (durationSecs * 1000) / fps);
  }

  function tween_stop(o) {
    clearInterval(o._tween_int);
  }

  function tween_setProperty(o, x, y) {
    o.style.cssText += ";transform:translate3d(" + x + "vw," + y + "vh,0);";
  }

  function Quad_easeInOut(t, b, c, d) {
    if ((t /= d / 2) < 1) return (c / 2) * t * t * t * t + b;
    return (-c / 2) * ((t -= 2) * t * t * t - 2) + b;
  }

  useEffect(() => {
    /*console.log(activeSkills);*/

    setTimeout(() => {
      skillWrap.current.childNodes[19].classList.add("active");
    }, 2000);

    window.addEventListener("wheel", wheelEventHandler);

    function constantScroll() {
      // prevents scrolling while changing views
      setTimeout(function() {
        // emulate scrolling of the skill list
        let scrollEvent = new WheelEvent("wheel", {
          deltaY: scrollDirection === "up" ? -100 : 100,
          deltaZ: 1 // used to differentiate between user scroll / programmatic scroll
        });

        if (!hasScrolledRecently) {
          // 3 scroll events are dispatched to mirror scrolling of 3 skills
          for (let r = 0; r < 3; r++) {
            window.dispatchEvent(scrollEvent);
          }
        }
        constantScroll();
      }, 3000);
    }

    // wait 3 seconds before issuing first scroll
    setTimeout(function() {
      constantScroll();
    }, 2000);

    return () => {
      window.removeEventListener("wheel", wheelEventHandler);
      console.log("Skills Component will unmount");
    };
  }, [activeSkills]);

  return (
    <div>
      <div className="view skills active active-f">
        <div className="header-container skills">
          <div className="header-title-wrap">
            <div className="cover" />
            <h1 className="header big first">My</h1>
            <h1 className="header big last">Skillsset</h1>
          </div>
          <div className="header-info-wrap">
            <div className="header-content-body skill-one">
              <div className="line-left" />
              <p className="header body about-one">
                The core of my skill set largely surrounds the MERN{" "}
                <strong>stack</strong>, to achieve both a clear and dynamic user
                experience. I also have some experience with mobile integration
                (Android and iOS). Strengthening my grasp of the mobile app
                development space is one of my primary goals for the near
                future.
              </p>
            </div>
          </div>
        </div>
        <div className="skill-container active active-f">
          <div className="skill-wrap">hey</div>
        </div>
      </div>
      <div className="skill-container active-f active">
        <div ref={skillWrap} className="skill-wrap">
          {console.log(activeSkills)}
          {activeSkills.map((skill, i) => (
            <div key={i} className="skill">
              {skill}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

Below is the https://codesandbox.io/ page to see what I've done so far, and the link to the Angular JS implementation which I'm trying to achieve with React.

My React implementation (from the skills route): https://codesandbox.io/s/shy-http-jlrle
Original Angular implementation (skills route): https://codesandbox.io/s/github/eazylaykzy/portfolio-ex

I really would appreciate any effort at helping me get this to work.

Eazy
  • 133
  • 11
  • 3
    push is a mutation. avoid using it, especially in react coding – Joe Lloyd Nov 14 '19 at 14:07
  • @JoeLloyd thanks, if you would, please look at the provided links, what would you suggest I use? – Eazy Nov 14 '19 at 14:09
  • 2
    @Eazy try the spread operator, it's easier to read and does not mutate the original array – Ricardo Costa Nov 14 '19 at 14:10
  • 2
    yeah use spread `[...oldArrayOfStuff, newItem]` – Joe Lloyd Nov 14 '19 at 14:11
  • @RicardoCosta thanks for the insight, I will try that now. – Eazy Nov 14 '19 at 14:11
  • @JoeLloyd okay, thanks Joe – Eazy Nov 14 '19 at 14:11
  • @RicardoCosta I did use the spread operator with my array state, but I’m still getting the same error, please can you take a look at the provided link, and see what you think I could do to effect the needed change. Thanks. – Eazy Nov 14 '19 at 14:24
  • 1
    I think you have to wrap the first part, where you set the activeSkills, with a useMemo or a useEffect. Otherwise setting the state like this should rerender the component the whole time. – Julian Kleine Nov 14 '19 at 14:42
  • for starters i would rewrite the for loop to either use a spread operator or correctly use array mutations (copy the array to a variable, push to the new variables and then after you exit the for loop use setActiveSkills. As @Julian said you should wrap the logic in useEffect otherwise you are constantly running the code. I'm having a hard time trying to edit the sandbox because the for loops run everytime and slow the page very much. please consider the modifications and report if you find anything new – Ricardo Costa Nov 14 '19 at 14:49
  • @Julian the whole of the function is called from inside a `useEffect`, and `activeSkills` passed in as a dependency. – Eazy Nov 14 '19 at 14:49
  • @RicardoCosta this seem clear, I will implement this and get back to y’all, thanks. – Eazy Nov 14 '19 at 14:51
  • https://slemgrim.com/mutate-or-not-to-mutate/ take a look at this link if you have doubts with array mutation (this is just best practices, makes easier for other people to understand your code etc) please consider doing something like: declare variable (using let) equal to activeSkills array; for loop; mutate the new variable that has the activeskills inside the for loop; and outside the for loop use the setActiveSkills with the declared variable. hope that was clear – Ricardo Costa Nov 14 '19 at 14:52

1 Answers1

0

Just to abstract the first part as mentioned in my comment, you could write your own hook and use it as so

import React, { useMemo } from 'react';

// How to shuffle an array
// https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
function shuffle(array) {
  const newArray = [...array];
  for (let i = newArray.length - 1; i > 0; i -= 1) {
    const j = Math.floor(Math.random() * (i + 1));
    [newArray[i], newArray[j]] = [newArray[j], newArray[i]];
  }
  return newArray;
}

function useActiveSkills(skills) {
  return useMemo(() => shuffle(skills), [skills]);
}

function Component() {
  const activeSkills = useActiveSkills();

  return (
    <>
      {activeSkills.map((skill) => (
        <p>{skill.name}</p>
      ))}
    </>
  );
}

Julian Kleine
  • 1,539
  • 6
  • 14