简体   繁体   中英

React JavaScript prevent component unmount

I'm new to React JavaScript. Previously I'm a desktop app developer with WPF and I uses Frame and Page extensively for my desktop apps. I want to achieve almost the same thing with React JavaScript. I want to make a bottom tab bar with React JavaScript. Here is my current work:

App.js :

import './App.css';
import React, { Component } from 'react';
import BottomTabBar from './BottomTabBar/BottomTabBar';
import HomePage from './HomePage/HomePage';

class App extends Component {
  render() {
    let menuItems = [];

    menuItems.push({
      label: 'Home',
      faIcon: 'fas fa-home',
      content: (
        <HomePage/>
      )
    });

    menuItems.push({
      label: 'Numbers',
      faIcon: 'fas fa-ellipsis-h',
      content: (
        <h1>
          This is numbers page.
        </h1>
      )
    });

    menuItems.push({
      label: 'Notifications',
      faIcon: 'fas fa-bell',
      content: (
        <h1>
          This is notifications page.
        </h1>
      )
    });

    menuItems.push({
      label: 'Menu',
      faIcon: 'fas fa-bars',
      content: (
        <h1>
          This is menu page.
        </h1>
      )
    });

    return (
      <div 
        className='App'
      >
        <BottomTabBar
          menuItems={menuItems}
        />
      </div>
    );
  }
}

export default App;

BottomTabBar.js :

import './BottomTabBar.css';
import '../Ripple.css';
import React, { Component } from 'react';

class BottomTabBar extends Component {
  constructor() {
    super();

    this.state = {
      content: null,
      selectedTabIndex: 0,
    };
  }

  handleClick = (index) => {
    // Changing content.
    this.setState({
      selectedTabIndex: index
    });
  }

  render() {
    // Putting them all.
    return (
      <div
        className='BottomTabBar'
      >
        <div
          className='Content'
        >
          {this.props.menuItems[this.state.selectedTabIndex].content}
        </div>

        <div
          className='IconsBar'
        >
          {
            this.props.menuItems.map((menuItem, i) => {
              return (
                <div
                  className="MenuItem Ripple"
                  key={i}
                  onClick={()=>this.handleClick(i)}
                >
                  <div
                    className="Gap"
                  />
                  <div
                    className="Icon"
                  >
                    <div
                      className={menuItem.faIcon}
                    />
                  </div>
                  <div
                    className="Gap"
                  />
                  <div
                    className="Text"
                  >
                    {menuItem.label}
                  </div>
                </div>
              )
            })
          }
        </div>
      </div>
    );
  }
}

export default BottomTabBar;

HomePage.js :

import './HomePage.css';
import React, { Component } from 'react';

class HomePage extends Component {
  constructor() {
    super();

    this.state = {
      counter: 0,
    };
  }

  componentDidMount() {
    this.counterInterval = setInterval(() => {
      this.setState((prevState) => ({
        counter: prevState.counter + 1
      }));
    }, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.counterInterval);
  }

  render() {
    return (
      <div
        className='HomePage'
      >
        Home page counter: {this.state.counter}
      </div>
    );
  }
}

export default HomePage;

As you might know from the codes above, my homepage is just a simple page with automatic counter increment per second:

Normal home page:
普通首页

The problem is my HomePage can't be persistent. If I tried to change between tabs, it works but the counter at the home page is always reset to 0, meaning my home page and the other pages are recreated just because it is navigated between tabs. That's very weird to me as I created the <HomePage/> at the App.js line 14. What I'm doing wrong? Feel free to ask me if you need more details.

Maybe what I'm trying to do is more like this library:

https://github.com/lishengzxc/react-no-unmount-hide

I'm not really sure what that library does but I tried it and it doesn't work (just throws some weird error)

It's expected because new HomePage instance is created each time Home tab is navigated.

This is the case for lifting the state up . If counter state should persist, it should be moved to a component that isn't destroyed on tab navigation, ie App :

class App extends Component {
  state = { counter: 0 };

  incrementCounter = () => setState((state) => ({ counter: state.counter + 1 }));

  render() {
    let menuItems = [];

    menuItems.push({
      label: 'Home',
      faIcon: 'fas fa-home',
      content: (
        <HomePage counter={this.state.counter} increment={this.incrementCounter} />
      )
    });
    ...

These props should be used instead of local HomePage state:

this.counterInterval = setInterval(this.props.increment);

and

Home page counter: {this.props.counter}

An alternative is to use Redux or React context API to manage global application state.

You should use the react-router-dom components for Linking and Navigation purposes.

This Project is based on something you want to achieve. Check it out

https://github.com/onejeet/image-sharing-app

After struggling for hours, i finally managed to do it based on my idea. Here is the code for those who curious:

App.js:

import './App.css';
import React, { Component } from 'react';
import BottomTabBar from './BottomTabBar/BottomTabBar';
import HomePage from './HomePage/HomePage';
import NumbersPage from './NumbersPage/NumbersPage';

class App extends Component {
  render() {
    let menuItems = [];

    menuItems.push({
      label: 'Home',
      faIcon: 'fas fa-home'
    });

    menuItems.push({
      label: 'Numbers',
      faIcon: 'fas fa-ellipsis-h'
    });

    return (
      <div 
        className='App'
      >
        <BottomTabBar
          menuItems={menuItems}
        >
          <HomePage/>
          <NumbersPage/>
        </BottomTabBar>
      </div>
    );
  }
}

export default App;

BottomTabBar.js:

import './BottomTabBar.css';
import '../Ripple.css';
import React, { Component } from 'react';

class BottomTabBar extends Component {
  constructor() {
    super();

    this.state = {
      selectedIndex: 0,
    };

    console.log('Initialized bottom tab bar');
  }

  handleClick = (index) => {
    // Changing content.
    this.setState({
      selectedIndex: index
    });
  }

  render() {
    return (
      <div
        className='BottomTabBar'
      >
        <div
          className='Content'
        >
          {
            this.props.children.map((child, i) => {
              return (
                <div
                  style={{
                    display: i == this.state.selectedIndex
                    ? 'block'
                    : 'none'
                  }}
                  key={i}
                >
                  {child}
                </div>
              )
            })
          }
        </div>

        <div
          className='IconsBar'
        >
          {
            this.props.menuItems.map((menuItem, i) => {
              return (
                <div
                  className="MenuItem Ripple"
                  key={i}
                  onClick={()=>this.handleClick(i)}
                >
                  <div
                    className="Gap"
                  />
                  <div
                    className="Icon"
                  >
                    <div
                      className={menuItem.faIcon}
                    />
                  </div>
                  <div
                    className="Gap"
                  />
                  <div
                    className="Text"
                  >
                    {menuItem.label}
                  </div>
                </div>
              )
            })
          }
        </div>
      </div>
    );
  }
}

export default BottomTabBar;

HomePage.js:

import './HomePage.css';
import React, { Component } from 'react';

class HomePage extends Component {
  constructor() {
    super();

    this.state = {
      counter: 0,
    };

    console.log('Initialized home page');
  }

  componentDidMount() {
    this.counterInterval = setInterval(() => {
      this.setState((prevState) => ({
        counter: prevState.counter + 1
      }));
    }, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.counterInterval);
  }

  render() {
    return (
      <div
        className='HomePage'
        style={this.props.style}
      >
        Home page counter: {this.state.counter}
      </div>
    );
  }
}

export default HomePage;

Note: NumbersPage.js is just basically a page with very long lorem ipsum text.

Thats it, all states preserved without lifting. The scrolling position on NumbersPage.js is not preserved because i played with css display property there and thats normal :)

Thanks for the helps!

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