9

I've got a problem with react and react-router. When I click on a link (in my example contact in Footer.js), the url changes, but the desired component Location is not shown. When I refresh the site then, the correct component is displayed.

App.js:

import React, { Component } from 'react';
import { BrowserRouter as Router, HashRouter, Route, Link } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.css';
import Footer from './Footer.js';
import Navigation from './Navigation.js';
import Background from './Background.js';
import Home from './Home.js';
import Products from './Products.js';
import Industries from './Industries.js';
import Partner from './Partner.js';
import Location from './Location.js';
import MeetUs from './MeetUs.js';
import ScrollUp from './ScrollUp.js';
import Divider from './Divider.js';
import Country from './Country.js';
import Language from './Language.js';
import Waypoint from 'react-waypoint';
import $ from "jquery";

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      currentLanguage: 'en',
      currentBU: '',
      currentIndustry: '',
      showMainProductGroups: false,
      currentCountry: 'group',
      countryObject: Country['group'],
      contacts: [],
      mainProductGroups: [],
    };
  }

  handleCountryChange() {
  //...
  }

  handleLanguageChange() {
  //...
  }

  handleBUChange() {
  //...
  }

  render() {
    const routes = [
      { 
        path: '/',
        exact: true,
        components: () => 
          <div>
            <Home key="home" currentLanguage={this.state.currentLanguage} />
          </div>,
      },
      { 
        path: '/contact',
        exact: true,
        components: () => <Location key="locations" currentLanguage={this.state.currentLanguage} country={this.state.countryObject} contacts= {this.state.contacts} onCountryChange={this.handleCountryChange.bind(this)} />
      },
    ]
    return (
      <HashRouter>
    <div>
      <Background />
      <div id="wrap">
        <div id="main" className="container clear-top marginBottom50px">
            <div id="content">
              <Navigation key="navBar" currentLanguage={this.state.currentLanguage} onLanguageChange={this.handleLanguageChange.bind(this)} onBUChange={this.handleBUChange.bind(this)} onCountryChange={this.handleCountryChange.bind(this)} />
              {
                routes.map((route, index) => (
                <Route key={index} path={route.path} exact={route.exact} component={route.components} />
              ))
              }
            </div>
        </div>
      </div>
      <Footer key="footer" currentLanguage={this.state.currentLanguage} />
      <ScrollUp key="scrollUp" />
    </div>
  </HashRouter>
    );
  }
}

export default App;

Home.js:

import React, { Component } from 'react';
import $ from "jquery";
import {  Link } from 'react-router-dom';
import {withRouter} from 'react-router';
import Language from './Language.js';
import locations from './locations.jpg';
import locationLegend from './locationLegend.jpg';
require('bootstrap')

class Home extends Component {
    constructor(props) {
        super(props);
        this.state = {
        };
    }

    render() {
        return (
            <div className="container marginTop50px marginBottom50px area">
                <div className="row">
                    <div className="col-12 text-center animDelay2 fadeInDown animated">
                        <h1>International Distribution of Specialty Chemicals</h1>
                    </div>
                </div>
                <div className="row marginTop25px">
                    <div className="col-12 text-center animDelay2 fadeInUp animated">
                        {Language[this.props.currentLanguage].homeStartText}
                    </div>
                </div>
                <div className="row marginTop25px">
                    <div className="col-12 text-center">
                        <img src={locations} className="img-fluid" alt="Locations" />
                    </div>
                </div>
                <div className="row marginTop25px">
                    <div className="col-12 text-center">
                        <img src={locationLegend} className="img-fluid" alt="Locations" />
                    </div>
                </div>
            </div>
        );
    }
}

export default withRouter(Home);

Location.js:

import React, { Component } from 'react';
import $ from "jquery";
import { Link } from 'react-router-dom';
import Language from './Language.js';
import Country from './Country.js';
import ContactPerson from './ContactPerson.js';
import locations from './locations.png';
import phone from './phoneBlack.svg';
import fax from './faxBlack.svg';
import email from './emailBlack.svg';
import {withRouter} from 'react-router';
require('bootstrap');

class Location extends Component {
    constructor(props) {
        super(props);
        this.state = {
        };
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log('Country change:' + this.props.country.key);
        $('#selectCountry').val(this.props.country.key); //name['en']
    }

    onCountryChange() {
        let countryName = this.refs.country.value;
        this.props.onCountryChange(countryName);
    }

