最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do you swap out a component in React? - Stack Overflow

programmeradmin3浏览0评论


I am trying to build a single-page application using React. Currently I have a ponent called App that is being rendered by ReactDOM, it contains navigation, then the ponent (or page) to be rendered after the navigation ponent.

import React from 'react';
import Navigation from './Navigation';
import NavigationLink from './NavigationLink';
import Home from './Home';
import About from './About';

const App = () => (
  <div>
    <Navigation>
      <NavigationLink onClick={ ... }>Home</NavigationLink>
      <NavigationLink onClick={ ... }>About</NavigationLink>
    </Navigation>
    <Home />
  </div>
);

export default App;

I want to be able to select the "About" link and the Home ponent update to be About. I am looking for solutions to this issue that will work with more than two elements (or pages).

This is my current code that works, though is a very poor solution.

import React, { Component } from 'react';
import {
  NavItem,
  NavLink,
  UncontrolledDropdown,
  DropdownToggle,
  DropdownMenu,
  DropdownItem,
} from 'reactstrap';
import firebase from '../../utils/firebase';
import Navigation from '../../ponents/Navigation/Navigation';
import Login from '../Login/Login';
import Profile from '../Profile/Profile';
import Home from '../Home/Home';

class App extends Component {
  state = {
    currentShowingPage: 0,
    currentlyLoggedInUser: null,
    isLoginModalOpen: false,
  };

  ponentDidMount = () => {
    firebase.auth().onAuthStateChanged((currentUser) => {
      if (currentUser) {
        this.setState({
          currentlyLoggedInUser: currentUser,
          isLoginModalOpen: false,
        });
      } else {
        this.setState({
          currentlyLoggedInUser: null,
        });
      }
    });
  };

  handleLoginModalToggle = () => {
    this.setState((previousState) => ({
      isLoginModalOpen: !previousState.isLoginModalOpen,
    }));
  };

  render = () => {
    let currentShowingPageComponent;

    const {
      isLoginModalOpen,
      currentlyLoggedInUser,
      currentShowingPage,
    } = this.state;

    if (currentShowingPage === 0) {
      currentShowingPageComponent = <Home />;
    } else if (currentShowingPage === 1) {
      currentShowingPageComponent = <Profile />;
    }

    return (
      <div>
        <Login
          isModalOpen={isLoginModalOpen}
          modalToggler={this.handleLoginModalToggle}
        />
        <Navigation>
          {currentlyLoggedInUser ? (
            <UncontrolledDropdown nav>
              <DropdownToggle nav caret>
                {currentlyLoggedInUser.email}
              </DropdownToggle>
              <DropdownMenu right>
                <DropdownItem
                  onClick={() =>
                    this.setState({
                      currentShowingPage: 1,
                    })
                  }
                >
                  Profile
                </DropdownItem>
                <DropdownItem disabled>Expeditions</DropdownItem>
                <DropdownItem divider />
                <DropdownItem onClick={() => firebase.auth().signOut()}>
                  Sign Out
                </DropdownItem>
              </DropdownMenu>
            </UncontrolledDropdown>
          ) : (
            <NavItem>
              <NavLink onClick={this.handleLoginModalToggle}>Login</NavLink>
            </NavItem>
          )}
        </Navigation>
        {currentShowingPageComponent}
      </div>
    );
  };
}

export default App;


I am trying to build a single-page application using React. Currently I have a ponent called App that is being rendered by ReactDOM, it contains navigation, then the ponent (or page) to be rendered after the navigation ponent.

import React from 'react';
import Navigation from './Navigation';
import NavigationLink from './NavigationLink';
import Home from './Home';
import About from './About';

const App = () => (
  <div>
    <Navigation>
      <NavigationLink onClick={ ... }>Home</NavigationLink>
      <NavigationLink onClick={ ... }>About</NavigationLink>
    </Navigation>
    <Home />
  </div>
);

export default App;

I want to be able to select the "About" link and the Home ponent update to be About. I am looking for solutions to this issue that will work with more than two elements (or pages).

This is my current code that works, though is a very poor solution.

import React, { Component } from 'react';
import {
  NavItem,
  NavLink,
  UncontrolledDropdown,
  DropdownToggle,
  DropdownMenu,
  DropdownItem,
} from 'reactstrap';
import firebase from '../../utils/firebase';
import Navigation from '../../ponents/Navigation/Navigation';
import Login from '../Login/Login';
import Profile from '../Profile/Profile';
import Home from '../Home/Home';

