Highlight "antd" menu-item when route is changed from redux-saga - reactjs

I am using the "antd" framework for a react app and have one slight issues with menus, more precisely with highlighting the currently selected menu item.
I found the following solution that works fine when a link is called, an url is entered directly to a specific page and when "back" is pressed:
render() {
const href = window.location.href.split('/');
const href2 = href[3];
<Menu
mode="horizontal"
theme="dark"
defaultSelectedKeys={['/' + href2]}
selectedKeys={['/' + href2]}
>
<Menu.Item key="/">
...
</Menu.Item>
<Menu.Item key="/test">
...
</Menu.Item>
<Menu>
My problem happens when I set my route from a redux saga (with history.push), I can then see that the "navigation bar" component gets rendered/updated before the "history.push" action is called in the saga.
How can I get my "navigation bar" component to be re-rendered after every route change (however the route change is done). My "navigation bar" is currently a component, because I tried to use the different events, but none of them gets fired. It could also be a functional component (it has no state) it that helps.
I also tried suggestions from "https://stackoverflow.com/questions/41054657/react-routerantd-how-to-highlight-a-menu-item-when-press-back-forward-button" but could not get it to work with my use case

If you're using react-router library you shouldn't use window.location object. In your example of code, you're using the class component. In this case the component in <Route> receives location prop. And you can use it when you want
class Comp extends React.Component {
componentDidUpdate(prevProps) {
// will be true
const locationChanged =
this.props.location !== prevProps.location;
}
}
<Route component={Comp} />;

Related

React Router history on browser's back button

I have a home component with a link which loads a display component and in display component i have the same link which loads the display component again.
if user clicks the link many times in Display Component then there will be a lot of router history.i want that when a user clicks the browser back button it should load the home component not all the previous history.
when i use history.replace("/"); in Display component with onClick event then it works only one time back.but again back is resulting the previous history of Display Component
Routes.js
import Home from "./Components/Home"
import Display from "./Components/Display"
<Router>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/:city">
<Display />
</Route>
</Switch>
</Router>
Home.js
<Link to ={`/${city}`} onClick={()=>{dispatch(fetchWeather(city)); }}>Search</Link>
Display.js
<Link to ={`/${city}`} onClick={()=>{dispatch(fetchWeather(city)); }}>Search</Link>
Depending on the version of react router you are using you can just add the replace prop on your Link component in the Display.js file to not push new states on the history stack and instead update the current one.
<Link replace to ={`/${city}`} onClick={()=>{dispatch(fetchWeather(city)); }}>Search</Link>
If you're on an older version where this isn't supported what you can do is have a click handler do this for you
// Display.js file
function Display(props) {
// whatever code you have for this file
const handleViewCity = useCallback((event, city) => {
// prevent the default redirect from happening, were going to manage that ourselves
event.preventDefault()
dispatch(fetchWeather(city))
props.history.replace(`/${city}`)
}, [props.history, fetchWeather])
return (
// your jsx
// keep the href so you get browser builtin functionality like right click "open in new window"
<a href={`/${city}`} onClick={(e) => handleViewCity(e, city)}>Search</Link>
)
}
export default withRouter(Display)
To visualize what would be happening here think of history as a stack of locations. (this is a simple example - pseudo code)
history.push('/city1') // ['/home', '/city1']
history.push('/city2') // ['/home', '/city1', '/city2']
history.push('/city3') // ['/home', '/city1', '/city2', '/city3']
Pressing the browser back button fires a window popstate event. Pop being the keyword there. When the browser back button is pressed your history then looks like this ['/home', '/city1', '/city2'], which is why you were seeing different cities from the history.
Instead you want to use replace to achieve the desired effect
history.replace('/city1') // ['/home', '/city1']
history.replace('/city2') // ['/home', '/city2']
history.replace('/city3') // ['/home', '/city3']

Route path="/subject/:selectedSubject" does not change subject when navigated via < Link />

I'm trying to display articles filtered by subject. When you access directly path="/subject/:selectedSubject" (subject/TECH) for example it works perfectly. However if you navigate through <Link to={"/subject/TECH"} /> it will change the URL but will not load new articles.
I've tried: connecting everything with "withRouter".
I know that the <Link/> is changing the redux state, however that is not calling componentWillMount() which is where fetchArticles() is.
So, my question is, where should I put fetchArticles in order for it to be called when is triggered. I tried putting it inside render() but it keeps getting called non-stop. Or maybe I'm approaching this the wrong way.
PS: if another path gets called via <Link/>, like for example path="/", it works as intended (loads up new articles).
at App/
<BrowserRouter>
<div>
<Route path="/" exact component={ArticlesList} />
<Route path="/subject/:selectedSubject" component={ArticlesList} />
</div>
</BrowserRouter>
at ArticleList/
componentDidMount() {
if (this.props.match.params.selectedSubject) {
const selectedSubject = this.props.match.params.selectedSubject.toUpperCase();
this.props.fetchArticles(selectedSubject);
} else {
this.props.fetchArticles();
}
}
at Link/
{this.props.subjects.map(subject => {
return (
<Link to={`/subject/${subject.name}`} key={subject.name}>
<li>
<span>{subject.name}</span>
</li>
</Link>
);
})}
Since you use the same Component for / and /subject/:selectedSubject, he's not calling mounting lifecycle methods, including constructor() and componentDidMount().
You have to use updating lifecycle method, especially componentDidUpdate() to handle a update of your component.

Link changing URL but not the page

I am using Ant design breadcrumbs. I am trying to change the page using link and pushing the URL, I can see the URL change but the page in not changing.
I tried using Link, then creating a function for onClick but everything just change the URL.
Route:
<Route exact path="/assistant/:wId/skill/xyz/:sid" component={ xyz } />
Tried process 1:
<Breadcrumb separator=">">
<Breadcrumb.Item
onClick={this.redirectToParam2}>
{param2}
</Breadcrumb.Item>
</Breadcrumb>
redirectToParam2 = () => {
this.props.history.push(`/assistant/${wId}/skill/xyz/${sId}`);
}
Tried process 2:
<Breadcrumb separator=">">
<Breadcrumb.Item>
<Link to= {`/assistant/${wId}/skill/xyz/${sId}`}>
{param2}
</Link>
</Breadcrumb.Item>
</Breadcrumb>
Even I tried without the Breadcrumbs component but it's still not changing the page.
I want the page to change as soon as the URL changes.
Thank you in advance.
Try this,
import { Link } from "react-router-dom";
<Breadcrumb separator=">">
<Breadcrumb.Item>
<Link to= {`/assistant/${wId}/skill/xyz/${sId}`}>
{param2}
</Link>
</Breadcrumb.Item>
</Breadcrumb>
The problem you are running into, is that with changing the parameters used as props for the xyz component, the component is not replaced but gets new properties. Since nothing changes, i'm assuming you have state that gets filled either in the constructor or ComponentWillMount/ComponentDidMount.
React class components have a lifecycle function for this: componentDidUpdate.
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
Quote from react docs, See: https://reactjs.org/docs/react-component.html#componentdidupdate
componentDidUpdate(prevProps) {
if ((this.props.params.match.sid !== prevProps.params.match.sid) ||
(this.props.params.match.wid !== prevProps.params.match.wid)) {
this.populateState(this.props.params.match); //fill your state
}
}

How can I pass props from the current component to another component when the second component is called by url?

We can send data as props from parent component to child component easily but what to do in case of no relationship between two components ?
Please Read my context first so that I can make you understand my problem easily.
In my e commerce react App, when any new user visits my site I want to recommend the user to register. In registration process I keep two steps.
At first I want to take his mobile number from only one input field. when he fills the field and press 'continue' button -
I want to take him another page (component) where from I can take his name, password etc.
Here these two components are independent to each other. When user hits the 'continue' button I am calling a and redirect him to another component.
Now my question is How do I preserve this mobile number in 1st component and send to the 2nd component
[N.B: I don't want the mobile number be visible in the url when calling the 2nd Route]
First Component
export default class FirstComponent extends Component {
render() {
return (
<div>
<input type="text" placeholder="Type Mobile number" />
<Link to={{ pathname: '/signup' }}>
<button>Continue</button>
</Link>
</div>
)
}
}
Second Component
export default class SecondComponent extends Component {
render() {
return (
<div>
<input type="text" placeholder="Type Mobile number" />
</div>
)
}
}
Router
<Route exact path="/signup" render={(props) => {return <SecondComponent />} }/>
You can do it this way:
<Link to={{ pathname: '/signup', state: { number: 3199192910} }}>
<button>Continue</button>
</Link>
And you can access that in your signup component like:
import {withRouter} from 'react-router';
class SecondComponent extends Component {
render() {
// get the number
console.log(this.props.location.state.number)
return (
<div>
<input type="text" placeholder="Type Mobile number" />
</div>
)
}
}
export default withRouter(SecondComponent);
Check out this example, Quotes is what you should be looking at:
On click of button, you can call handleClick function and inside this function you could use push method to send props. This way you would have more control over what representation you want to send your data to other component.
<button onClick={this.handleClick}>continue</button>
handleClick = () => {
this.props.history.push({
pathname: '/signup',
state: { mobile: this.state.mobileNumber }
})
}
Hope that helps!!!
This is where Redux comes into the picture.
Redux is an open-source JavaScript library for managing application state.
So what happens in redux we having something called store which manages the whole state of the application. we dispatch some actions to the stores which calls a function called reducer which mutate the data in the store based on the action that has been dispatched. don't worry if you didn't understand what I said
just watch this 15 min video and you'll completely understand how to use Redux in your application and solve your problem https://www.youtube.com/watch?v=sX3KeP7v7Kg
in order to learn in depth I will recommend you go through the https://redux.js.org/basics/basic-tutorial
now coming back to your problem all you have to do is create a store which saves phone no and when the user clicks on continue button dispatch an action to the reduces to store the phone no and thus your phone no is persisted throughout the application and whenever you want to access the phone just write mapstatetoprops function which is shown in the video to access the data from the store and use it in that component
Hopefully, this will solve your problem
what happens when you don't use redux
of course, you send data as props but what will happen to the props when you refresh the page!!!! the props are lost but when we use redux we can save the data. And you're application works as expected even after refreshed of course they are many other ways to do it

Cannot update state from render method React

I have a NavLink component like this:
<NavLink isActive={(match, location) => this.isActiveFunc(match, location)} className={classes.subLink} to={{ pathname: "/admin/users" }}>
<ListItem button className={classes.nested}>
<ListItemText disableTypography={this.state.activePath === "/admin/users"} inset primary="Users" />
</ListItem>
</NavLink>
and try to set my state every time the NavLink is active as follows:
updateActivePath = (match) => {
if (match && this.state.activePath !== match.path) {
this.setState({activePath: match.path})
}
};
isActiveFunc = (match, location) => {
this.updateActivePath(match);
};
This however gives me the following error:
Warning: Cannot update during an existing state transition (such as
within render or another component's constructor). Render methods
should be a pure function of props and state; constructor side-effects
are an anti-pattern, but can be moved to componentWillMount.
Any ideas how I can fix this?
If the "state" you're after is already in React Router, then copying it to your own state is unnecessary duplication, and the cause of your error (trying to update the state during render. You're better off just grabbing the state from the React Router. You can use a Route without a path for that
<Route render={({ location }) => (
// this will render whenever the location changes
<YourNavigationBar location={location} />
)}
/>
You use a setState in react lifecycle. You call your method updateActivePath in render or other lifecycle method react. This trigger an extra rendering for your component.
For fix this warning change your setState location in componentWillMount method or in function call by action (click, ...).
For must information go to lifecycle component documentation: https://reactjs.org/docs/react-component.html

Resources