    render() {
        return (
            <div className="container marginTop50px marginBottom50px area" id="locations">
                <div className="row">
                    <div className="col-12 text-center">
                        <h2>{Language[this.props.currentLanguage].locations}</h2>
                    </div>
                </div>
                <div className="row marginTop25px">
                    <div className="col-12 text-center">
                        <div className="form-group">
                            <select id="selectCountry" className="form-control" ref="country" onChange={this.onCountryChange.bind(this)}>
                                <option defaultValue>{Language[this.props.currentLanguage].selectLocation.toUpperCase()}</option>
                                {
                                    Object.keys(Country).map((countryKey) => {
                                        const country = Country[countryKey];
                                        return (
                                            <option value={countryKey} key={"loc" + countryKey}>{country.name[this.props.currentLanguage].toUpperCase()}</option>
                                        );
                                    })
                                }
                            </select>
                        </div>
                    </div>
                </div>
                <div className="row marginTop25px">
                    <div className="col-12 text-center">
                        {this.props.country.name[this.props.currentLanguage].toUpperCase()}
                        <br />
                        <address>
                            <span dangerouslySetInnerHTML={{__html: this.props.country.address}}></span>
                            <br />
                            <br />
                            <img src={phone} alt="Anrufen" className="phoneMain"></img><span> </span>
                            <a href={this.props.country.phoneHTML}>{this.props.country.phone}</a>
                            <br />
                            <img src={fax} alt="Fax" className="phoneMain"></img><span> </span>
                            <a href={this.props.country.faxHTML}>{this.props.country.fax}</a>
                            <br />
                            <img src={email} alt="Email" className="emailMain"></img><span> </span>
                            <a href={"mailto://" + this.props.country.email}>{this.props.country.email}</a>
                        </address>
                    </div>
                </div>
                <div className="row marginTop25px">
                    <div className="col-12 text-center">
                        {Language[this.props.currentLanguage].vatRegistrationNumber + ": " + this.props.country.vatNo}
                        <br />
                        {Language[this.props.currentLanguage].registrationOffice + ": "}
                        <span dangerouslySetInnerHTML={{__html: this.props.country.registrationOffice}}></span>
                    </div>
                </div>
                <div className="row marginTop50px">
                    <div className="col-12 text-center">
                        <h3>{Language[this.props.currentLanguage].contact}</h3>
                    </div>
                </div>
                <div className="row">

                        {
                            this.props.contacts.map((contact) => {
                                return (
                                    <div className="col-12 col-sm-12 col-md-12 col-lg-6 text-center">
                                        <ContactPerson contact={contact} key={"contact" + contact.id} />
                                    </div>
                                );
                            })
                        }
                </div>
            </div>
        );
    }
}

export default withRouter(Location);

Footer.js:

import React, { Component } from 'react';
import $ from "jquery";
import {  Link } from 'react-router-dom';
import {withRouter} from 'react-router';
import Language from './Language.js';
import phone from './phoneWhite.svg';
import fax from './faxWhite.svg';
require('bootstrap');

class Footer extends Component {
    constructor(props) {
        super(props);
        this.state = {
        };
    }

    render() {
        return (
            <footer className="footer">
                <div className="container-fluid borderTop1px footerLayout">
                    <div className="row">
                        <div className="col-3">
                            <address>
                                <small>
                                    Some text
                                </small>
                            </address>
                        </div>
                        <div className="col-6 text-center">
                            <div className="row">
                                <div className="col-12 col-sm-12 col-md-12 col-lg-3 text-center">
                                    <a href="https://download.group.com" className="nav-link footerLink" target="_self"><small>{Language[this.props.currentLanguage].download}</small></a>
                                </div>
                                <div className="col-12 col-sm-12 col-md-12 col-lg-3 text-center">
                                    <Link to="/imprint" className="nav-link footerLink"><small>{Language[this.props.currentLanguage].imprint}</small></Link>
                                </div>
                                <div className="col-12 col-sm-12 col-md-12 col-lg-3 text-center">
                                    <Link to="/contact" className="nav-link footerLink"><small>{Language[this.props.currentLanguage].contact}</small></Link>
                                </div>
                                <div className="col-12 col-sm-12 col-md-12 col-lg-3 text-center">
                                    <Link to="/termsAndConditions" className="nav-link footerLink"><small>{Language[this.props.currentLanguage].termsAndConditions}</small></Link>
                                </div>
                            </div>
                        </div>
                        <div className="col-3">
                            <ul className="list-inline">
                                <li>
                                    <img src={phone} alt="Anrufen" className="phone"></img> <small><a className="footerLink" href="tel:+49">+49</a></small>
                                </li>
                                <li>
                                    <img src={fax} alt="Fax" className="phone"></img> <small><a className="footerLink" href="tel:+49">+49</a></small>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </footer>
        );
    }
}

export default withRouter(Footer);

What I'm doing wrong? Why it is not working, when I click on a link?

reisdev
  • 3,215
  • 2
  • 17
  • 38
dns_nx
  • 3,651
  • 4
  • 37
  • 66
  • Why does your code have multiple `HashRouter`s? – Agney Apr 27 '18 at 09:13
  • I've changed my code with only one `HashRouter`. Please see my update. It still does not work. – dns_nx Apr 27 '18 at 09:18
  • Have a look at this, it might help you : https://stackoverflow.com/questions/50208165/refreshing-page-removes-bootstrap-select-stylings/50208348#50208348 – Nyle Hassan May 15 '18 at 07:42

5 Answers5

1

Got it working now. I needed to change <HashRouter> to <Router>. Then it works fine.

UPDATE: This solution solves the problem, but then there is a different problem: When I have navigated and refresh the page, then an error (404) is thrown, because there is of course no such a page on the server.

I need to get the HashRouter work.

