React Router does not update component - reactjs

I got a component here which should display gifs from giphy on a route base search term:
public render(){
return(
<div>
<Router>
<div>
<Route path='/:term' component={this.dispGiphy} />
</div>
</Router>
</div>
)
}
private readonly dispGiphy = (props) => {
this.getGifs(props.match.params.term)
return(
this.state.items.map(data => (
<img className="images" src={data.images.downsized.url} />
)))
}
public getGifs(inputValue){
// Get Request to the API and write the result to this.state.items
fetch("https://api.giphy.com/v1/gifs/search?limit=3&api_key=KEY_GOES_HERE&q=" + inputValue)
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.data
});
},
(error) => {
this.setState({
error,
isLoaded: true
});
}
)
}
I also got a list, listing previous search terms:
public render(){
return(
<div>
<ul id="ul_LeftList">
{this.state.terms.map((term) => (
<li key={term}>
<Link to={`${term}`}>{term} </Link><button className="fa fa-cancel btnDelete" onClick={this.deleteElement(term)}
>X</button>
</li>
))}
</ul>
</div>
)
}
These two components are bound together in the app.tsx
return(
<div>
<div><header className="App-header">
<input type="text" placeholder="Search..." onChange={this.searchChanged} />
<button type="submit" className="fa fa-search searchButton" onClick={this.process} />
</header>
</div>
<Router>
<p className="leftList">
<LeftList terms={this.state.searchterms} />
</p>
</Router>
<p className="App-content">
<Router>
<Route path='/:term' component={Giphy} />
</Router>
<Giphy />
</p>
</div>
)
Now, when I click on a Link in the LeftList, I expect two things:
1. The Route will be updated with the search term I just clicked (working)
2. The Giphy component shows up the new gifs relating to the search term from the route (not working)
Actually I don't know what the problem is.

Related

redirecting to logout a user using react router

I am trying to redirect user to the URL hostUrl+'logout' when they click Yes button from the Are you sure you want to log out? popup dialog. Since my other parts of code is making use of
react router, I am wondering if I could make use of the same inside the following function :
logoutDialogClose = event =>{
if (event) {
this.setState({ displayLogoutPopup: false})
}
}
Someone in this post suggested making use of Link but not sure if that applies to my case or not.
Maybe I will need a separate function logoutDialogYes once above testing is done to separate Cancel and Yes clicks from the popup dialog.
My complete code is below:
const hostUrl = properties.baseUrlUI
class Main extends Component {
constructor() {
super();
this.state = {
isAuthenticated: false,
displayLogoutPopup: false,
};
this.logoutDialogOpen = this.logoutDialogOpen.bind(this)
this.logoutDialogClose = this.logoutDialogClose.bind(this)
}
componentDidMount() {
//some stuff here
}
loadDropDownObjects() {
// some axios call to store info in session storage
}
logoutDialogOpen = event =>{
if (event) {
this.setState({ displayLogoutPopup: true})
}
}
logoutDialogClose = event =>{
if (event) {
this.setState({ displayLogoutPopup: false})
/* <Link to="/">
<i className="fa fa-sign-out pull-right"></i>
Log Out
</Link> */
/* console.log("Testing host URL");
console.log(hostUrl);
axios.delete(hostUrl+'logout')
//axios.get(hostUrl+'logout')
.then(response => {
console.log("Reaching inside the response of logoutDialogClose function and printing the response below:");
console.log(response);
//keeping the setState outside until we test it on dev server
//this.setState({ displayLogoutPopup: false})
})
.catch (error => {
console.log("logout error", error);
}) */
}
}
render() {
const logoutDialog = this.state.displayLogoutPopup ? (
<div className="popup popup--icon -question js_question-popup"
id={this.state.displayLogoutPopup ? 'popup--visible' : ''}>
<div className="popup__background"></div>
<div className="popup__content">
<h3 className="popup__content__title">
Are you sure you want to log out?
</h3>
<p>
<button className="button button_accept" onClick={this.logoutDialogClose}>Yes</button>
<button className="button button--warning" onClick={this.logoutDialogClose}>Cancel</button>
</p>
</div>
</div>
) : null
return (
<div>
{
this.state.isAuthenticated ? (
<BrowserRouter basename={process.env.REACT_APP_ROUTER_BASE || ''}>
<div>
<div className="header">
<div className="header-logo">
<img src="images/logo.jpg"/>
<span>DATA</span>
</div>
<div className="logout_sec">
<div className="logout_icon">
<a href="javascript:void(0)" onClick={this.logoutDialogOpen}> <FontAwesomeIcon
style={{fontSize: '1.5em'}} icon={faSignOutAlt}/></a>
</div>
</div>
<ul className="header-list">
<li>
<NavLink exact to="/">
Home
</NavLink>
</li>
<li>
<NavLink to="/myprojects">My Projects</NavLink>
</li>
<li>
<NavLink to="/myassets">My Assets</NavLink>
</li>
<li>
<NavLink to="/datadownload">DataSet Download</NavLink>
</li>
<li>
<NavLink to="/assetbrowser">Asset Browser</NavLink>
</li>
</ul>
{/* Popup COde start */}
{logoutDialog}
{/* Popup code End */}
</div>
<div id="forNotification"></div>
<div>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/myprojects"
render={(props) =>
<div>
<Projects {...props}/>
</div>
}
/>
<Route exact path="/project" component={ProjectView}/>
<Route exact path="/datadownload" component={DataDownload}/>
<Route exact path="/assetbrowser" component={AssetBrowser}/>
<Route exact path="/datarequest" component={DataRequest}/>
<Route path='/404' component={NotFoundPage}/>
<Redirect to="/404"/>
</Switch>
</div>
</div>
</BrowserRouter>
) : null
}
</div>
);
}
}
export default Main;
Try adding this import:
import {withRouter} from 'react-router-dom';
Change the export statement to:
export default withRouter(Main);
Then, you should be able to access the history object inside Main through this.props.history. For example:
logoutDialogClose = event =>{
if (event) {
this.setState({ displayLogoutPopup: false})
this.props.history.replace(`/some/path`);
// OR
this.props.history.push(`/some/path`);
}
}
Edit:
Try replacing the export statement with this:
const MainWithRouter = withRouter(Main);
const MainWrapper = props => (
<BrowserRouter basename={process.env.REACT_APP_ROUTER_BASE || ''}>
<MainWithRouter {...props} />
</BrowserRouter>
);
export default MainWrapper;

