Change window location when rendering component with React? - reactjs

Can i change window location when react component going to be rendered without use react router <Route /> ?
{
this.state.loading ?
( <Loader percentageLoadingProgress={this.state.percentageLoadingProgress} loadingInformation={this.state.loadingInformation} /> ) :
( <Companies {...this.state} /> )
}
I need <Companies /> to be rendered with changed window location

You can either return a Redirect, eg:
const someComponent = props => {
if (props.foo) return `<Redirect to="/somewhere/else" />`;
return <NormalUI />;
}
Or you can use the useHistory hook to get a history object, and then call history.push('/somewhere/else'):
const someComponent = props => {
const history = useHistory();
if (props.foo) history.push('/somewhere/else');
else return <NormalUI />;
}
The Redirect component is the preferred way.

Related

How to prevent HOC from rendering if wrapped component renders nothing

I have components that can return null in render based on their props. Props are not the same between components. Now I want all of my components to render a fancy divider after them. I decide to use one shared HOC for this (e.g. by importing said HOC in my components and exporting the new wrapped component).
However, now I end up in a situation where if my actual components render nothing I still get the divider rendered from the HOC and my page only contains lonely dividers. In this case I would want the HOC to render nothing. Can I solve this in the HOC? I could check different props before rendering the HOC+wrapped component but that would have to be done everywhere. If not HOC, is there some other way of solving this?
import React from 'react'
import ReactDOM from 'react-dom'
const withDivider = (Component) => {
return ({ fancyDividerProp, ...props }) => (
<>
<Component {...props} />
<hr some-fancy-prop={fancyDividerProp} />
</>
)
}
const SpicyComponent = ({isSpicy}) => {
if (!isSpicy) return null
return (
<p>Spicy!</p>
)
}
const SpicyWithDivider = withDivider(SpicyComponent)
const SweetAndSourComponent = ({isSweet, isSour}) => {
if (!isSweet || !isSour) return null
return (
<p>Sweet&Sour!</p>
)
}
const SweetAndSourWithDivider = withDivider(SweetAndSourComponent)
class App extends React.Component {
render() {
return (
<div>
<SpicyWithDivider isSpicy={false} />
<SweetAndSourWithDivider isSweet={true} isSour={false} />
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
)

React router is not mounting the component

I'm using React Router for routing to different routes as below:
<Router>
<Switch>
<Route path="/teams/:teamName/matches" >
<MatchPage/>
</Route>
<Route path="/teams/:teamName" >
<TeamPage/>
</Route>
</Switch>
</Router>
Now in my TeamPage component I'm calling an API using async and then in the render method invoking another component called MatchDetailCard
class TeamPage extends Component {
constructor(props) {
console.log('const called')
super(props)
this.state = {
team: [],
teamName:null
}
}
async componentDidMount() {
console.log(this.props.match.params.teamName);
const teamName = this.props.match.params.teamName;
const response = await fetch(`http://localhost:8080/team/${teamName}`);
const json = await response.json();
console.log(json);
this.setState({team:json, teamName: teamName});
}
componentDidUpdate () {
console.log('updated')
}
render() {
if (!this.state.team || !this.state.team.teamName) {
return <h1>Team not found</h1>;
}
return (
<div className="TeamPage">
<div className="match-detail-section">
<h3>Latest Matches</h3>
<MatchDetailCard teamName={this.state.team.teamName} match={this.state.team.matches[0]}/>
</div>
</div>
);
}
}
export default withRouter(TeamPage);
Within the MatchDetailCard component I create a router Link to the same TeamPage component which will have a different teamName this time as below:
const MatchDetailCard = (props) => {
if (!props.match) return null;
const otherTeam = props.match.team1 === props.teamName ? props.match.team2 : props.match.team1;
const otherTeamRoute = `/teams/${otherTeam}`;
const isMatchWon = props.teamName === props.match.matchWinner;
return (
<div className={isMatchWon ? 'MatchDetailCard won-card' : 'MatchDetailCard lost-card'}>
<div className="">
<span className="vs">vs</span><h1><Link to={otherTeamRoute}>{otherTeam}</Link></h1>
</div>
</div>
);
}
export {MatchDetailCard};
The problem that I'm facing is that the on-click of the link to /team/teamName route only the TeamPage component is not mounting instead it's just getting an update.
I want to have the call to componentDidMount hook to make the API call again in this scenario.
What's the problem with my logic?
If the same component is used as the child of multiple <Route>s at the same point in the component tree, React will see this as the same component instance and the component’s state will be preserved between route changes. If this isn’t desired, a unique key prop added to each route component will cause React to recreate the component instance when the route changes.
https://reactrouter.com/web/api/Route
You can add the teamName as a key prop on the component, which will tell React to unmount/mount the component when the key value changes.
<Router>
<Switch>
<Route
path="/teams/:teamName/matches"
render={({ match }) => {
return <MatchPage key={match.params.teamName} />;
}}
/>
<Route
path="/teams/:teamName"
render={({ match }) => {
return <TeamPage key={match.params.teamName} />;
}}
/>
</Switch>
</Router>

Next JS, React have child component tell parent what to render

I am new to NextJS and React and want to create some dynamic routing. I have a parent page that is calling a child component which show some navigation.
In my parent component I have a layout that includes a header and right sidebar which will show on each page, but depending on the link that is clicked in my Nav component the body content will change.
My parent component:
import React from 'react';
import { Header } from './Header;
import { SubNav } from './SubNav;
import { Sidebar } from './Sidebar;
export const AboutPage = () => {
return (
<>
<Header />
<SubNav />
<div className="content">
>>>I want to do something like this <<<
{section === history && <History />}
{section === contact && <Contact />}
.....
</div>
<Sidebar />
</>
);
};
In my child Nav component I am mapping through some data that includes a 'value' for each different section that I have defined in an array.
<NavButtons
onClick={() => {
const path = AppRoute.ABOUT.replace(':section', 'value');
router.push(path, path, { shallow: true });
}}
/>
The page routes correctly with the value of the section changing each time I click on a different Navigation button but I can't get the content to change in the parent component, I have tried creating a function in the parent that will get the value from the child so that I can use it to define the sections in the parent, but it ends up creating an infinite loop. Confused on how to get the two to work together.
You can detect the pathname with the useRouter Hook.
Then you can check in a switch case statement which site the user is on and render the components for the route.
import { useRouter } from next/router
Inside the component
const router = useRouter();
The function to return the component
function renderSidebar(pathname){
switch(pathname){
...
}
Inside the render function
{ renderSidebar(router.pathname) }
See further usage in the NextJS docs
https://nextjs.org/docs/api-reference/next/router
import React from 'react';
import {useState} from 'react';
import { Header } from './Header';
import { SubNav } from './SubNav';
import { Sidebar } from './Sidebar';
export const AboutPage = () => {
const [section,setSection] = useState()
sectionHandler = (section) => {
setSection(section)
}
return (
<>
<Header />
<SubNav />
<div className="content">
>>>I want to do something like this <<<
{section === history && <History />}
{section === contact && <Contact />}
.....
</div>
<Sidebar />
</>
);
};
and in your child comp
<NavButtons
onClick={() => {
const path = AppRoute.ABOUT.replace(':section', 'value');
props.sectionHandler(section)
router.push(path, path, { shallow: true });
}}
/>
don't forget to pass sectionhandler from parent to child comp

How to create a condition depending on the route one is in?

I have an app that is divided in 3 sections, a Header, Middle and Popular
<>
<Header />
<Middle />
<Popular />
</>
Header has a button that says 'Book it now' <Link to="/booking"> <button> Book it now </button> </Link>. When clicked, it uses React Router to switch route in the Middle component.
The 'Middle' component:
<Switch>
<Route path="/booking"> //When 'Book it now' is clicked, this is displayed.
<Booking />
</Route>
<Route path="/">
<Form />
</Route>
</Switch>
I would like to now make that Header button disappear when the route is '/booking'. I could create state and switch it to false after the button is clicked to not display the button, but I was thinking it would be pretty handy if I could do something like this in the Header file:
if (route === '/booking') {
//don't display button in Header (also, how would I do this?)
}
You can use withRouter imported from react-router-dom
import { withRouter } from 'react-router-dom';
const OtherComponent = withRouter(props => <MyComponent {...props}/>);
class MyComponent extends Component {
render() {
const { pathname } = this.props.location;
if(pathnanme != 'booking') {
<button>Hidden on booking</button>
}
}
}
That should be your Booking component:
import { withRouter } from 'react-router-dom';
const Booking = (props) => {
const { pathname } = props.location;
console.log(pathname);
return (
<div>
{(pathname !== 'booking' && pathname !== '/booking') ? <button>Hidden on booking</button> : null}
<span>always shown</span>
</div>
);
}
export default withRouter(Booking)
Hope this helps you
If you are using react-router v5 then I like to use hooks:
let location = useLocation();
let isBookingPage = location.pathname.includes('booking')
<>
{!isBookingPage && <Header />}
<Middle />
<Popular />
</>
By using the React Router 'useLocation' hook, you can have access to the route at which the user is currently at:
import { useLocation } from 'react-router-dom'
const location = useLocation() //with no arguments
console.log(location.pathname) //e.g. '/' or '/booking'
The location can the be used to conditionally render anything, in any component, depending on the route the user is at
location.pathname === '/booking' && <button> Book it now </button>

Prevent react route unmounting component on state change

I'm using react-router (v.4.3.1) to render the main part of my application and I have a drawer on the left side with the menu. When a button is toggled in the app header I'm changing the state of the collapsed variable so that the components re-render the css accordantly. My problem is this variable needs to be stored on the component rendering all my Route and when the component is updated Route is unmounting and mounting it's component.
I've already tried to provide a key to my Route but it's not working.
My code looks like this and the parent of this component is the one being updated which re-renders my Main component:
class Main extends Component {
constructor(props) {
super(props);
this.observer = ReactObserver();
}
getLayoutStyle = () => {
const { isMobile, collapsed } = this.props;
if (!isMobile) {
return {
paddingLeft: collapsed ? '80px' : '256px',
};
}
return null;
};
render() {
const RouteWithProps = (({index, path, exact, strict, component: Component, location, ...rest}) =>
<Route path={path}
exact={exact}
strict={strict}
location={location}
render={(props) => <Component key={"route-" + index} observer={this.observer} {...props} {...rest} />}/>
);
return (
<Fragment>
<TopHeader observer={this.observer} {...this.props}/>
<Content className='content' style={{...this.getLayoutStyle()}}>
<main style={{margin: '-16px -16px 0px'}}>
<Switch>
{Object.values(ROUTES).map((route, index) => (
<RouteWithProps {...route} index={index}/>
))}
</Switch>
</main>
</Content>
</Fragment>
);
}
}
I would like the Route just to update and not to unmount the component. is this possible?
you are having this issue due to defining RouteWithProps inside of render method. This causes React to unmount old and mount a new one each time render method is called. Actually creating component dynamically in the render method is a performance bottleneck and is considered a bad practice.
Just move the definition of RouteWithProps out of Main component.
Approximate code structure will look like:
// your impors
const RouteWithProps = ({observer, path, exact, strict, component: Component, location, ...rest}) =>
<Route path={path}
exact={exact}
strict={strict}
location={location}
render={(props) => <Component observer={observer} {...props} {...rest} />}/>;
class Main extends Component {
...
render(){
...
{Object.values(ROUTES).map((route, index) => (
<RouteWithProps key={"route-" + index} {...route} observer={this.observer}/>
))}
^^^ keys should be on this level
...
}
}

Resources