2

Assuming the following and all the components/fus/fci/ssg have just a single h1 with a site props. I want to understand why it is a valid react element yet these are not showing equally rendered. That is one has the h1 element and the other doesn't. The idea was to not create large component with toggles for different sites and each site would be swapped out based on the nav pick. I don't see anything documented for this unless I missed it...

{this.state.renderSite}
<Fci site="Fci"/>

enter image description here

import React from 'react';                                                 
import styles from './App.css';                                            
import Nav from '../components/Nav.js'                                     
import Fus from '../components/Fus.js'                                     
import Fci from '../components/Fci.js'                                     
import Ssg from '../components/Ssg.js'                                     

export default class App extends React.Component {                         
  constructor(props) {                                                     
    super(props);                                                          
    this.state = {renderSite: '', site: 'default' };                       
    this.pickSite = this.pickSite.bind(this);                              
  }                                                                        

  pickSite(site){                                                          
    this.setState({renderSite: React.createElement(site, {"site":site})}); 
    this.setState({site: site});                                           
    console.log( React.isValidElement(this.state.renderSite));             
}                                                                          

  render() {                                                               
    return (                                                               
      <div className={styles.app}>                                         
      <Nav site={this.pickSite.bind(this)} /> 
      {this.state.renderSite}                  
      <Fci site="Fci"/>                                                    
      </div>                                                               
    );                                                                     
  }                                                                        
}                                                                          

The Nav

import React from 'react';

export default class Nav extends React.Component {
        constructor(props) {
            super(props);
            this.update = this.update.bind(this);
        }

        update(e) {
           this.props.site(e.target.dataset.site);
        }

        render(){ 
            return ( 
                <div>
                    <button onClick={this.update} data-site="Ssg"> SSG </button>
                    <button onClick={this.update} data-site="Fci"> FCI </button>
                    <button onClick={this.update} data-site="Fus"> FUS </button>
                </div>
                     );
        }       
}
JustDave
  • 516
  • 1
  • 8
  • 18

1 Answers1

2

The problem is when you create the element you are passing a string (data-site value), not a component reference. So it ends up like this:

React.createElement("Fci");

As opposed to:

React.createElement(Fci);

Using a string will create a simple HTML element, not a component with with its own rendered content.

You could create a component map like this:

const componentMap = {
    "Fci": Fci,
    "Fus": Fus,
    "Ssg": Ssg
}

Then from your string you can resolve a component reference:

React.createElement(componentMap[site], {site: site});

Or you could pass a component reference from your Nav:

<button onClick={this.update.bind(this, Ssg, "Ssg"}> SSG </button>

update(component, site, e) {
    this.props.site(component, site);
}

pickSite(component, site) {
    React.createElement(component, {site: site});
}
Aaron Beall
  • 49,769
  • 26
  • 85
  • 103
  • Well spotted! As an aside, rather than create the element, and store the (reference to) the created element entirely in state, I would advise to store only the reference to the component in your state, and create the actual element inside your render function. keeps your code cleaner – wintvelt Apr 12 '16 at 16:35
  • I agree with @wintvelt, in fact I would go as far as only storing the string in state and do all the element mapping and creation in your `render` function. You could create a sub-render function like `renderSite()` to isolate it. – Aaron Beall Apr 12 '16 at 17:20
  • @Aaron I don't like the idea of inner mixing jsx with js in the render; however, assuming the state is fine, the same <{this.componentMap[this.state.site]} site={this.state.site] /> fails at unexpected token. Is it normal to mix and why does createElement work in this case and not the jsx? – JustDave Apr 12 '16 at 20:03
  • See https://stackoverflow.com/a/33471928/567525 for a simpler method that avoid `createElement()` – jbustamovej Aug 29 '17 at 12:28
  • @jbustamovej That method doesn't work in this case, because `Fci` is a *component* not an html *tag*. – Aaron Beall Aug 29 '17 at 13:37
  • Because, this had a few views I'd like to come back with a bit more insight now that I've done a lot more complicated things over the year. If this situation should arise please do yourself a favor and move on to react router; as of 4 it does dynamic routing. Please also read the principles behind it in the document section as it is helpful to understand the difference between it and the normal static routing that everyone is used to. It took me a day or two to really make practical use of it. – JustDave Oct 12 '17 at 19:41