Passing props through Link to a child component to complete the url to make an api call

I have a father component (which is a child of HousesGallery and receiving props to display api data), but now I want to display HouseDetail component as a place to show details about the house where you clicked. The api needs the name of the house so I'm trying to pass the name through props via Link and I don't know if I'm missing something in the Route or somewhere else.
App component where the Route is:
export default function App() {
return (
<div className="got-font">
<Router>
<div>
<Menu/>
</div>
<Switch>
<Route path="/detallecasa/:name">
<HouseDetail/>
</Route>
<Route path="/personajes">
<CharactersGallery/>
</Route>
<Route path="/casas">
<HousesGallery/>
</Route>
<Route path="/cronologia">
<Chronology/>
</Route>
<Route path="/">
<HomePage/>
</Route>
</Switch>
</Router>
</div>
);
}
Father component:
export default function HouseComponent(props) {
return (
<div className="container">
<div className="row">
{props.info.map((item, i) =>
item.logoURL ?
<Link to={{pathname: `/detallecasa/${item.name}`, query: {housename: item.name}}}
key={i} className="col-lg-2 col-md-4 col-sm-12 c-houses_div">
<figure className="c-houses_div_figure" key={i}>
<img className="c-houses_div_figure_img" src={item.logoURL} alt=""/>
<figcaption>
<h3>{item.name}</h3>
</figcaption>
</figure>
</Link> : null
)}
</div>
</div>
);
}
And the child component:
export default function HouseDetail(props) {
const [houseDetail, setHouseDetail] = useState([]);
useEffect(() => {
axios.get(process.env.REACT_APP_BACK_URL + "houses/" + props.match.params.housename)
.then(res => {
console.log(res.data)
setHouseDetail(res.data);
})
}, [])
return (
<div className="">
<div className="container">
<div className="row">
{houseDetail.map((item, i) =>
<div key={i} className="">
<figure className="" key={i}>
<img className="" src={item.logoURL} alt=""/>
<figcaption>
<h3>{item.name}</h3>
</figcaption>
</figure>
</div>
)}
</div>
</div>
</div>
);
}
The Link's to prop object doesn't take a query property, but you can pass additional data in route state
<Link
to={{
pathname: `/detallecasa/${item.name}`,
state: { housename: item.name }, // <-- Pass route state, if you wanted to
}}
... // other props, etc..
>
...
</Link>
This issue is more about how you are trying to reference the route's match params in the rendered component.
The access the route match param based on what it is named in the Route's path, i.e. name.
<Route path="/detallecasa/:name"> // <-- match param is `name`
<HouseDetail/>
</Route>
Access correctly, i.e. props.match.params.name.
useEffect(() => {
axios.get(process.env.REACT_APP_BACK_URL + "houses/" + props.match.params.name)
.then(res => {
console.log(res.data)
setHouseDetail(res.data);
})
}, []);