class App extends Component {
  state = {
    currentShowingPage: 0,
    currentlyLoggedInUser: null,
    isLoginModalOpen: false,
  };

  ponentDidMount = () => {
    firebase.auth().onAuthStateChanged((currentUser) => {
      if (currentUser) {
        this.setState({
          currentlyLoggedInUser: currentUser,
          isLoginModalOpen: false,
        });
      } else {
        this.setState({
          currentlyLoggedInUser: null,
        });
      }
    });
  };

  handleLoginModalToggle = () => {
    this.setState((previousState) => ({
      isLoginModalOpen: !previousState.isLoginModalOpen,
    }));
  };

  render = () => {
    let currentShowingPageComponent;

    const {
      isLoginModalOpen,
      currentlyLoggedInUser,
      currentShowingPage,
    } = this.state;

    if (currentShowingPage === 0) {
      currentShowingPageComponent = <Home />;
    } else if (currentShowingPage === 1) {
      currentShowingPageComponent = <Profile />;
    }

    return (
      <div>
        <Login
          isModalOpen={isLoginModalOpen}
          modalToggler={this.handleLoginModalToggle}
        />
        <Navigation>
          {currentlyLoggedInUser ? (
            <UncontrolledDropdown nav>
              <DropdownToggle nav caret>
                {currentlyLoggedInUser.email}
              </DropdownToggle>
              <DropdownMenu right>
                <DropdownItem
                  onClick={() =>
                    this.setState({
                      currentShowingPage: 1,
                    })
                  }
                >
                  Profile
                </DropdownItem>
                <DropdownItem disabled>Expeditions</DropdownItem>
                <DropdownItem divider />
                <DropdownItem onClick={() => firebase.auth().signOut()}>
                  Sign Out
                </DropdownItem>
              </DropdownMenu>
            </UncontrolledDropdown>
          ) : (
            <NavItem>
              <NavLink onClick={this.handleLoginModalToggle}>Login</NavLink>
            </NavItem>
          )}
        </Navigation>
        {currentShowingPageComponent}
      </div>
    );
  };
}

export default App;
Share Improve this question edited Jul 18, 2018 at 23:10 asked Jul 18, 2018 at 23:03 user7885981user7885981 4
  • I don't quite understand what you want to do. Do you want the clicked link to be moved to the start of the link list? Could you include the code of what you have tried so far? – Tholle Commented Jul 18, 2018 at 23:08
  • @Tholle Sure, it has been added. – user7885981 Commented Jul 18, 2018 at 23:11
  • Thanks. Have you looked into something like React Router? I think your solution is good, but it might be difficult to scale up to more pages and nested pages. – Tholle Commented Jul 18, 2018 at 23:14
  • 1 No, I'll take a look tomorrow. Thanks. – user7885981 Commented Jul 18, 2018 at 23:16
Add a ment  | 

3 Answers 3

Reset to default 11

This is just a really simple example of switching out ponents, there are many many ways to do it, and I'm sure you will get better practice answers but hopefully, this gives you some ideas. (in case it is not obvious, you would use this by calling setState({currentComponent: 'pX'}))

getComponent(){
    let ponent;
    switch (this.state.currentComponent){
        case 'pA' :
            ponent = <CompA/>;
            break;
        case 'pB' :
            ponent = <CompB/>;
            break;
        case 'pC' :
            ponent = <CompC/>;
            break;
        case 'pD' :
            ponent = <CompD/>;
            break;
    }
    return ponent;
}

render(){
    return(
        <div>
            {this.getComponent()}
        </div>
    );
}

(Shameless plug ing)

I wrote a blog post about dynamically loading React ponents.
https://www.slightedgecoder./2017/12/03/loading-react-ponents-dynamically-demand/#case1

Refer to the 1st case where you load a ponent dynamically using dynamic import().

The gist is that you create a ponent as a state, and update it depending on the type (name of module in my blog) passed to addComponent.

The benefit of this approach is that, browser won't download ponent that's not needed.

In your case, if nobody clicks on About page, that ponent is never downloaded.

addComponent = async type => {
  console.log(`Loading ${type} ponent...`);

  import(`./ponents/${type}.js`)
    .then(ponent =>
      this.setState({
        ponents: this.state.ponents.concat(ponent.default)
      })
    )
    .catch(error => {
      console.error(`"${type}" not yet supported`);
    });
};

