简体   繁体   中英

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>
    )
  }
}

What I usually do is just connect the lower level components via Redux. This helps with not passing the state in huge chunks from the top-most component.

A great video course by one of the React creators, Dan Abramov: Getting started with Redux

Absolutely agree with @t1gor . The answer for us was to use REDUX. It changed the entire game for us. Suddenly a button that is nested 10 levels deep (that is, inside a main view, header, header-container, left side grid, etc, etc, deeper and deeper) into purely custom components, has a chance to grab state whenever it needs.

Instead of...

  • Parent (pass down state) - owns state vars
    • Child (will pass down again) - parent has state vars
      • Grandchild (will pass down a third time) - grandparent has state vars
        • Great Grandchild (needs that state var) - great grandparent has state vars

You can do...

  • Parent (no passing) - reads global state vars
    • Child
      • Grandchild
        • Great Grandchild - also reads same global level state vars without being passed...

Usually the code looks something like this...

'use strict'

//Importation of Connection Tools & View
import { connect } from 'react-redux';
import AppView from './AppView';


//Mapping -----------------------------------

const mapStateToProps = state => {
    return {
        someStateVar: state.something.capturedInState,
    };
}

const mapDispatchToProps = dispatch => {
    return {
        customFunctionsYouCreate: () => {
            //do something!
            //In your view component, access this by calling this.props.customFunctionsYouCreate
        },
    };
}

//Send Mappings to View...
export default connect(mapStateToProps, mapDispatchToProps)(AppView);

Long story short, you can keep all global app state level items in something called a store and whenever even the tiniest component needs something from app state, it can get it as the view is being built instead of passing.

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) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM