Show search result component in another route - reactjs

I am working with the MovieDB API. I want to show now playing movies on the root route but search result in another route.
I have tried putting history.push() method in handlesubmit but it shows error. Here's the code. Currently I am showing search result component in the home page itself.
App.js
import React, { Component } from "react";
import "./App.css";
import { BrowserRouter, Link, Switch, Route } from "react-router-dom";
import Nav from "./component/Nav";
import axios from "axios";
import { Provider } from "./context";
import Home from "./component/Home";
import SearchResult from "./component/SearchResult";
import MovieDetails from "./component/movieDetails";
class App extends Component {
state = {
movieList: [],
searchResult: [],
currentpage: 1,
totalpage: 1,
API_KEY: "c51081c224217a3989b0bc0c4b3d3fff"
};
componentDidMount() {
this.getCurrentMovies();
}
getCurrentMovies = e => {
axios
.get(
`https://api.themoviedb.org/3/movie/now_playing?api_key=${
this.state.API_KEY
}&language=en-US&page=${this.state.currentpage}`
)
.then(res => {
this.setState({
movieList: res.data.results,
currentpage: res.data.page,
totalpage: res.data.total_pages
});
console.log(this.state);
});
};
getMovies = e => {
e.preventDefault();
const moviename = e.target.elements.moviename.value;
axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${
this.state.API_KEY
}&query=${moviename}`
)
.then(res => {
this.setState({
searchResult: res.data.results
});
console.log(this.state.searchResult);
});
console.log(this.router);
};
nextPage = () => {
this.setState(
{
currentpage: (this.state.currentpage += 1)
},
() => console.log(this.state.currentpage)
);
this.getCurrentMovies();
};
prevPage = () => {
if (this.state.movieList && this.state.currentpage !== 1) {
this.setState(
{
currentpage: (this.state.currentpage -= 1)
},
() => console.log(this.state.currentpage)
);
this.getCurrentMovies();
}
};
render() {
const contextProps = {
myState: this.state,
getMovies: this.getMovies,
nextPage: this.nextPage,
prevPage: this.prevPage,
};
return (
<Provider value={contextProps}>
<BrowserRouter>
<Nav />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/:id" component={MovieDetails} />
</Switch>
</BrowserRouter>
</Provider>
);
}
}
export default App;
Home.js
import React, { Component } from "react";
import NowPlaying from "./NowPlaying";
import SearchResult from "./SearchResult";
import SearchBox from "./SearchBox";
class Home extends Component {
state = {};
render() {
return (
<div>
<SearchBox />
<SearchResult />
<NowPlaying />
</div>
);
}
}
export default Home;
SearchBox.js
import React, { Component } from "react";
import { MyContext } from "../context";
import { withRouter } from "react-router-dom";
class SearchBox extends Component {
static contextType = MyContext;
render() {
return (
<React.Fragment>
<div className="jumbotron jumbotron-fluid">
<div className="container" style={{ textAlign: "center" }}>
<h1 className="display-4">Find your Movie</h1>
<p className="lead">
Find rating, descrips and much more of your fev. movie.
</p>
<form onSubmit={this.context.getMovies}>
<input
name="moviename"
className="form-control mr-sm-2"
type="search, submit"
placeholder="Search"
aria-label="Search"
style={{ height: "50px" }}
/>
</form>
</div>
</div>
<div />
</React.Fragment>
);
}
}
export default withRouter(SearchBox);
SearchResult.js
import React, { Component } from "react";
import Movie from "./movie";
import { withRouter } from "react-router-dom";
import { MyContext } from "../context";
import SearchBox from "./SearchBox";
class SearchResult extends Component {
static contextType = MyContext;
render() {
return (
<React.Fragment>
<div className="container">
<div className="row justify-content-center">
{this.context.myState.searchResult.map(movie => {
return <Movie id={movie.id} image={movie.poster_path} />;
})}
</div>
{/* <button>Prev</button>
<button>Next</button> */}
</div>
</React.Fragment>
);
}
}
export default SearchResult;
and another thing. The pagination works for Now Playing Movies but couldn't make it to work with search result. Please help.

You can pass data with Redirect like this:
<Redirect to={{
pathname: '/movies',
state: { id: '123' }
}}
/>
and this is how you can access it:
this.props.location.state.id

Related

react : why is axios api call not using proxy in package.json?

I have a react app that i've made and it is working great in storybook with a mock for data retrieving.
When I switch to yarn start to check the app without mock, the page is loading some components but not the main component (PostPageCardContainer) which is only displaying "loading" (see the code below).
The component which load properly make api calls like this in ComponentDidMount :
axios.get("/api/blog/categories/").then((res) => {
const categories = res.data.results;
this.setState({
categories,
loading: false });
and
axios.get("/api/blog/tags/").then((res) => {
const tags = res.data.results;
this.setState({
tags,
loading: false });
}); }
The components that dont load make an api call like this in COmponentDidMount:
PostPageCard.js:
const pk = this.props.match.params.id;
axios.get(`/api/cms/pages/${pk}/`).then((res) => {
const post = res.data;
this.setState({
post,
loading: false });
}) }
PostDetail.js
axios.get(`/api/cms/pages/${this.props.postPk}/`).then((res) => {
this.setState({
data: res.data,
loading: false
}); });
In the browser console, when i try to load the page i get :
printWarnings # webpackHotDevClient.js:138
:3000/api/cms/pages/6/:1
Failed to load resource: the server responded with a status of 404 (Not Found)
And when i hover the mouse on the link i get http://localhost:3000/api/cms/pages/6.
In fact the react page is being served on localhost:3000 but I have put "proxy": "http://172.20.128.2:8000" in packages.json so my api call go on this adress.
How come some api calls go on the good adress and others dont?
The issue is similar to this : How to set proxy when using axios to send requests? and this Axios not using proxy setting with https and this axios request ignores my proxy and even when hardcoded I can't fetch any data but there is not really a solution except using fetch or restarting the machine
I ve tried to hardcode the proxy in the api call like axios.get(http://172.20.128.2:8000/api/cms/pages/${this.props.postPk}` and removed the proxy line from package.json but then nothing is loading properly...
Here is some sample of the code:
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./components/App";
import 'bootstrap/dist/css/bootstrap.css';
import { MemoryRouter } from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<MemoryRouter initialEntries={["/"]}>
<App/>
</MemoryRouter>
</React.StrictMode>,
document.getElementById("root")
);
App.js
import React from "react";
import { Route, Switch } from "react-router";
import { Container, Row } from "react-bootstrap";
import { BlogPage } from "./BlogPage";
import { PostPage } from "./PostPage";
function App() { return (
<Switch>
<Route path="/post/:id([\d]+)" component={PostPage}/>
<Route path="/tag/:tag/:page([\d]+)?" component={BlogPage}/>
<Route path="/:page([\d]+)?" component={BlogPage}/>
<Route
path="*"
component={() => (
<Container> <Row>
<h1>404</h1> </Row>
</Container> )}
/> </Switch>
); }
export default App;
BlogPage.js
import React from "react";
import { Container, Row } from "react-bootstrap";
import { TopNav } from "./TopNav";
import { Footer } from "./Footer";
import { PostPageCardContainer } from "./PostPageCardContainer";
import { SideBar } from "./SideBar";
class BlogPage extends React.Component { render() {
return (
<div>
<TopNav />
<Container>
<Row>
<PostPageCardContainer {...this.props} />
<SideBar />
</Row>
</Container>
<Footer />
</div> );
} }
export { BlogPage };
Postpagecardcontainer.js
import React from "react";
import axios from "axios";
import { Col } from "react-bootstrap";
import { Link } from "react-router-dom";
import { generatePath } from "react-router";
import _ from 'lodash';
import { PostPageCard } from "./PostPageCard";
class PostPageCardContainer extends React.Component {
constructor(props) {
super(props); this.state = {
posts: [],
pageCount: 0,
pageStep: 2,
};
this.getPosts = this.getPosts.bind(this);
}
componentDidMount() {
this.getPosts();
}
componentDidUpdate(prevProps) {
if (prevProps.location !== this.props.location) {
this.getPosts(); }
}
getCurPage() {
// return the page number from the url
const page = this.props.match.params.page;
return page === undefined ? 1 : parseInt(page);
}
getPrePageUrl() {
const target = _.clone(this.props.match.params);
target.page = this.getCurPage() - 1;
return generatePath(this.props.match.path, target);
}
getNextPageUrl() {
const target = _.clone(this.props.match.params);
target.page = this.getCurPage() + 1;
return generatePath(this.props.match.path, target);
}
getPosts() {
let category = this.props.match.params.category === undefined ? "*" : this.props.match.params.category;
let tag = this.props.match.params.tag === undefined ? "*" : this.props.match.params.tag;
let offset = (this.getCurPage() - 1) * this.state.pageStep;
const url = `/api/blog/posts/?limit=${this.state.pageStep}&offset=${offset}&category=${category}&tag=${tag}`;
axios.get( url).then((res) => {
const posts = res.data.results;
this.setState({
posts,
pageCount: Math.ceil(parseInt(res.data.count) / this.state.pageStep),
});
});
}
render() {
return (
<Col md={8}> {this.state.posts.map((post) => (
<PostPageCard postPk={post.id} key={post.id} /> ))}
<nav aria-label="Page navigation example">
<ul className="pagination">
<li className={
this.getCurPage() <= 1 ? "page-item disabled" : "page-item" }>
<Link to={this.getPrePageUrl()}
className="page-link" >
Previous
</Link>
</li>
<li className={this.getCurPage() >= this.state.pageCount ? "page-item disabled" : "page-item" }>
<Link to={this.getNextPageUrl()}
className="page-link" >
Next
</Link>
</li>
</ul>
</nav>
</Col>
);
}
}
export { PostPageCardContainer };
PostPage.js
import React from "react";
import { Container, Row } from "react-bootstrap";
import { TopNav } from "./TopNav";
import { Footer } from "./Footer";
import { SideBar } from "./SideBar";
import { PostDetail } from "./PostDetail";
class PostPage extends React.Component { render() {
return ( <div>
<TopNav/> <Container>
<Row>
<PostDetail {...this.props} /> <SideBar/>
</Row> </Container> <Footer/>
</div> );
} }
export { PostPage };
PostDetail.js
import React from "react";
import axios from "axios";
import { StreamField } from "./StreamField/StreamField";
class PostDetail extends React.Component {
constructor(props) {
super(props); this.state = {
post: [],
loading: true, };
}
componentDidMount() {
const pk = this.props.match.params.id;
axios.get(`/api/cms/pages/${pk}/`).then((res) => {
const post = res.data;
this.setState({
post,
loading: false });
}) }
render() {
if (!this.state.loading) {
const post = this.state.post;
return (
<div className="col-md-8">
<img src={post.header_image_url.url} className="img-fluid rounded" alt=""/>
<hr />
<h1>{post.title}</h1>
<hr />
<StreamField value={post.body} />
</div> );
}
else {
return <div className="col-md-8">Loading...</div>;
}
}
}
export { PostDetail };
PostPageCard.js
import React from "react";
import { Link } from "react-router-dom";
import axios from "axios";
class PostPageCard extends React.Component {
constructor(props) {
super(props); this.state = {
data: null,
loading: true,
};
}
componentDidMount() {
axios.get(`/api/cms/pages/${this.props.postPk}/`).then((res) => {
this.setState({
data: res.data,
loading: false
}); });
}
renderPost(data) {
const dateStr = new Date(data.pub_date).toLocaleString();
return (
<div className="card mb-4">
<Link to={`/post/${data.id}`}> <img src={data.header_image_url.url} className="card-img-top" alt=""/> </Link>
<div className="card-body">
<h2 className="card-title">
<Link to={`/post/${data.id}`}>{data.title}</Link>
</h2>
<p className="card-text">{data.excerpt}</p>
<Link to={`/post/${data.id}`} className="btn btn-primary">Read More → </Link>
</div>
<div className="card-footer text-muted">Posted on {dateStr}
</div>
</div>
); }
render() {
if (this.state.loading) {
return 'Loading...'; }
else{
return this.renderPost(this.state.data); }
} }
export { PostPageCard };

Extract Data from API and show in another page

This question may sound silly to some people, but I am really confused on how to do it
I have 3 file: App.js, HomePage.js and Profile.js
App.js :
import React from "react"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import HomePage from "./components/HomePage";
import Profile from "./components/Profile"
function App() {
return (
<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route exact path="/profile/:profileId" component= {Profile} />
</Switch>
</Router>
);
}
export default App;
From here, the default page it will go to is HomePage.js
HomePage.js:
import React, { Component } from "react";
import axios from "axios";
import { Link } from "react-router-dom";
class HomePage extends Component {
constructor() {
super();
this.state = {
userData: [],
}
}
componentDidMount() {
axios.get("XXXXXXXX").then((response) => {
const userDataList = response.data.users;
this.setState({
userData: userDataList
})
})
}
render() {
const userGrid = this.state.userData.map((user, index) => {
return (
<div key={index}>
<Link to={`/profile/${user.id}`}>
<img src={user.profilepicture} />
<p>{user.name}</p>
</Link>
</div>
)
})
return (
<div className="App">
<div className="card">
<div className="card__top">
<span className="card__title">
<p>Select An Account</p>
</span>
</div>
<div className="card__bottom">
<div className="card__table">
{userGrid}
</div>
</div>
</div>
</div>
)
}
}
export default HomePage;
In HomePage.js, I am able to show the profile picture and name of the user from API.
In the next page which is Profile.js , I am able to print the ID of the user.
Profile.js:
import React, { Component } from "react";
class Profile extends Component{
componentDidMount(){
const uid = this.props.match.params.profileId;
}
render() {
console.log(this.props.match);
return(
<h1>{this.props.match.params.profileId}</h1>
)
}
}
export default Profile;
As you can see I am printing the ID of user.
Here I also want to show the Profile Picture of the user which I selected in HomePage.js
This I am not able to do it.
JSON file:
{ - users: [-{id:1, name:"abc", profilepicture: "xxxxx.jpeg"}, ]}
You need to store a global state in your applicattion, which you can access from every connected component. This is a more complex topic. redux is a good framework to handle your global state changes.
Here is a tutorial: https://appdividend.com/2018/06/14/how-to-connect-react-and-redux-with-example/
I found it pretty hard to learn redux, but in the end it takes away a lot of pain. Because this is a problem you gonna have in every app you build with react.
You need use Context API o redux
Example context API: https://ibaslogic.com/react-context-api/
Context's well to little projects, but Redux performs better.
App.js
import React from "react"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import HomePage from "./components/HomePage";
import Profile from "./components/Profile"
import { UsersProvider } from "./UsersProvider.js";
function App() {
return (
<Router>
<UsersProvider>
<Switch>
<Route path="/" exact component={HomePage} />
<Route exact path="/profile/:profileId" component= {Profile} />
</Switch>
</UsersProvider>
</Router>
);
}
export default App;
UsersContext.js
import React, { Component } from "react"
const UsersContext = React.createContext();
const UsersProvider = UsersContext.Provider;
const UsersConsumer = TodosContext.Consumer;
class MyContext extends Component {
state = {
value: null,
};
setValue = (value) => {
this.setState({ value });
};
render() {
return (
<UsersProvider value={{ setValue, value }}>{this.props.children}
</UsersProvider>
)
}
}
export { UsersContext, UsersProvider, UsersConsumer }
HomePage.js
import React, { Component } from "react";
import axios from 'axios';
class HomePage extends Component {
componentDidMount() {
axios.get("XXXXXXXX").then((response) => {
const userDataList = response.data.users;
// updating your context
this.props.context.setValue(userDataList);
})
}
render() {
const userGrid = this.props.context.value.map((user, index) => {
return (
<div key={index}>
<Link to={`/profile/${user.id}`}>
<img src={user.profilepicture} />
<p>{user.name}</p>
</Link>
</div>
)
})
return (
<div className="App">
<div className="card">
<div className="card__top">
<span className="card__title">
<p>Select An Account</p>
</span>
</div>
<div className="card__bottom">
<div className="card__table">
{userGrid}
</div>
</div>
</div>
</div>
)
}
}
export default HomePage;
Profile.js
import React, { Component } from "react";
import { UsersConsumer } from "./UsersContext.js";
class Profile extends Component{
render() {
return(
<UsersConsumer>
{users => (
<h1>{users.value.find(user => user.id === this.props.match.params.profileId)}</h1>
)}
</UsersConsumer>
)
}
}
export default Profile;

How to get rid of this ? from the url

I'm making a react application and whenever I search for something(eg cat) on the homepage, the url changes to search/cat and the forward, backward button work normally & help me switch between the homepage and the cat search page ...but when i search for something again (eg rat) after(homepage->cat) so the url changes to search/rat? and now when i press the back button the url changes to search/rat and i'm on the same page then if i press back button again the url becomes search/cat but the page still has the results of the rat search and if i press back again ,the homepage appears..why is this happening?I think it's because of the ? that appears at the end of the url..Please help
after searching cat
after searching for rat
after pressing the back button
after pressing the back button
after pressing the back button
This is the code of the search bar
import React, { Component } from "react";
import "./styles/searchBar.scss";
import "font-awesome/css/font-awesome.min.css";
import { withRouter } from "react-router-dom";
import SearchForm from "./SearchForm";
import { connect } from "react-redux";
import { fetchRandomPhotos } from "../redux/actions/randomPhotoAction";
class SearchBar extends Component {
state = {
searchQuery: "",
};
componentDidMount() {
this.props.fetchRandomPhotos();
}
handleChange = (event) => {
this.setState({ searchQuery: event.target.value });
};
handleSubmit = (event) => {
//event.preventDefault();
this.props.history.push(`/search/${this.state.searchQuery}`);
};
handleProfile = () => {
this.props.history.push(`/public/${this.props.photo.user.username}`);
};
render() {
const { photo } = this.props;
return !photo ? (
<div className="search-bar-container">
<div className="search-bar-area">
<div className="about-foto-fab">
<h1>Foto-Fab</h1>
<p>The internet's source of freely-usable images.</p>
<p>Powered by creator everywhere</p>
</div>
<SearchForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
</div>
</div>
) : (
<div
className="search-bar-container"
style={{ backgroundImage: `url("${photo.urls.full}")` }}
>
<div className="black-layer"></div>
<div className="search-bar-area">
<div className="about-foto-fab">
<h1>Foto-Fab</h1>
<p>The internet's source of freely-usable images.</p>
<p>Powered by creator everywhere</p>
</div>
<SearchForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
</div>
<div className="picture-info">
<div className="photographer">
<p onClick={this.handleProfile}>
<strong>Photo</strong> by {""}
<strong>{photo.user.name}</strong>
</p>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
photo: state.randomPhotoState.photo,
};
};
export default connect(mapStateToProps, { fetchRandomPhotos })(
withRouter(SearchBar)
);
This is the App.js
import React from "react";
import Navbar from "./components/Navbar";
import { BrowserRouter, Switch, Route, Redirect } from "react-router-dom";
import Home from "./pages/Home";
import LoginPage from "./pages/LoginPage";
import ProfilePage from "./pages/ProfilePage";
import SearchPage from "./pages/SearchPage";
import PublicUserProfilePage from "./pages/publicUserProfilePage";
import MobileNavigation from "./components/MobileNavigation";
import AboutPage from "./pages/AboutPage";
function App() {
return (
<BrowserRouter>
<Navbar />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={LoginPage} />
<Route exact path="/profile" component={ProfilePage} />
<Route exact path="/search/:searchQuery" component={SearchPage} />
<Route exact path="/about" component={AboutPage} />
<Route
exact
path="/public/:username"
component={PublicUserProfilePage}
/>
<Redirect to="/" />
</Switch>
<MobileNavigation />
</BrowserRouter>
);
}
export default App;
search form component
import React, { Component } from "react";
export class SearchForm extends Component {
render() {
const { onSubmit, onChange } = this.props;
return (
<form className="search-form" onSubmit={onSubmit}>
<input
type="text"
placeholder="Search free high-resolution photos"
onChange={onChange}
/>
<button type="submit">
<i className="fa fa-search"></i>
</button>
</form>
);
}
}
export default SearchForm;
import React, { Component } from "react";
import "./styles/searchBar.scss";
import "font-awesome/css/font-awesome.min.css";
import { withRouter } from "react-router-dom";
import SearchForm from "./SearchForm";
import { connect } from "react-redux";
import { fetchRandomPhotos } from "../redux/actions/randomPhotoAction";
class SearchBar extends Component {
state = {
searchQuery: "",
};
componentDidMount() {
this.props.fetchRandomPhotos();
}
handleChange = (event) => {
this.setState({ searchQuery: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
if (this.state.searchQuery) {
this.props.history.push(`/search/${this.state.searchQuery}`);
}
};
handleProfile = () => {
this.props.history.push(`/public/${this.props.photo.user.username}`);
};
render() {
const { photo } = this.props;
return !photo ? (
<div className="search-bar-container">
<div className="search-bar-area">
<div className="about-foto-fab">
<h1>Foto-Fab</h1>
<p>The internet's source of freely-usable images.</p>
<p>Powered by creator everywhere</p>
</div>
<SearchForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
</div>
</div>
) : (
<div
className="search-bar-container"
style={{ backgroundImage: `url("${photo.urls.full}")` }}
>
<div className="black-layer"></div>
<div className="search-bar-area">
<div className="about-foto-fab">
<h1>Foto-Fab</h1>
<p>The internet's source of freely-usable images.</p>
<p>Powered by creator everywhere</p>
</div>
<SearchForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
</div>
<div className="picture-info">
<div className="photographer">
<p onClick={this.handleProfile}>
<strong>Photo</strong> by {""}
<strong>{photo.user.name}</strong>
</p>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
photo: state.randomPhotoState.photo,
};
};
export default connect(mapStateToProps, { fetchRandomPhotos })(
withRouter(SearchBar)
);

How do you access match in a React-Redux container?

Firstly, thank you to anyone who is reading this and is willing to help!
I'm trying to build a react-redux web app, and I'm having trouble accessing an id from the url in a container. The url looks like this: websitename.com/games/:game_id
I need to access that :game_id so that I can use it in a redux action to make a call to my api, but I can't figure out how to access the usage of match. I get the following error when I try to compile:
./src/containers/GameDetails.js
Line 9:19: 'match' is not defined no-undef
My app is set up with the following structure: Main.js houses the routes:
import React from "react";
import {Switch, Route, withRouter, Redirect} from "react-router-dom";
import {connect} from "react-redux";
import Homepage from "../components/Homepage";
import AuthForm from "../components/AuthForm";
import {authUser} from "../store/actions/auth";
import {removeError} from "../store/actions/errors"
import withAuth from "../hocs/withAuth";
import GameForm from "./GameForm";
import GamePage from "../components/GamePage";
const Main = props => {
const {authUser, errors, removeError, currentUser} = props;
return (
<div className="container">
<Switch>
<Route path="/" exact render={props => <Homepage currentUser={currentUser} {...props} /> } />
<Route
path="/signin" exact
render={props => {
return(
<AuthForm
removeError={removeError}
errors={errors}
onAuth={authUser}
buttonText="Log in"
heading="Welcome Back."
{...props}
/>
)
}} />
<Route
path="/signup" exact
render={props => {
return(
<AuthForm
removeError={removeError}
errors={errors}
onAuth={authUser}
signUp
buttonText="Sign me up"
heading="Join Weekly Matchup today."
{...props}
/>
)
}}
/>
<Route
path="/games/new" exact
component={withAuth(GameForm)}
/>
<Route
path="/games/:game_id"
render={props => {
return(
<GamePage
currentUser={currentUser}
{...props}
/>
)
}}
/>
<Redirect to="/" />
</Switch>
</div>
)
}
function mapStateToProps(state){
return {
currentUser: state.currentUser,
errors: state.errors
};
}
export default withRouter(connect(mapStateToProps, {authUser, removeError})(Main));
GamePage.js is a component that has the GameDetails container in it:
import React from "react";
import { Link } from "react-router-dom";
import GameDetails from "../containers/GameDetails";
const GamePage = ({ currentUser }) => {
if (!currentUser.isAuthenticated) {
return (
<div className="home-hero">
<h1>You must sign in or sign up in order to vote for matchups and view comments.</h1>
</div>
);
}
return (
<div>
<div className="home-hero">
<GameDetails />
</div>
</div>
)
}
export default GamePage;
And GameDetails.js is where I'm trying to get the id from the url to use in my redux action:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { getGameDetails } from "../store/actions/games";
class GameDetails extends Component {
componentDidMount() {
const game_id = match.params.game_id;
this.props.getGameDetails(game_id);
}
render() {
const { match, game } = this.props;
return (
<div className="home-hero">
<div className="offset-1 col-sm-10">
<h4>You are viewing the Game Page for game.title</h4>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
game: state.game
};
}
export default connect(mapStateToProps, { getGameDetails })(
GameDetails
);
I'm still very new to react and redux, so I appreciate any help you can offer.
Thank you for your time and patience!
Try something like this in GameDetails.js and GamePage.js
import React from "react";
import { Link } from "react-router-dom";
import GameDetails from "../containers/GameDetails";
const GamePage = ({ currentUser, ...props }) => {
if (!currentUser.isAuthenticated) {
return (
<div className="home-hero">
<h1>You must sign in or sign up in order to vote for matchups and view comments.</h1>
</div>
);
}
return (
<div>
<div className="home-hero">
<GameDetails {...props} />
</div>
</div>
)
}
export default GamePage;
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { getGameDetails } from "../store/actions/games";
class GameDetails extends Component {
componentDidMount() {
const {game_id}= this.props.match.params.game_id;
this.props.getGameDetails(game_id);
}
render() {
const { match, game } = this.props;
return (
<div className="home-hero">
<div className="offset-1 col-sm-10">
<h4>You are viewing the Game Page for game.title</h4>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
game: state.game
};
}
export default connect(mapStateToProps, { getGameDetails })(
GameDetails
);
I suppose you use you exported component same thing like
<Route path="/games/:game_id" component={GameDetails} />
mapStateToProps get 2 arguments state, and ownProps.
function mapStateToProps(state, ownProps) {
const { match: { params: { game_id } } } = ownProps; // you able to use game_id inside mapStateToProps
return ({ game: state.game });
}
1 solution
import React from "react";
import { Link } from "react-router-dom";
import GameDetails from "../containers/GameDetails";
const GamePage = ({ currentUser, ...routeProps }) => {
if (!currentUser.isAuthenticated) {
return (
<div className="home-hero">
<h1>You must sign in or sign up in order to vote for matchups and view comments.</h1>
</div>
);
}
return (
<div>
<div className="home-hero">
<GameDetails {...routeProps} />
</div>
</div>
)
}
export default GamePage;
2 solution
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link, withRouter } from "react-router-dom";
import { getGameDetails } from "../store/actions/games";
class GameDetails extends Component {
componentDidMount() {
const game_id = match.params.game_id;
this.props.getGameDetails(game_id);
}
render() {
const { match, game } = this.props;
return (
<div className="home-hero">
<div className="offset-1 col-sm-10">
<h4>You are viewing the Game Page for game.title</h4>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
game: state.game
};
}
export default withRouter(
connect(mapStateToProps, { getGameDetails })(GameDetails)
);

How do I get connected-react-router to redirect using a redux-thunk dispatch action?

I'm getting a new project going. I am using create-react-app with redux and thunk. The api server is a separate project, and uses node/express and mongo/mongoose for the database. I am trying to redirect to an item list view after either a create or delete item action. If the redirect is in the component itself, the redirect happens before the action is completed, and the refetch of the list happens before the create or delete happens. Therefore I am trying to use thunk to dispatch a redirect action in the .then() part of the action. The redirect successfully changes the url in the browser, but no re-render is triggered. How can I get the redirect to from either the create action or delete action to trigger a re-render of the stockitems list component?
full code is at: https://github.com/jhlindell/BarCode-app/tree/stockitem
server is at: https://github.com/jhlindell/BarCode-server/tree/jon
My index.js:
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import React from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import { ConnectedRouter } from 'connected-react-router'
import { createStore, applyMiddleware, compose } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { createBrowserHistory } from 'history';
// import { browserHistory } from 'react-router'
import reducers from './reducers';
import thunk from 'redux-thunk';
import { connectRouter, routerMiddleware } from 'connected-react-router'
import { logger } from 'redux-logger';
const history = createBrowserHistory();
const reactRouterMiddleware = routerMiddleware(history);
const middleWares = [
thunk,
logger,
reactRouterMiddleware
]
const store = createStore(
connectRouter(history)(reducers),
composeWithDevTools(applyMiddleware(...middleWares)));
ReactDOM.render(
<Provider store = {store}>
<ConnectedRouter history={history}>
<App history={history}/>
</ConnectedRouter>
</Provider>
, document.getElementById('root'));
registerServiceWorker();
My app.js file:
import './App.css';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Footer from './components/Nav/Footer';
import NavBar from './components/Nav/NavBar';
import React, { Component } from 'react';
import StockItemCreate from './components/StockItems/StockItemCreate';
import StockItemDetail from './components/StockItems/StockItemDetail';
import StockItemList from './components/StockItems/StockItemList';
import HomePage from './components/HomePage';
class App extends Component {
com
render() {
const flexCol = {
display: 'flex',
flexDirection: 'column',
};
const flex0 = {
flex: 0
};
const flex1 = {
display: 'flex',
flex: '1 1 100%',
};
return (
<Router>
<div className="App" style={flexCol}>
<div style={flex0}>
<NavBar />
</div>
<div style={flex1} id="mainBlock">
<Switch>
<Route exact path='/' component={HomePage} />
<Route exact path='/stockitems/create' component={StockItemCreate} />
<Route exact path='/stockitems' component={StockItemList} />
<Route path='/stockitems/:id' component={StockItemDetail} />
</Switch>
</div>
<div style={flex0}>
<Footer />
</div>
</div>
</Router>
);
}
}
export default App;
the list component:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import React, {Component} from 'react';
import { getStockItemList, clearStockItemList } from '../../actions';
const listStyle = {
display: 'flex',
margin: 'auto'
}
class StockItemList extends Component {
componentDidMount(){
this.props.getStockItemList();
}
componentWillUnmount(){
this.props.clearStockItemList();
}
render(){
return (
<div style={listStyle}>
{this.props.stockItemList ? <ul className="list-group">
{this.props.stockItemList.map((item) => {
return <li
className="list-group-item"
key={item.name}
onClick={()=> this.props.history.push(`/stockitems/${item._id}`)}
>{item.name}</li>
})}
</ul> : <span>loading...</span>}
</div>
);
}
}
function mapStateToProps(state){
return { stockItemList: state.stockItemList }
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ getStockItemList, clearStockItemList }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(StockItemList);
the create component:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import React, {Component} from 'react';
import { createStockItem } from '../../actions'
const cardStyle = {
display: 'flex',
margin: 'auto',
};
const formStyle = {
display: 'flex',
flexDirection: 'column',
width: '80%',
margin: 'auto'
};
class StockItemCreate extends Component{
constructor(props){
super(props);
this.state = {
name: '',
description: ''
}
}
handleFormSubmit = (event) => {
event.preventDefault();
this.props.createStockItem(this.state);
this.clearForm();
//this.props.history.push('/stockitems');
}
handleInputChange = (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({[name]: value});
}
clearForm = () => {
this.setState({ name: '', description: ''});
}
render(){
return(
<form className="card" onSubmit={this.handleFormSubmit} style={cardStyle}>
<div className="card-header">
<h3>Add new ingredient</h3>
</div>
<div className="card-block mt-2">
<div style={formStyle}>
<label>Name</label>
<input name="name" type="text"
onChange={(e) => {this.handleInputChange(e)}}
placeholder="Name"
value={this.state.name}/>
</div>
<div className="mt-2" style={formStyle}>
<label>Description</label>
<input name="description" type="text"
onChange={(e) => {this.handleInputChange(e)}}
placeholder="Description"
value={this.state.description}/>
</div>
</div>
<div className="btn-group mb-2 mt-2" style={{padding: '0', margin: 'auto'}}>
<button className="btn btn-primary" type="submit">
Submit
</button>
<button className="btn btn-secondary" type="button" onClick={()=>this.clearForm()}>
Cancel
</button>
</div>
</form>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({ createStockItem }, dispatch);
}
export default connect(null, mapDispatchToProps)(StockItemCreate);
and finally, the action file where the redirect is called:
import axios from 'axios';
import { push, replace } from 'connected-react-router'
const URL = 'http://localhost:8000';
export function getStockItemList(){
return function(dispatch){
axios.get(`${URL}/api/stock_items/`)
.then((response) => {
dispatch({ type: 'STOCK_ITEM_LIST', payload: response.data });
})
.catch((error) => {
console.log('error getting stock items');
});
}
}
export function clearStockItemList(){
return { type: 'CLEAR_STOCK_ITEM_LIST' };
}
export function getStockItemById(id){
return function(dispatch){
axios.get(`${URL}/api/stock_items/${id}`)
.then((response) => {
dispatch({ type: 'SINGLE_STOCK_ITEM', payload: response.data });
})
.catch((error) => {
console.log('error getting stock item by id');
dispatch(push('/stockitems'));
});
}
}
export function clearSingleStockItem(){
return { type: 'CLEAR_SINGLE_STOCK_ITEM' };
}
export function createStockItem(item){
return function(dispatch){
axios.post(`${URL}/api/stock_items/`, item)
.then((response)=> {
console.log("response", response);
dispatch(push('/stockitems'));
})
.catch((error) => {
//create error container to post error to
console.log('error creating stock item', error);
});
}
}
export function deleteStockItem(id){
return function(dispatch){
axios.delete(`${URL}/api/stock_items/${id}`)
.then((response)=> {
console.log("delete response: ", response);
dispatch(push('/stockitems'));
})
.catch((error) => {
//create error container to post error to
console.log('error deleting stock item', error);
});
}
}
Consider using push from connected-react-router as an "action creator" instead of this.props.history.push.
As per this SO answer:
Changing from this:
<Switch>
<Route path="/">
<Welcome />
</Route>
<Redirect to="/" />
</Switch>
To this:
<Switch>
<>
<Route path="/">
<Welcome />
</Route>
<Redirect to="/" />
</>
</Switch>
... worked for me.
I was trying to implement a basic fallback redirect for any paths not explicitly specified, so random paths like http://localhost:3000/askfjasdf would redirect to http://localhost:3000. For some reason adding the fragment as a top level child of <Switch> did the trick.

Resources