Problem to render a component dynamically with React Router

I'm working on a personal website and I have a problem rendering a component dynamically using React Router. To me, everything seems correct but for some reason, it's not working.
I tried to follow the documentation and watched a couple of tutorials but I have been stuck for a long time so I feel like I need help on this one.
In this component, I want to render the 'Articles' component dynamically using the id
class JobCard extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentWillMount() {
fetch(url)
.then(data => data.json())
.then(result =>
this.setState({
data: result
})
);
}
render() {
const { match } = this.props;
const { data, value } = this.state;
return (
<>
<div>
{results.map((job, id) => (
<div key={id}>
<div key={job._id} className="blog-card">
<div className="meta">
<div className="photo">
<img src={job.img} alt="logo" />
</div>
</div>
<div className="description">
<h5>{job.position_name}</h5>
<p className="read-more">
<p>{job.location}</p>
<p>
<span className="learn-pow">
{" "}
<Link
to={{
pathname: `${match.url}/${job._id}`,
state: job
}}
>
{job.workplace_name}
</Link>{" "}
Enter Location
</span>
</p>
</p>
</div>
</div>
</div>
))}
</div>
}
<Route path={`${match.path}/:id`} component={Articles} />
</>
);
}
}
export default JobCard;
And here is the component that i want to render:
import React from 'react';
const Articles = ({ location }) => (
<div>
<h1>{location.state.name}</h1>
<h2>{location.state.email}</h2>
<h2>{location.state.place}</h2>
</div>
)
export default Articles;
When I click on the Card the URL is right so I get the id but I don't have access to the Article component. I tried to console log but nothing appears.
Instead of this
<Route path={`${match.path}/:id`} component={Articles} />
try doing this
<Route path={`${match.url}/:id`} component={Articles} />

How can I get a component to update on path change while using the same route and component?

