Aman Gupta
Aman Gupta

Reputation: 3

React Router Link is changing URL but the component remains same

So I'm stuck in a problem where when clicking on the Link the URL is changing but the view remains the same until I refresh the page.

I have looked into many solutions and the only thing working is to forcefully reload the page which I don't want as React is a SPA(Single Page Application). I have tried history.push() and Link both but the output remains the same. This is my repo if you need to look at other files.

App.js

import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';

import Header from './components/header/header.component';

import HomePage from './pages/homepage/homepage.component';
import Details  from './pages/detail/detail.component';

import './App.scss';

const App = () => (
    <Router>
        <Header />
        <Switch>
            <Route exact path="/" component={HomePage} />
            <Route path="/:name" component={Details}/>
        </Switch>
    </Router>
);

Detail Component

import React from 'react';

import LinkButton from '../../components/link-button/link-button.component';

import './detail.style.scss';

class Details extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            borders: [],
            country: '',
        };
    }

    async componentDidMount() {
        const { name } = this.props.match.params;
        fetch(`https://restcountries.eu/rest/v2/name/${name}?fullText=true`)
            .then((res) => res.json())
            .then((data) => {
                this.setState({ country: data[0] });
                return data[0].borders;
            })
            .then((country) => {
                for (let i = 0; i < country.length; i++) {
                    if (i > 2) break;
                    fetch(`https://restcountries.eu/rest/v2/alpha/${country[i]}`)
                        .then((res) => res.json())
                        .then((data) =>
                            this.setState({ borders: [...this.state.borders, data.name] })
                        );
                }
            });
    }

    render() {
        const { country, borders } = this.state;
        if (country !== '') {
            return (
                <div className="detail-container">
                    <div className="detail-back-btn">
                        <LinkButton value="/">
                            <i className="fas fa-long-arrow-alt-left icon"></i> Back
                        </LinkButton>
                    </div>
                    <div className="detail-stats">
                        <img className="detail-flag" alt="Flag" src={country.flag} />
                        <div className="detail-text-container">
                            <h1 className="heading">{country.name}</h1>
                            <div className="detail-text">
                                <div className="left">
                                    <p className="text">
                                        Native Name: <span>{country.nativeName}</span>
                                    </p>
                                    <p className="text">
                                        Population:
                                        <span>{country.population.toLocaleString()}</span>
                                    </p>
                                    <p className="text">
                                        Region: <span>{country.region}</span>
                                    </p>
                                    <p className="text">
                                        Sub Region: <span>{country.subregion}</span>
                                    </p>
                                    <p className="text">
                                        Capital: <span>{country.capital}</span>
                                    </p>
                                </div>
                                <div className="right">
                                    <p className="text">
                                        Top Level Domain: <span>{country.topLevelDomain}</span>
                                    </p>
                                    <p className="text">
                                        Currencies:{' '}
                                        <span>
                                            {country.currencies.map((e) => e.name).join(', ')}
                                        </span>
                                    </p>
                                    <p className="text">
                                        Languages:{' '}
                                        <span>
                                            {country.languages.map((e) => e.name).join(', ')}
                                        </span>
                                    </p>
                                </div>
                            </div>

                            <div className="border">
                                <p className="border-text">Border Countries:</p>
                                <span className="border-btn">
                                    {borders.map((border, index) => (
                                        <LinkButton key={index} value={border}>
                                            {border}
                                        </LinkButton>
                                    ))}
                                </span>
                            </div>
                        </div>
                    </div>
                </div>
            );
        } else return null;
    }
}

export default Details;

LinkButton Component

import React from 'react';
import { Link, withRouter } from 'react-router-dom';

import './link-button.style.scss';

//this is the brute force reload solution
//which is working, but i need a better approach
function refreshPage() {
    setTimeout(() => {
        window.location.reload(false);
    }, 0);
    console.log('page to reload');
}

const LinkButton = ({ value, children, history, match, location }) => {
    console.log(history, match, location);
    return (
        <Link to={value} onClick={refreshPage}>
            <button
                className="link-btn"
                value={value}
            >
                {children}
            </button>
        </Link>
    );
};

export default withRouter(LinkButton);

Upvotes: 0

Views: 1963

Answers (1)

Brian Thompson
Brian Thompson

Reputation: 14355

What you're seeing is a change from one URL to another, but they both match the same Route path so react-router will not re-mount the component. This is intentional, and also makes sense if you think about the purpose of the Switch component.

For example: "/a" and "/b" both match <Route path="/:name" component={Details}/>. So when changing from one to another, there's no reason for react-router to re-mount the Details component because it still matches.

To accomplish what you are trying to do, you will need to listen to updates in the route parameter (the name prop).

One strategy for this is to use the componentDidUpdate lifecycle method to check if the value has changed:

componentDidUpdate(prevProps) {
  if (this.props.match.params.name !== prevProps.match.params.name) {
    // Refetch your data here because the "name" has changed.
  }
}

Note that componentDidUpdate is not called on the initial render so you will need both lifecycle methods. However, you could pull out your fetch call into it's own method so that both your componentDidMount and componentDidUpdate can reuse the same code.

For those using functional components with hooks, this listening process becomes a little easier. Using a useEffect hook with the route parameter as the dependency will accomplish the same thing as both lifecycle methods in the class version.

const { name } = useParams();

useEffect(() => {
  // Fetch data
}, [name]);

Upvotes: 9

Related Questions