skip to Main Content

I will be posting multiple types of Block components in a vertical list in random orders and conditions. The blocks will be placed according to list values where list items will be inserted or deleted at any time. I need to store the reference of all the blocks in the exact order of their position in the parent element. The list of the references of the blocks should also be updated when the elements get updated/removed.

import React, { Fragment } from "react";

...

type Blocks = Block1 | Block2 | Block3;

interface Props {
  data: ListData[];
}

interface State {
  data: ListData[];
}

export default class Page extends React.Component<Props, State> {

  state: State;
  private blocks: Blocks[] = [];

  constructor(props: Props) {
    super(props);
    this.state = {
      data: props.data
    };
  }

  private addListDataItems(): void {} // Add `ListData` items randomly

  private removeListDataItems(): void {} // Remove `ListData` items randomly

  render(): JSX.Element {

    return <>
      <div className="page">

        {this.state.data.map((li: ListData, i: number) => <Fragment key={i}>

          {((i % 10) === 0) && <>
            <Block1
              ref={(e: Block1): void => {
                this.blocks[ /* Insert reference according to it's position in the parent element */ ] = e;
              }}
              ...
            />
          </>}

          {(
            // Some conditional Logic Here
          ) && <>
            <Block2
              ref={(e: Block2): void => {
                this.blocks[ /* Insert reference according to it's position in the parent element */ ] = e;
              }}
              ...
            />
          </>}

          {(
            // Some conditional Logic Here
          ) && <>
            <Block3
              ref={(e: Block3): void => {
                this.blocks[ /* Insert reference according to it's position in the parent element */ ] = e;
              }}
              ...
            />
          </>}

        </Fragment>

      </div>
    </>;
  }
}

2

Answers


  1. The best solution would probably depend on what you need to do with the references afterwards. Since you’re looking for an array in order, I’m going to assume you’ll need to iterate over them top to bottom.

    I’d propose a sparse array as your best option here: each ListData item in the this.state.data array can result in 0, 1, 2, or 3 Block being added to your array, so the resulting sparse array of refs could look like [ref, ref, undefined, ref, undefined undefined, ref, ref, ref, ...].

    To obtain that array, simply use a multiple of i as your index:

    {this.state.data.map((li: ListData, i: number) => <Fragment key={i}>
      {/* condition */ && (
        <Block1 ref={(e: Block1) => this.blocks[i * 3 + 0] = e} />
      )}
      {/* condition */ && (
        <Block2 ref={(e: Block2) => this.blocks[i * 3 + 1] = e} />
      )}
      {/* condition */ && (
        <Block3 ref={(e: Block3) => this.blocks[i * 3 + 2] = e} />
      )}
    </Fragment>)}
    

    Don’t forget to also reset your ref array at the top of the render function to avoid preserving old refs when re-rendering.

    render() {
      this.blocks = []
      // ...
    

    And when you need to iterate over your blocks, just skip the falsy ones:

    for (let i = 0; i < this.blocks.length; i++) {
      if (!this.blocks[i]) continue
      // actual code
    }
    
    for (const block of this.blocks) {
      if (!this.blocks[i]) continue
      // actual code
    }
    

    Or use array functions since they already skip empty elements in a sparse array:

    this.blocks.forEach(block => {
      // actual code
    })
    
    this.blocks.map(block => {
      // actual code
    })
    

    Clarification: I represented a sparse array as having either a ref or undefined in each item, but a sparse array is an actual thing in JS when an item hasn’t been defined ever (or has been deleted). And the console representation of a sparse array looks like this

    [ ref, <1 empty item>, ref, <2 empty items>, ref ]
    
    Login or Signup to reply.
  2. The best solution would probably depend on what you need to do with the references afterwards. Since you’re looking for an array in order, I’m going to assume you’ll need to iterate over them top to bottom.

    If you need to have a compact (regular) array, you should try and think more in JavaScript terms and less React. React is just JS after all.

    For example, you could have a counter to remember the indices of your array:

    render(): JSX.Element {
      let cursor = 0
      const blocks = []
      this.blocks = [] // resetting in case array size changes
      this.state.data.forEach((li, i) => {
        if (/* condition 1 */) {
          blocks.push(<Block1 key={i+'1'} ref={e => this.blocks[cursor] = e} />)
          cursor++
        }
        if (/* condition 2 */) {
          blocks.push(<Block2 key={i+'2'} ref={e => this.blocks[cursor] = e} />)
          cursor++
        }
        if (/* condition 2 */) {
          blocks.push(<Block2 key={i+'3'} ref={e => this.blocks[cursor] = e} />)
          cursor++
        }
      })
    
      return <>
        <div className="page">
          {blocks}
    

    Or if manipulating JSX outside of the return of the render function is too odd looking, you could just compute the conditions beforehand

    render(): JSX.Element {
      const blocksData = this.state.data.reduce((array, li, i) => {
        if (/* condition 1 */) {
          array.push({ Element: Block1, index: array.length })
        }
        if (/* condition 2 */) {
          array.push({ Element: Block2, index: array.length })
        }
        if (/* condition 3 */) {
          array.push({ Element: Block3, index: array.length })
        }
        return array
      }, [])
    
      this.blocks = [] // resetting in case array size changes
      return <>
        <div className="page">
          {blocksData.map(({Element, index}) => (
            <Element ref={e => this.blocks[index] = e} />
          ))}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search