typescriptpreact

How to give every element of an array a ref in Typescript?


I have a component that renders multiple things, one of that is an array of spans.

Here is a part of my code:

class MyComponent ... {
  private targetSpan = HTMLElement;

  componentDidMount() {
    //Here I get some data that fills an array that has 6 values
  }

   ...
   ...
   ...

  private setActive(value) {
    let target = this.targetSpan;

    if (target.classList.contains('active')) {
      target.classList.remove('active');
      //do more things
    }
    else {
      target.classList.add('active');
      //do even more things
    }
  }

  private splitMyThings() {
    const returnThings: any[] = [];

    forEach(array, thing => {
      returnThings.push(
        <span class="thing" onClick={() => this.setActive(thing)} ref={elem => this.targetSpan = elem}>
          {thing}
        </span>
      );
    });
  }

  render() {
  ...

    return <div class="container">
      ...
      <div class="myTargets">
        {this.splitMyThings()}
      </div>
      ...
    </div>;
  }
}

When I watch the result in a Browser, all the spans show up how I want, only thing that doesnt seem to work correctly is the setActive-Function (or even something else?). No matter what span I click, everytime only the last one gets the active-class, I absolutely cant figure out how to fix that.

Hope someone has a quick and simple solution or idea how to achieve that, thanks in advance!


Solution

  • Here's an example of how you should being approaching something like this:

    import { Component, render } from 'preact';
    
    const CSS_TEXT = `
        .thing {
            cursor: pointer;
        }
    
        .selected {
            color: red;
        };
    `;
    
    class MyComponent extends Component {
        state = {
            // index of the selected item -- if your list items can change, you'll want a better identifier, such as the text.
            selectedIdx: -1, 
            array: []
        }
    
        componentDidMount() {
            // Just creating an array as you mention in the post
            this.setState({ array: Array.from({ length: 6 }).map((_, i) => i) });
        }
    
        render() {
            return (
                <div>
                    <style>{CSS_TEXT}</style>
                    <ul>
                        {this.state.array.map((thing, i) => (
                            <li
                                class={this.state.selectedIdx == i ? 'selected' : ''}
                                onClick={() => this.setState({ selectedIdx: i })}
                            >
                                {thing}
                            </li>
                        ))}
                    </ul>
                </div>
            );
        }
    }
    
    render(<MyComponent />, document.getElementById('app'));
    

    Can test it in the Preact REPL

    Refs are not the way, nor are DOM APIs like .classList. You also should not be using a forEach loop to iterate over one list and populate another -- this is what .map is for.

    Changed up your example slightly for a better demo, (making the array a list instead of spans, adding in CSS for the selected items), but it should be easy to translate back.