0

I am trying to switch between two pages, but ran into many problems whiling learning react router

Within my App.js, there is a bottom, which will navigate me to Cart when clicked.

  render()  {
    return (
      <div>
        <button type="button" className="btn btn-primary Bottom-right " onClick={(e) => this.handleSubmit(e)}>
              Confirmed Order
        </button>
        <button type="button" className="btn btn-Primary">
          <Link to="/displayCart" >Show Car</Link>
        </button>
      </div>
    );
  }
}

export default App;

In my cart component, also have similar button that navigtaes me back to the App component.

class Cart extends React.Component {
    constructor(props){
        super(props)

        this.state={
            cartData:null
        }
    }

    componentDidMount() {
        this.callApi()
          .then(res => this.setState({ cartData: res.express}))
          .catch(err => console.log(err));
      }

      callApi = async () => {
          const response = await fetch('/myCart');
          const body = await response.json();
          if (response.status !== 200) throw Error(body.message);

          return body;
      };

    render(){
        return(
            <div className="col">
            <div className="App">
                <header className="App-header">
                    <img src={logo} className="App-logo" alt="logo" />
                    <h1 className="App-title">Welcome to Shopping Center</h1>
                </header>
            </div>
                <div>{ReactHtmlParser(this.state.cartData)}</div>
                <button type="button Bottom-right" className="btn btn-Primary">
                    <Link to="/" >Keep Shopping</Link>
                </button>
            </div>
        )
    }
}

My index.js contains two components, app JS being the default component.

    ReactDOM.render(
        <BrowserRouter>
            <switch>
                <div>
                    <Route exact path="/displayCart" component={Cart}/>
                    <Route component={App} />
                </div>
            </switch>
        </BrowserRouter>,
        document.getElementById('root')
    );

Now they are really flaw in many levels:

1: When I clicked show cart, instead of rendering the new view. The cart component is rendered on the top of the view with App still rendered.

2: After clicking the Show Cart , I clicked the Show App to get back to my App Component, however the console prints:

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in Cart (created by Route)

3: If I changed the state in App component when I get back to App component from Cart component, the state did not get refreshed. For example (For example, the Boolean value "purchase" in items.) When I get back from App component from Cart, the state stays true, instead, I want it to get back to the initial state.

I followed the tutorial but it is just broken. What did i do wrong

Spencer Ovetsky
  • 111
  • 4
  • 12

1 Answers1

1

So the warning is because setState is being called by <Cart /> even after you go back to the App which means that the cart component is unmounted by then.

You should keep track of when Cart becomes unmounted.

You can do this by creating a variable in your Cart component such as _isMounted and then using the lifecycle event componentWillUnmount to keep track.

componentWillUnmount(){
   _isMounted =false
}

Then just before you use setState, do a quick check to make sure _isMounted is true.

this.callApi()
          .then(res => if(isMounted){
                         this.setState({ cartData: res.express}))
                       }
          .catch(err => console.log(err));

Obviously you need to also change _isMounted to true using ComponentDidMount

Here is an article on this topic


In regards to App's state not 'refreshing'... it is because the App component never unmounts and mounts again as you go from App > Cart > App.

To fix this, tell switch to only mount the <App /> component when the url is exactly matching the homepage using exact path="/"

        <switch>
            <div>
                <Route exact path="/displayCart" component={Cart}/>
                <Route exact path="/" component={App} />
            </div>
        </switch>

Now, if you want to keep track of the state changes even if is closed (unmounted) before the API call finishes then you should create a function inside of <App/> that does the setState for you and pass it as a prop to <Cart /> and then from inside You can call this prop function.

This will only work though if you put your React Router stuff inside of

The way I do it is this: - index.js always loads - Inside of I then redirect to the show the proper component

This means that is always mounted. However it lets me use App's state as the default "main" state that always exists since I never unmount it. So if I have a child component like ... I can give Cart one of 's functions. This function will change the state within regardless of whether is still mounted/shown.

If you restructure you app like that then you can do the following:

App.js Add a function that will change it's state.

changeState(key, value){
   this.setState({
      key : value
   }
}

Now pass this function as a prop

    <BrowserRouter>
        <switch>
            <div>
                <Route exact 
                       path="/displayCart" 
                       render={
                         (props) => <Cart {...props} 
                         changeStateinApp={this.changeState} /> 
                       }
                  />
                <Route component={App} />
            </div>
        </switch>
    </BrowserRouter>,

Cart.js Now call this function

this.callApi()
          .then(res => 
              this.props.changeStateinApp(cartData, res.express)
          .catch(err => console.log(err));
Badrush
  • 1,003
  • 10
  • 29
  • OK, i got rid of the error. What about the problem of new component does not render on a complete new view? – Spencer Ovetsky Sep 28 '18 at 17:45
  • I updated my answer. I personally always use the component as the base empty view. And then render one more view inside of it at a time. – Badrush Sep 28 '18 at 17:48
  • I dont want to change Cart's state in App tho. These two component will fetch from two different urls. And i dont think that the reason why Cart.js only mount on exist on is because of it. – Spencer Ovetsky Sep 28 '18 at 17:50
  • Once Cart is unmounted (you go back to App) you can't change its state. Also App cannot access Cart's state. – Badrush Sep 28 '18 at 17:52
  • Look at this answer from another user's question. https://stackoverflow.com/questions/46632830/react-how-to-access-childs-state-from-parent-no-need-to-update-parents-state?rq=1 – Badrush Sep 28 '18 at 17:53
  • I think in #3 you meant that you setState in App.js and then when you go App > Cart > App, that state you set is missing from App? – Badrush Sep 28 '18 at 18:00
  • lets say one of the state in App is false. I set it to true. And then i go to cart, and then from cart i go back to App again. The state is still true, however, i want it to go back to its original state. – Spencer Ovetsky Sep 28 '18 at 18:05
  • Also, using your above code, I got changeStateinApp is not a function error – Spencer Ovetsky Sep 28 '18 at 18:05
  • Which component gives you that error? when you try to pass it down or when you call it? – Badrush Sep 28 '18 at 18:08
  • Your state in persists because it is never unmounted. Try adding `exact path="/"` to your `` and it might unmount for you. – Badrush Sep 28 '18 at 18:12
  • yeah adding "exact" fixed both 1 and 3. Now just 2 left. – Spencer Ovetsky Sep 28 '18 at 18:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/180954/discussion-between-spencer-ovetsky-and-badrush). – Spencer Ovetsky Sep 28 '18 at 18:15