7

I am making a simple react app where there are two different div's..

One with select input and selected list,

  <div id="container">
    <div className="_2iA8p44d0WZ">
      <span className="chip _7ahQImy">Item One</span>
      <span className="chip _7ahQImy">Item Two</span>
      <span className="chip _7ahQImy">Item Three</span>
      <span className="chip _7ahQImy">Item Four</span>
      <span className="chip _7ahQImy">Item Five</span>
      <input
        type="text"
        className="searchBox"
        id="search_input"
        placeholder="Select"
        autoComplete="off"
        value=""
      />
    </div>
  </div>

Another will list down the selected option as fieldset,

  <div>
    {selectedElements.map((item, i) => (
      <div key={i} className="selected-element" ref={scrollDiv}>
        <fieldset>
          <legend>{item}</legend>
        </fieldset>
      </div>
    ))}
  </div>

Based on this solution, I have added createRef to the selected element like,

<div key={i} className="selected-element" ref={scrollDiv}>
</div>

Then I took Javascript query methods to get DOM elements like,

  const chipsArray = document.querySelectorAll("#container > div > .chip");

Added click event listener to all the elements like,

  chipsArray.forEach((elem, index) => {
    elem.addEventListener("click", scrollSmoothHandler);
  });

Then scrollSmoothHandler like,

const scrollDiv = createRef();

  const scrollSmoothHandler = () => {
    console.log(scrollDiv.current);
    if (scrollDiv.current) {
      scrollDiv.current.scrollIntoView({ behavior: "smooth" });
    }
  };

But this doesn't work the way as expected.

Requirement:

On click over any item in first div, then its related fieldset needs to get smooth scrolled in another div..

Eg: If user click on the element Item Four under <div id="container"> ... <span className="chip _7ahQImy">Item Four</span> ... </div>

then the related fieldset needs to get scrolled into. Here the fieldset with legend as Item Four ..

I think also making the js dom query methods on react and it seems not a react way of implementation. Can anyone please kindly help me to achieve the result of scrolling to a related fieldset on click over the selected item..

Edit React Functional Component (forked)

3
  • Sorry, I saw your message late yesterday, and was preoccupied most of today until just a bit ago. I have answered below with what I think is a fix for what your are going for. Commented Nov 24, 2020 at 6:29
  • @DrewReese, Again sorry for disturbing but now its different scenario question here, stackoverflow.com/q/64989134/13270726 Commented Nov 24, 2020 at 16:00
  • @DrewReese, Let me know from you how can I handle the above posted question.. Data I receive as an array and I will iterate and display on each row with save button for each row.. I need to submit the data of each row as an object like, { ContactMode: 1} ... Commented Nov 24, 2020 at 16:58

1 Answer 1

9

Issue

  1. React.createRef is really only valid in class-based components. If used in a functional component body then the ref would be recreated each render cycle.
  2. Don't use a DOM query selector to attach onClick listeners to DOM elements. These live outside react and you'd need to remember to clean them up (i.e. remove them) so you don't have a memory leak. Use React's onClick prop.
  3. When the selectedElements are mapped you attach the same ref to each element, so the last one set is the one your UI gets.

Solution

  1. Use React.useRef in the functional component body to store an array of react refs to attach to each element you want to scroll into view.
  2. Attach the scrollSmoothHandler directly to each span's onClick prop.
  3. Attach each ref from the ref array created in 1. to each mapped field set you want to scroll to.

Code

import React, { createRef, useRef } from "react";
import { render } from "react-dom";

const App = () => {
  const selectedElements = [
    "Item One",
    "Item Two",
    "Item Three",
    "Item Four",
    "Item Five"
  ];

  // React ref to store array of refs
  const scrollRefs = useRef([]);

  // Populate scrollable refs, only create them once
  // if the selectedElements array length is expected to change there is a workaround
  scrollRefs.current = [...Array(selectedElements.length).keys()].map(
    (_, i) => scrollRefs.current[i] ?? createRef()
  );

  // Curried handler to take index and return click handler
  const scrollSmoothHandler = (index) => () => {
    scrollRefs.current[index].current.scrollIntoView({ behavior: "smooth" });
  };

  return (
    <div>
      <div id="container">
        <div className="_2iA8p44d0WZ">
          {selectedElements.map((el, i) => (
            <span
              className="chip _7ahQImy"
              onClick={scrollSmoothHandler(i)} // <-- pass index to curried handler
            >
              {el}
            </span>
          ))}
          <input
            type="text"
            className="searchBox"
            id="search_input"
            placeholder="Select"
            autoComplete="off"
            value=""
          />
        </div>
      </div>
      <div>
        {selectedElements.map((item, i) => (
          <div
            key={i}
            className="selected-element"
            ref={scrollRefs.current[i]} // <-- pass scroll ref @ index i
          >
            <fieldset>
              <legend>{item}</legend>
            </fieldset>
          </div>
        ))}
      </div>
    </div>
  );
};

Solution #2

Since you can't update any elements in the div with id="container" and all the onClick handlers need to be attached via querying the DOM, you can still use a curried scrollSmoothHandler callback and enclose an index in scope. You'll need an useEffect hook to query the DOM after the initial render so the spans have been mounted, and an useState hook to store a "loaded" state. The state is necessary to trigger a rerender and re-enclose over the scrollRefs in the scrollSmoothHandler callback.

const App = () => {
  const selectedElements = [
    "Item One",
    "Item Two",
    "Item Three",
    "Item Four",
    "Item Five"
  ];

  const [loaded, setLoaded] = useState(false);
  const scrollRefs = useRef([]);

  const scrollSmoothHandler = (index) => () => {
    scrollRefs.current[index].current.scrollIntoView({ behavior: "smooth" });
  };

  useEffect(() => {
    const chipsArray = document.querySelectorAll("#container > div > .chip");

    if (!loaded) {
      scrollRefs.current = [...Array(chipsArray.length).keys()].map(
        (_, i) => scrollRefs.current[i] ?? createRef()
      );

      chipsArray.forEach((elem, index) => {
        elem.addEventListener("click", scrollSmoothHandler(index));
      });
      setLoaded(true);
    }
  }, [loaded]);

  return (
    <div>
      <div id="container">
        <div className="_2iA8p44d0WZ">
          <span className="chip _7ahQImy">Item One</span>
          <span className="chip _7ahQImy">Item Two</span>
          <span className="chip _7ahQImy">Item Three</span>
          <span className="chip _7ahQImy">Item Four</span>
          <span className="chip _7ahQImy">Item Five</span>
          <input
            type="text"
            className="searchBox"
            id="search_input"
            placeholder="Select"
            autoComplete="off"
            value=""
          />
        </div>
      </div>
      <div>
        {selectedElements.map((item, i) => (
          <div key={i} className="selected-element" ref={scrollRefs.current[i]}>
            <fieldset>
              <legend>{item}</legend>
            </fieldset>
          </div>
        ))}
      </div>
    </div>
  );
};

Edit scroll-into-view-in-react

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks much Reese, Could you kindly look at the above comments please under question.. I cannot modify this line onClick={scrollSmoothHandler(i)} in my real ap because there I am using a library ..
So In general I need to add the click event after the DOM is loaded may be but I need to make it dynamically anyhow because those are not a hard coded jsx in real app..
And I am just need a help in only first div/span tags where click event happening because in second section containing fieldset, you can modify anything and no problem with it..
@Undefined Ah, I see. Let me see what I can come up with. Just so I understand correctly, you can't modify the spans in the id="container" div and attaching of scrollSmoothHandler, the ref creation needs to be dynamic based on what is queried from the DOM?
Thanks Reese, In end the requirement is that if I click on any of the selected chip(other than the close icon) in that library then selected item's fieldset needs to get scrolled.. So I have brought down the codesandbox like in question with simple code of that library and I cannot modify that first div code alone.. So only I also made the click event handler using DOM in my question codesandbox.. And I am sorry for not mentioning.. And sure kindly go through the scenario and let me know for updated solution please..
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.