Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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>
    )
  }
}
like image 315
neaumusic Avatar asked Jul 04 '17 22:07

neaumusic


2 Answers

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

like image 146
t1gor Avatar answered Sep 19 '22 16:09

t1gor


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.

like image 22
GoreDefex Avatar answered Sep 20 '22 16:09

GoreDefex