TLDR: inside the render() method where the main body of your app is built, dynamically insert the React ponent that coincides with the current ponent/module/page that you are trying to display to the user.

You can absolutely use React Router. It's well-established and widely-used. But you can totally do this with without React Router if you wish. I'm also building a single-page app and I'm also swapping out ponents as you've described. Here are the two main files that acplish that:

template.default.js:

// lots o' imports up here...

// styles/themes that are needed to support the drawer template

class DefaultTemplate extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            mainHeight : 200,
            mobileOpen : false,
            toolbarSpacerHeight : 100,
        };
        session[the.session.key.for.DefaultTemplate] = this;
        session.browser = detect();
    }

    getModule() {
        // here I'm switching on a session variable (which is available throughout the entire app to determine which module ID the user has currently chosen
        // notice that the return values are the dynamic React ponents that coincide with the currently-chosen module ID 
        switch (session.DisplayLayer.state.moduleId) {
            case the.module.id.for.home:
                return <HomeModule/>;
            case the.module.id.for.lists:
                return <ListsModule/>;
            case the.module.id.for.login:
                return <LogInModule/>;
            case the.module.id.for.logout:
                return <LogOutModule/>;
            case the.module.id.for.register:
                return <RegisterModule/>;
            case the.module.id.for.roles:
                return <RolesModule/>;
            case the.module.id.for.teams:
                return <TeamsModule/>;
            case the.module.id.for.users:
                return <UsersModule/>;
            default:
                return null;
        }
    }

    handleDrawerToggle = () => {
        this.setState({mobileOpen : !this.state.mobileOpen});
    };

    render() {
        // the module is dynamically generated every time a render() is invoked on this template module 
        const module = this.getModule();
        return (
            <div className={classes.root} style={{height : the.style.of.percent.hundred}}>
                <AppBar className={classes.appBar} style={{backgroundColor : the.color.for.appBar}}>
                    <Toolbar>
                        <IconButton
                            aria-label={the.ariaLabel.openDrawer}
                            className={classes.navIconHide}
                            color={the.style.of.inherit}
                            onClick={this.handleDrawerToggle}
                        >
                            <MenuIcon/>
                        </IconButton>
                        <FontAwesome name={the.icon.for.palette} style={{marginRight : '10px', fontSize : the.style.of.onePointFiveEms}}/>
                        <Typography variant={the.variant.of.title} color={the.style.of.inherit} noWrap>
                            <TranslatedText english={'Groupware'}/>.<TranslatedText english={'Studio'}/>
                        </Typography>
                        <LanguageMenu
                            containerStyle={{marginLeft : the.style.of.margin.auto}}
                            onClose={event => {this.updateLanguage(event)}}
                            selectedLanguageId={db.getItem(the.db.item.for.languageId)}
                        />
                    </Toolbar>
                </AppBar>
                <Hidden mdUp>
                    <Drawer
                        anchor={theme.direction === the.direction.of.rightToLeft ? the.direction.of.right : the.direction.of.left}
                        classes={{paper : classes.drawerPaper}}
                        ModalProps={{keepMounted : true}}
                        onClose={this.handleDrawerToggle}
                        open={this.state.mobileOpen}
                        variant={the.variant.of.temporary}
                    >
                        {drawer}
                    </Drawer>
                </Hidden>
                <Hidden smDown implementation={the.implementation.of.css}>
                    <Drawer
                        classes={{paper : classes.drawerPaper}}
                        open
                        variant={the.variant.of.permanent}
                    >
                        {drawer}
                    </Drawer>
                </Hidden>
                <main
                    className={classes.content}
                    ref={main => this.main = main}
                    style={{backgroundColor : the.color.for.module.background}}
                >
                    <div
                        className={classes.toolbar}
                        ref={toolbarSpacer => this.toolbarSpacer = toolbarSpacer}
                    />
                    {/*
                        here is where that dynamically-generated module is rendered inside the template 
                    */}
                    {module}
                </main>
            </div>
        );
    }
}

export default withStyles(styles, {withTheme : true})(DefaultTemplate);

And navigation.left.js:

// lots o' imports up here 