dns_nx
  • 3,651
  • 4
  • 37
  • 66
  • How are you deploying your website? eg. AWS S3, Netlify, Github pages? – Roy Wang May 08 '18 at 13:03
  • It is just a standard webserver hosted by apache, i assume. But the functionality is not even there when I develop the web page with `npm` and `node.js` on my development system. – dns_nx May 09 '18 at 05:20
1

When you declare your routes in App.js, you should pass the props to the component:

components: props => <Location {...props} <insert other props> />

You should stick to the <Router> solution as having unnecessary hash in the url is ugly.

When I have navigated and refresh the page, then an error (404) is thrown, because there is of course no such a page on the server.

To resolve this, you need to set up a redirect to redirect all requests to the base url for the React app to handle (the url displayed will be preserved).

On Netlify, you can create a _redirects file in your public folder with the content:

/*  /index.html  200

On AWS S3, the redirect rules can be set in S3 or CloudFront, see the answers here.

For Google Cloud bucket, see this.

For Github pages, see this.

Roy Wang
  • 11,112
  • 2
  • 21
  • 42
  • Please see my comment above. Standard web server with apache. I do not understand what exactly you mean with "you should pass the props to the component.". I'm doing it this way, don't I? Please see the `App.js` code. – dns_nx May 09 '18 at 05:22
  • In your `App.js`, you have `components: () => `. You did not define the `props` parameter and pass it to the `Location` component, like what I did above. – Roy Wang May 09 '18 at 05:29
  • See https://askubuntu.com/questions/484986/how-do-i-make-apache-serve-a-single-static-page-no-matter-what-the-entered-url-i or https://www.sej-ko.dk/2017/03/29/routing-single-page-application-on-apache-with-htaccess/ on how to redirect all routes to `index.html`. – Roy Wang May 09 '18 at 05:30
  • Can you try hosting it on another platform (Netlify etc) to see if the problem is with your Apache redirect configuration? – Roy Wang May 12 '18 at 04:20
  • But it does not work with `npm` and `node.js` in development as well. – dns_nx May 14 '18 at 11:01
  • What do you mean? The configurations are for the production environment. You don't use Apache for development. – Roy Wang May 14 '18 at 11:08
  • Nope. It does not work even in development. The problem is not the deploying / production. It does not work at all. – dns_nx May 14 '18 at 11:22
  • Of course it won't work in development, you have no such configurations in development. If you need a proof of it working in production: https://vis.wangriwu.com/Compare. The configurations are meant for production, not development. There is no point having redirects in development. – Roy Wang May 14 '18 at 11:24
  • We're talking at cross purposes. :-) I say it basically doesn't work. But it should work, even with `HashRouter`. I'm not talking about your suggested changes for Apache Webserver for the production environment. I need a general solution. – dns_nx May 14 '18 at 11:58
  • I'm not sure if I understand what you want. Your objective is to allow users to visit `example.com/some-route` directly without getting a 404 right? What do you mean by a "general" solution? – Roy Wang May 14 '18 at 12:40
  • out dated with v6 `````` no longer has an `exact` prop – ANimator120 Oct 12 '22 at 08:24
1

In your Route component you use component prop to pass the Location component (instead of render or children props available on Route) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop.However in your case it seems you are using it for no reason so you should just pass the component and not an inline function that returns it like so :

const routes = [
      { 
        path: '/',
        exact: true,
        components: <Home key="home" currentLanguage={this.state.currentLanguage}/>

      },
      { 
        path: '/contact',
        exact: true,
        components: <Location key="locations" currentLanguage={this.state.currentLanguage} country={this.state.countryObject} contacts= {this.state.contacts} onCountryChange={this.handleCountryChange.bind(this)} />
      },
    ]
Tal Goldfus
  • 151
  • 3
  • Your explanation makes sense. However, I need several components per route. My code above is simplified. For example, I have a home route with several components in it. How do I realize this without inline function? – dns_nx May 14 '18 at 05:42
  • I have tried your solution, but it does not work either. The url changes, but not the component(s). – dns_nx May 14 '18 at 06:03
1

Make your routes use Component as below

import {IndexRoute, Route} from 'react-router';

 <Route component={App}>
    <Route path='/locations' component={LocationComponent}/>
 </Route>

This is what I am doing in my current project without using HashRouter.

Currently, When you do

<Route key={index} path={route.path} exact={route.exact} component={route.components} />

I don't think {route.components} treats it as a component.

Manoz
  • 6,507
  • 13
  • 68
  • 114
  • The issue is that it is working without `HashRouter`. As I wrote, it works with standard `Router`. But then I'm facing other issues. – dns_nx May 15 '18 at 12:37
  • @dns_nx, can't you separate out your components in some `jsx` or `ts` files? this way you won't get 404 – Manoz May 16 '18 at 04:44
1

Could be a problem with withRouter().

Have you seen this? https://github.com/ReactTraining/react-router/issues/5037

aravindanve
  • 979
  • 7
  • 14
  • @dns_nx No, I mean did you read the Github issue. It says you must implement `shouldComponentUpdate` if you want components nested deep down to update on location change. https://github.com/ReactTraining/react-router/issues/5037#issuecomment-325881666 – aravindanve May 15 '18 at 17:53