Search code examples
javascriptdesign-patternsreactjsreact-dom

React - proper state management for rows of unmounted JSX?


We have a crazy DOM hierarchy, and we've been passing JSX in props rather than embedding children. We want the base class to manage which documents of children are shown, and which children are docked or affixed to the top of their associated document's window.

  • List (crazy physics writes inline styles to base class wrappers)
    1. Custom Form (passes rows of JSX to Base class)
      • Base Class (connects to list)
    2. Custom Form (passes rows of JSX to base class)
      • Base class (connects to list)

The problem is that we're passing deeply nested JSX, and state management / accessing refs in the form is a nightmare.

I don't want to re-declare every row each time, because those rows have additional state attached to them in the Base Class, and the Base Class needs to know which rows actually changed. This is pretty easy if I don't redeclare the rows.


I don't know how to actually deal with rows of JSX in Custom Form.

  1. Refs can only be appended in a subroutine of render(). What if CustomForm wants to measure a JSX element or write inline CSS? How could that JSX element exist in CustomForm.state, but also have a ref? I could cloneElement and keep a virtual DOM (with refs) inside of CustomForm, or depend on the base class to feed the deeply-nested, mounted ref back.
  2. I believe it's bad practice to write component state from existing state. If CustomForm state changes, and I want to change which rows are passed to BaseClass, I have to throttle with shouldComponentUpdate, re-declare that stage document (maintaining row object references), then call setState on the overarching collection. this.state.stages.content[3].jsx is the only thing that changed, but I have to iterate through every row in every stage document in BaseClass when it sees that props.stages changed.

Is there some trick to dealing with collections of JSX? Am I doing something wrong? This all seems overly-complicated, and I would rather not worsen the problem by following some anti-pattern.


Custom Form:

render () {
  return <BaseClass stages={this.stages()}/>
}

stages () {
  if (!this._stages) this._stages = { title: this.title(), content: this.content() };
  return this._stages;
}

title () {
  return [{
    canBeDocked: false,
    jsx: (
      <div>A title document row</div>
    )
  }
}

content () {
  return [{
    canBeDocked: false,
    jsx: (
      <div>Hello World</div>
    )
  }, {
    canBeDocked: true,
    jsx: (
      <div>Yay</div>
    )
  }
}

Solution

  • The issue is having content as follows, and for some reason not being able to effectively persist the child instances that haven't changed (without re-writing the entire templateForChild).

    constructor (props) {
      super(props);
    
      // --- can't include refs --->
      // --- not subroutine of render --->
      this.state = {
        templateForChild: [
          <SomeComponentInstance className='hello' />,
          <AnotherComponentInstance className='world' />,
        ],
      };
    }
    
    componentDidMount () {
      this.setState({
        templateForChild: [ <div className='sometimes' /> ],
      }); // no refs for additional managing in this class
    }
    
    render () {
      return ( <OtherManagerComponent content={this.state.templateForChild} /> );
    }
    
    1. I believe the answer could be to include a ref callback function, rather than a string, as mentioned by Dan Abramov, though I'm not yet sure if React does still throw a warning. This would ensure that both CustomForm and BaseClass are assigned the same ref instance (when props.ref callback is executed)

    2. The answer is to probably use a key or createFragment. An unrelated article that addresses a re-mounting problem. Not sure if the fragment still includes the same instances, but the article does read that way. This is likely a purpose of key, as opposed to ref, which is for finding a DOM node (albeit findDOMNode(ref) if !(ref instanceof HTMLElement).