class LeftNavigation extends React.Component {
    listButtons = [];
    // this object controls the configuration of the nav links that show on the left side of the template
    navigation = {
        isLoggedIn : [
            {
                icon : the.icon.for.home,
                isFollowedByDivider : false,
                label : the.label.for.home,
                moduleId : the.module.id.for.home,
            },
            {
                icon : the.icon.for.powerOff,
                isFollowedByDivider : true,
                label : the.label.for.logOut,
                moduleId : the.module.id.for.logout,
            },
            {
                icon : the.icon.for.orderedList,
                isFollowedByDivider : false,
                label : the.label.for.lists,
                moduleId : the.module.id.for.lists,
            },
            {
                icon : the.icon.for.roles,
                isFollowedByDivider : false,
                label : the.label.for.roles,
                moduleId : the.module.id.for.roles,
            },
            {
                icon : the.icon.for.teams,
                isFollowedByDivider : false,
                label : the.label.for.teams,
                moduleId : the.module.id.for.teams,
            },
            {
                icon : the.icon.for.users,
                isFollowedByDivider : false,
                label : the.label.for.users,
                moduleId : the.module.id.for.users,
            },
        ],
        isLoggedOut : [
            {
                icon : the.icon.for.home,
                isFollowedByDivider : false,
                label : the.label.for.home,
                moduleId : the.module.id.for.home,
            },
            {
                icon : the.icon.for.powerOff,
                isFollowedByDivider : false,
                label : the.label.for.logIn,
                moduleId : the.module.id.for.login,
            },
            {
                icon : the.icon.for.registered,
                isFollowedByDivider : false,
                label : the.label.for.register,
                moduleId : the.module.id.for.register,
            },
        ],
    };

    populateListButtons() {
        // here we are generating an array of ListButtons that will prise the left-hand navigation 
        this.listButtons = [];
        let buttonConfigs = [];
        switch (db.getItem(the.db.item.for.isLoggedIn)) {
            case true:
                buttonConfigs = this.navigation.isLoggedIn;
                break;
            case false:
                buttonConfigs = this.navigation.isLoggedOut;
                break;
            default:
                return;
        }
        buttonConfigs.forEach(buttonConfig => {
            let buttonIsEnabled = true;
            let fontAwesomeStyle = {fontSize : the.style.of.onePointFiveEms};
            let listItemStyle = {};
            let textStyle = {};
            switch (buttonConfig.label) {
                case the.label.for.logIn:
                    fontAwesomeStyle[the.style.property.name.of.color] = the.color.for.success;
                    break;
                case the.label.for.logOut:
                    fontAwesomeStyle[the.style.property.name.of.color] = the.color.for.error;
                    break;
                default:
                    if (session.DisplayLayer.state.moduleId === buttonConfig.moduleId) {
                        fontAwesomeStyle[the.style.property.name.of.color] = the.color.for.white.text;
                    } else {
                        fontAwesomeStyle[the.style.property.name.of.color] = the.color.for.headerBar;
                    }
                    break;
            }
            if (session.DisplayLayer.state.moduleId === buttonConfig.moduleId) {
                buttonIsEnabled = false;
                listItemStyle[the.style.property.name.of.backgroundColor] = the.color.for.selectedLeftNavButtonOrange;
                textStyle[the.style.property.name.of.color] = the.color.for.white.text;
            }
            this.listButtons.push(
                <ListItem
                    button={buttonIsEnabled}
                    key={`${buttonConfig.label}-listItem`}
                    // notice that when one of the left nav links is clicked, we are updating the moduleId value in session, 
                    // which dynamically determines which module shows up in the center panel
                    onClick={() => session.DisplayLayer.updateModuleId(buttonConfig.moduleId)}
                    style={listItemStyle}
                >
                    <ListItemIcon>
                        <FontAwesome name={buttonConfig.icon} style={fontAwesomeStyle}/>
                    </ListItemIcon>
                    <TranslatedText english={buttonConfig.label} style={textStyle}/>
                </ListItem>,
            );
            if (buttonConfig.isFollowedByDivider) {
                this.listButtons.push(<Divider key={`${buttonConfig.label}-divider`}/>);
            }
        });
    }

    render() {
        // dynamically generate the array of left nav buttons before rendering the links 
        this.populateListButtons();
        return <List style={{paddingTop : the.style.of.pixels.zero}}>{this.listButtons}</List>;
    }
}

export default LeftNavigation;
发布评论

评论列表(0)

  1. 暂无评论