Marcelo Melo
Marcelo Melo

Reputation: 107

react router changes the URL, but the component is not rendered

I have a component that consumes an API and renders a list. Each list item has a custom link to redirect to it's page.

I'm trying to make this route getting the URL params, to show the other component.

But despite the URL changes, the other component is never rendered when I click the list item. But the funny thing is that if I type the URL manually, it renders.

Here goes my code:

App.js

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

// import Routes from "./routes";
import Navbar from "./components/Navbar";
import GlobalStyle from "./styles/global";

import Main from "./pages/Main";
import Hero from "./pages/Hero";
import Favorites from "./pages/Favorites";

const App = () => (
  <Router>
    <GlobalStyle />
    <Navbar />
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/favorites">Favoritos</Link>
      </li>
    </ul>
    <Switch>
      {/* <Main path="/hero/:heroId" component={Hero} /> */}
      <Route path="/hero" component={Hero} />
      <Route path="/favorites" component={Favorites} />
      <Route path="/" exact component={Main} />

      {/* <Route path="/hero" render={props => <Hero {...props} />} /> */}
    </Switch>
  </Router>
);
export default App;

Main.js

import React, { Component } from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import api from "../../services/api";
import md5 from "js-md5";

import { SearchBar, SearchInput, SearchButton, Wrapper } from "./styles";

import Card from "../../components/Card";

export default class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      heroes: [],
      search: ""
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  componentDidMount() {
    this.loadHeroes();
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.searc !== this.state.search) {
      this.loadHeroes();
    }
  }

  loadHeroes = async () => {
    const PUBLIC_KEY = process.env.REACT_APP_PUBLIC;
    const PRIVATE_KEY = process.env.REACT_APP_PRIVATE;
    const timestamp = Number(new Date());
    const hash = md5.create();
    hash.update(timestamp + PRIVATE_KEY + PUBLIC_KEY);

    if (this.state.search === "") {
      await api
        .get(`/characters?ts=${timestamp}&apikey=${PUBLIC_KEY}&hash=${hash}`)
        .then(response =>
          this.setState({ heroes: response.data.data.results })
        );
    } else {
      await api
        .get(
          `/characters?nameStartsWith=${this.state.search}&ts=${timestamp}&apikey=${PUBLIC_KEY}&hash=${hash}`
        )
        .then(response =>
          this.setState({ heroes: response.data.data.results })
        );
    }
  };

  handleChange(event) {
    this.setState({ search: event.target.value });
  }

  handleSubmit(event) {
    console.log("State do search: ", this.state.search);
    event.preventDefault();
  }

  render() {
    const { heroes } = this.state;

    let filteredHeroes = heroes.filter(
      hero => hero.name.toLowerCase().indexOf(this.state.search) !== -1
    );

    return (
      <div>
        <SearchBar onSubmit={this.handleSubmit}>
          <label>
            Buscar
            <SearchInput
              onChange={this.handleChange}
              type="search"
              value={this.state.search}
            />
          </label>
          <SearchButton type="submit" value="Enviar" />
        </SearchBar>

        <Router>
          <Wrapper>
            {filteredHeroes.map(hero => {
              return (
                <Link
                  to={`hero?q=${hero.id}`}
                  style={{ textDecoration: "none" }}
                  key={hero.id}
                >
                  <Card name={hero.name} thumbnail={hero.thumbnail} />
                </Link>
              );
            })}
          </Wrapper>
        </Router>
      </div>
    );
  }
}

Hero.js

import React from "react";

const Hero = props => {
  return (
    <div>
      <h1>{props.name}</h1>

      <article>
        <p>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Minima
          accusamus ducimus qui amet quis? Non rerum consequuntur soluta,
          voluptatum blanditiis explicabo, laudantium architecto distinctio enim
          aliquid placeat, quaerat voluptas totam!
        </p>
      </article>
    </div>
  );
};

export default Hero;

Upvotes: 4

Views: 5381

Answers (2)

Danish
Danish

Reputation: 912

Make following changes in your code !

App.js

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

// import Routes from "./routes";
import Navbar from "./components/Navbar";
import GlobalStyle from "./styles/global";

import Main from "./pages/Main";
import Hero from "./pages/Hero";
import Favorites from "./pages/Favorites";

const App = () => (
  <Router>
    <GlobalStyle />
    <Navbar />
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/favorites">Favoritos</Link>
      </li>
    </ul>
    <Switch>
      <Route path="/" exact component={Main} />
      <Route path="/hero" exact component={Hero} />
      <Route path="/favorites" exact component={Favorites} />
    </Switch>
  </Router>
);
export default App;

Main.js

import React, { Component } from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import api from "../../services/api";
import md5 from "js-md5";

import { SearchBar, SearchInput, SearchButton, Wrapper } from "./styles";

import Card from "../../components/Card";

export default class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      heroes: [],
      search: ""
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  componentDidMount() {
    this.loadHeroes();
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.searc !== this.state.search) {
      this.loadHeroes();
    }
  }

  loadHeroes = async () => {
    const PUBLIC_KEY = process.env.REACT_APP_PUBLIC;
    const PRIVATE_KEY = process.env.REACT_APP_PRIVATE;
    const timestamp = Number(new Date());
    const hash = md5.create();
    hash.update(timestamp + PRIVATE_KEY + PUBLIC_KEY);

    if (this.state.search === "") {
      await api
        .get(`/characters?ts=${timestamp}&apikey=${PUBLIC_KEY}&hash=${hash}`)
        .then(response =>
          this.setState({ heroes: response.data.data.results })
        );
    } else {
      await api
        .get(
          `/characters?nameStartsWith=${this.state.search}&ts=${timestamp}&apikey=${PUBLIC_KEY}&hash=${hash}`
        )
        .then(response =>
          this.setState({ heroes: response.data.data.results })
        );
    }
  };

  handleChange(event) {
    this.setState({ search: event.target.value });
  }

  handleSubmit(event) {
    console.log("State do search: ", this.state.search);
    event.preventDefault();
  }

  render() {
    const { heroes } = this.state;

    let filteredHeroes = heroes.filter(
      hero => hero.name.toLowerCase().indexOf(this.state.search) !== -1
    );

    return (
      <div>
        <SearchBar onSubmit={this.handleSubmit}>
          <label>
            Buscar
            <SearchInput
              onChange={this.handleChange}
              type="search"
              value={this.state.search}
            />
          </label>
          <SearchButton type="submit" value="Enviar" />
        </SearchBar>
          <Wrapper>
            {filteredHeroes.map(hero => {
              return (
                <Link
                  to={`/hero?q=${hero.id}`}
                  style={{ textDecoration: "none" }}
                  key={hero.id}
                >
                  <Card name={hero.name} thumbnail={hero.thumbnail} />
                </Link>
              );
            })}
          </Wrapper>
      </div>
    );
  }
}

and no need to change in Herocomponent.

Upvotes: 2

Udit
Udit

Reputation: 393

you need to make two changes

in you Main.js

                <Link
                  to={`hero/${hero.id}`}
                  style={{ textDecoration: "none" }}
                  key={hero.id}
                >
                  <Card name={hero.name} thumbnail={hero.thumbnail} />
                </Link>

and in your routes, add another route or change existing to

<Route path="/hero/:heroId" component={Hero} />

Hope this would help. Extract your heroID using

 props.match.params.heroId

Upvotes: 0

Related Questions