When the user is viewing another users profile, and attempt to click their own profile link in the navbar, the component never updates. In both the Redux and React dev tools, it shows that the state has been updated correctly but the component doesnt seem to notice and update.
class App extends Component {
render() {
return (
<Router>
<div>
<Navbar />
<NavbarFix />
<Switch>
<Route exact path="/" component={Posts} />
<Route exact path="/submit" component={AuthRoute(Submit)} />
<Route exact path="/signup" component={AlreadyAuth(SignUp)} />
<Route exact path="/login" component={AlreadyAuth(LogIn)} />
<Route exact path="/user/:id" component={AuthRoute(User)} />
<Route exact path="/item/:id" component={AuthRoute(Item)} />
<Route exact path="/admin" component={AdminAuth(Admin)} />
<Route exact path="/banned" component={Banned} />
<Route component={NoMatch} />
</Switch>
</div>
</Router>
);
}
}
.
class User extends Component {
constructor(props) {
super(props);
this.state = {
posts: [],
user: [],
comments: []
};
}
componentDidMount() {
this.loadUser();
}
loadUser = () => {
API.findUserById(this.props.match.params.id)
.then(res => {
console.log(res.data);
this.setState({
user: res.data.user,
posts: res.data.user.Posts,
comments: res.data.comments
});
console.log(this.state)
})
.catch(err => console.log(err));
this.setState(this.state)
}
handleCommentDelete = id => {
API.deleteComment(id)
.then(res => this.loadUser())
.catch(err => console.log(err));
}
handlePostDelete = id => {
API.deletePost(id)
.then(res => this.loadUser())
.catch(err => console.log(err));
}
render() {
return (
<div>
<div className="container-fluid">
<div className="row">
<div className="col-4 user-data-container">
<div className="row">
<div className="col-12 text-center">
<h2>{this.state.user.username}'s Posts</h2>
</div>
</div>
<hr className="pb-4" />
<div className="row">
<div className="col-12">
{this.state.posts.length > 0 ?
this.state.posts.map(post => (
<PostContainer handledelete={this.handlePostDelete} post={{ ...post, User: this.state.user }} key={post.id} check={this.props.id} />
))
:
<h1>No Posts To Show!</h1>
}
</div>
</div>
</div>
<div className="col-4 user-data-container">
<div className="row">
<div className="col-12 text-center">
<h2>{this.state.user.username}'s Comments</h2>
</div>
</div>
<hr className="pb-4" />
<div className="row">
<div className="col-12">
{this.state.comments.length > 0 ?
this.state.comments.map(comments => (
<CommentContainer verified={this.state.user.verified} handledelete={this.handleCommentDelete} check={this.props.id} comment={comments} className="hover-effect single-comment" key={comments.id}/>
))
:
<h1>No Comments To Show!</h1>
}
</div>
</div>
</div>
<div className="col-4 user-data-container">
<div className="row">
<div className="col-12 text-center">
<h2>{this.state.user.username}'s Information</h2>
</div>
</div>
<hr className="pb-4" />
<div className="row">
<div className="col-12">
<UserContainer user={this.state.user}/>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
username: state.auth.username,
id: state.auth.id,
email: state.auth.email,
profileImg: state.auth.profileImg,
verified: state.auth.verified
};
};
export default withRouter(connect(mapStateToProps)(User));
I believe this has to do with the same route and component being used, so the change isn't actually noticed. Is there any way to fix this? To my understanding, the component should be updating on state change.
If the link is directing to the same route with just a different param, it will not remount/componentDidMount will not be called again.
So, you could use the componentWillReceiveProps(newProps) lifecycle function and look for newProps.match.params.id and compare it with this.props.match.id and if different call loadUser again.
**You would also need to change your loadUser function to accept an id param
(Param validation not included)
componentDidMount() {
this.loadUser(this.props.match.id);
}
loadUser = (id) => {
API.findUserById(id)..
…
}
componentWillReceiveProps(newProps) {
if(newProps.match.params.id !== this.props.match.id) {
this.loadUser(newProps.match.params.id)
}
}

Search bar functionality for book API in React

I have a BooksAPI file that contains the following search method:
export const search = (query) =>
fetch(`${api}/search`, {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query })
}).then(res => res.json())
.then(data => data.books)
I'm trying to make it so that results start showing up when I type into the search bar, and I also want no books to show when the search bar is empty.
I have it working so that I can display results with the initial search, but if I try to backspace to type in a new term, I get a cannot read property map of undefined error, which makes sense, but I'm not sure how to address this. I can only do a new search if I refresh the page.
Any help is greatly appreciated.
This is what I have so far for App.js:
class BooksApp extends React.Component {
state = {
books: []
}
componentDidMount() {
BooksAPI.getAll()
.then((books) => {
this.setState(() => ({
books
}))
})
}
render() {
return (
<div className="app">
<div className="list-books">
<div className="list-books-title">
<h1>MyReads</h1>
</div>
<Route exact path="/" render={() => (
<BookList
books={this.state.books}
/>
)} />
<Route path="/search" component={Search} />
<Route path="/search" render={({ history }) => (
<Search
onSearch={(query) => {
this.search(query)
history.push('/')
}}
/>
)} />
<div className="open-search">
<Link to="/search">Add a book</Link>
</div>
</div>
</div>
)
}
}
export default BooksApp
And here is my Search component:
class Search extends Component {
state = {
query: '',
books: []
}
search = (query) => {
BooksAPI.search()
.then((books) => {
this.setState(() => ({
query,
books
}))
})
}
render() {
const { query, books } = this.state
return (
<div className="search-books">
<div className="search-books-bar">
<Link
className="close-search"
to='/'>Close
</Link>
<div className="search-books-input-wrapper">
<input
type="text"
onChange={(event) => this.search(event.target.value)}
placeholder="Search by title or author"
value={query}
/>
</div>
</div>
<div className="search-books-results">
<ol className="books-grid">
{books.map((book) => (
<li>
<div>
<p>{book.title}</p>
<p>{book.author}</p>
</div>
</li>
))}
</ol>
</div>
</div>
)
}
}
export default Search;
I had similar functionality I wanted to implement recently, I would recommend going to a library such as react-select to solve this problem rather than reinventing the wheel.

Resources