React link updates URL but page doesn't change/render - javascript

I am importing a header into the main.jsx file below as a simple component (SFC) that acts as a navigation menu. Everything looks fine and clicking on the menu does update the URL in the browser. However the application does not change page. I have googled a bit and come across various posts suggesting its related to a blocking update but I don't know how to fix it.
Full source is here: https://github.com/infornite/n4nite-react-ui-aug-2018
src/component/layout/header.tsx
import * as React from 'react'
import { Link } from 'react-router-dom'
import { Layout, Menu } from 'antd'
import '../../style/index.less';
const { Header } = Layout;
interface HeaderProps {
className?: string
}
const nHeader: React.SFC<HeaderProps> = () => (
<Header>
<div className="logo" />
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['1']}
style={{ lineHeight: '64px' }}
>
<Menu.Item key="1">
<Link to="/">Index</Link>
</Menu.Item>
<Menu.Item key="2">
<Link to="/register">Register</Link>
</Menu.Item>
</Menu>
</Header>
)
export default nHeader
src/routes.tsx
import * as React from 'react'
import { Route, Switch } from 'react-router-dom'
import IndexPage from './pages'
import RegisterPage from './pages'
const Routes: React.SFC = () => (
<Switch>
<Route exact path="/" component={IndexPage} />
<Route path="/register" component={RegisterPage} />
<Route component={() => <div>Not Found</div>} />
</Switch>
)
export default Routes
src/main.tsx
import * as React from 'react'
import { Provider, connect } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import { Store } from 'redux'
import { History } from 'history'
import Routes from './routes'
import { ApplicationState } from './store'
import { Layout } from 'antd'
import './style/index.less';
import Header from './components/layout/header'
import Breadcrumb from './components/layout/breadcrumb'
import Footer from './components/layout/footer'
const { Content } = Layout;
// Separate props from state and props from dispatch to their own interfaces.
interface PropsFromState {
}
interface PropsFromDispatch {
[key: string]: any
}
// Any additional component props go here.
interface OwnProps {
store: Store<ApplicationState>
history: History
}
// Create an intersection type of the component props and our Redux props.
type AllProps = PropsFromState & PropsFromDispatch & OwnProps
class Main extends React.Component<AllProps> {
public render() {
const { store, history } = this.props
return (
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Layout className="layout">
<Header />
<Content style={{ padding: '0 50px' }}>
<Breadcrumb />
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
<Routes />
</div>
</Content>
<Footer/>
</Layout>
</div>
</ConnectedRouter>
</Provider>
)
}
}
// It's usually good practice to only include one context at a time in a connected component.
// Although if necessary, you can always include multiple contexts. Just make sure to
// separate them from each other to prevent prop conflicts.
const mapStateToProps = () => ({
})
// Normally you wouldn't need any generics here (since types infer from the passed functions).
// But since we pass some props from the `index.js` file, we have to include them.
// For an example of a `connect` function without generics, see `./containers/LayoutContainer`.
export default connect<PropsFromState, PropsFromDispatch, OwnProps, ApplicationState>(
mapStateToProps
)(Main)

The issue was a silly mistake on my part where I was importing the same page (e.g. my Index page) as both the index page and the register page. Everything was actually working fine but because of this mistake React was loading the same page when I went to either link. I was too quick to assume I had a complex problem and started looking at Blocked Components and HOC's when it was actually a very simple issue.
Wrong: src/routes.tsx
import IndexPage from './pages'
import RegisterPage from './pages'
Correct: src/routes.tsx
import IndexPage from './pages/index'
import RegisterPage from './pages/register'

you've missed the keyword "exact", change your router to:
<Route exact path="/register" component={RegisterPage} />
also, surround it with BrowserRouter

Related

How can you wrap a story with a react-router layout route?

I have a very simple and plain ComponentX that renders some styled HTML, no data fetching or even routing needed. It has a single, simple story. ComponentX is meant to be used in a dark-themed website, so it assumes that it will inherit color: white; and other such styles. This is crucial to rendering ComponentX correctly. I won't bore you with the code for ComponentX.
Those contextual styles, such as background-color: black; and color: white;, are applied to the <body> by the GlobalStyles component. GlobalStyles uses the css-in-js library Emotion to apply styles to the document.
import { Global } from '#emotion/react';
export const GlobalStyles = () => (
<>
<Global styles={{ body: { backgroundColor: 'black' } }} />
<Outlet />
</>
);
As you can see, this component does not accept children, but rather is meant to be used as a layout route, so it renders an <Outlet />. I expect the application to render a Route tree like the below, using a layout route indicated by the (1)
<Router>
<Routes>
<Route element={<GlobalStyles/>} > <== (1)
<Route path="login">
<Route index element={<Login />} />
<Route path="multifactor" element={<Mfa />} />
</Route>
Not pictured: the <Login> and <Mfa> pages call ComponentX.
And this works!
The problem is with the Stories. If I render a plain story with ComponentX, it will be hard to see because it expects all of those styles on <body> to be present. The obvious solution is to create a decorator that wraps each story with this <Route element={<GlobalStyles/>} >. How can this be accomplished? Here's my working-but-not-as-intended component-x.stories.tsx:
import React from 'react';
import ComponentX from './ComponentX';
export default {
component: ComponentX,
title: 'Component X',
};
const Template = args => <ComponentX {...args} />;
export const Default = Template.bind({});
Default.args = {};
Default.decorators = [
(story) => <div style={{ padding: '3rem' }}>{story()}</div>
];
(I realize that I can make <GlobalStyles> a simple wrapper component around the entire <Router>, but I want to use this pattern to create stories for other components that assume other, intermediate layout routes.)
What I've usually done is to create custom decorator components to handle wrapping the stories that need specific "contexts" provided to them.
Example usage:
Create story decorator functions
import React from 'react';
import { Story } from '#storybook/react';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
import theme from '../src/constants/theme';
import { AppLayout } from '../src/components/layout';
// Provides global theme and resets/normalizes browser CSS
export const ThemeDecorator = (Story: Story) => (
<ThemeProvider theme={theme}>
<CssBaseline />
<Story />
</ThemeProvider>
);
// Render a story into a routing context inside a UI layout
export const AppScreen = (Story: Story) => (
<Router>
<Routes>
<Route element={<AppLayout />}>
<Route path="/*" element={<Story />} />
</Route>
</Routes>
</Router>
);
.storybook/preview.js
import { INITIAL_VIEWPORTS } from '#storybook/addon-viewport';
import { ThemeDecorator } from './decorators';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
options: {
storySort: {
includeName: true,
method: 'alphabetical',
order: ['Example', 'Theme', 'Components', 'Pages', '*'],
},
},
viewport: {
viewports: {
...INITIAL_VIEWPORTS,
},
},
};
export const decorators = [ThemeDecorator]; // <-- provide theme/CSS always
Any story that needs the app layout and routing context:
import React from 'react';
import { ComponentStory, ComponentMeta } from '#storybook/react';
import { AppScreen, MarginPageLayout } from '../../.storybook/decorators';
import BaseComponentX from './ComponentX';
export default {
title: 'Components/Component X',
component: BaseComponentX,
decorators: [AppScreen], // <-- apply additional decorators
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof BaseComponentX>;
const BaseComponentXTemplate: ComponentStory<typeof BaseComponentX> = () => (
<BaseComponentX />
);
export const ComponentX = BaseComponentXTemplate.bind({});
In my example you could conceivably place all your providers and that Global component (w/ props) in what I've implemented as ThemeDecorator and set as a default decorator for all stories.

Routing in next.js

When I execute my program, it shows me this error:
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
It's the first time I'm working with routes in next.js and I can't solve the error.
Index.js
import BgAnimation from '../components/BackgrooundAnimation/BackgroundAnimation';
import Hero from '../components/Hero/Hero';
import Projects from '../components/Projects/Projects';
import Contact from '../components/Contacts/Contact';
import Timeline from '../components/TimeLine/TimeLine';
import { Layout } from '../layout/Layout';
import { Section } from '../styles/GlobalComponents';
import { BrowserRoute as Router, Switch, Route } from 'react-router-dom'
const Home = () => {
return (
<Router>
<Layout>
<Section grid>
<Hero />
<BgAnimation />
</Section>
<Switch>
<Route path="/" component={Home} />
<Route path="/about" component={Timeline} />
<Route path="/contact" component={Contact} />
</Switch>
</Layout>
</Router>
);
};
export default Home;
Layout.js
import React from 'react'
import Header from '../components/Header/Header'
import { Container } from './LayoutStyles'
export const Layout = ({children}) => {
return (
<Container>
<Header/>
<main>{children}</main>
</Container>
)
}
Header.js
import Link from 'next/link';
import React from 'react';
import { AiFillGithub, AiFillInstagram, AiFillLinkedin } from 'react-icons/ai';
import { AiOutlineBook } from "react-icons/ai";
import { Container, Div1, Div2, Div3, NavLink, SocialIcons, Span } from './HeaderStyles';
const Header = () => (
<Container>
<Div1>
<Link href="/">
<a style={{ display:"flex", alignItems: "center", color: 'white', marginBottom:20}}>
<AiOutlineBook size="3rem"/><Span>Portfólio</Span>
</a>
</Link>
</Div1>
<Div2>
<Link href="#projects">
<NavLink>Projetos</NavLink>
</Link>
<Link href="#contact">
<NavLink>Contactos</NavLink>
</Link>
<Link href="#about">
<NavLink>Sobre mim</NavLink>
</Link>
</Div2>
<Div3>
<SocialIcons href="https://www.linkedin.com/in/sérgio-carreirinha/">
<AiFillLinkedin size="3rem"/>
</SocialIcons>
<SocialIcons href="https://github.com/SergioCarreirinha">
<AiFillGithub size="3rem"/>
</SocialIcons>
<SocialIcons href="https://www.instagram.com/sergio_carreirinha/">
<AiFillInstagram size="3rem"/>
</SocialIcons>
</Div3>
</Container>
);
export default Header;
Thanks for your time.
In Next.js we do not need to use react-router-dom package and its features like (BrowserRoute as Router, Switch, Route). Here you have the best source of information, I encourage you to read. Routing, router.
Next.js has a file-system based router built on the concept of
pages.
When a file is added to the pages directory it's automatically
available as a route.
Next.js router allows you to do client-side route transitions between
pages, similar to a single-page application.
A React component called Link is provided to do this client-side route
transition.

Redirecting using React Router shows blank page

I'm trying to build a simple example project where the user is redirected to the 'contact' page upon clicking a button, using React. I'm trying to achieve this by setting the value of a state property. When I run the code I have, it does change the browser address bar URL to that of the contact page, but does not seem to actually load the component - I get a blank page instead. If I manually navigate to that URL (http://localhost:3000/contact) I can see the contents.
Here are my App.js and Contact.js files -
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, Redirect } from 'react-router-dom';
import Contact from './Contact';
class App extends Component {
state = {
redirect: false
}
setRedirect = () => {
this.setState({
redirect: true
})
}
renderRedirect = () => {
if (this.state.redirect) {
return <Redirect to='/contact' />
}
}
render() {
return (
<Router>
<Switch>
<Route exact path='/contact' component={Contact} />
</Switch>
<div>
{this.renderRedirect()}
<button onClick={this.setRedirect}>Redirect</button>
</div>
</Router>
)
}
}
export default App;
Contact.js
import React, { Component } from 'react';
class Contact extends Component {
render() {
return (
<div>
<h2>Contact Me</h2>
<input type="text"></input>
</div>
);
}
}
export default Contact;
Using state isn't really a requirement for me, so other (preferably simpler) methods of redirection would be appreciated too.
Since your button is nothing more than a link, you could replace it with:
<Link to="/contact">Redirect</Link>
There are many alternatives though, you could for example look into BrowserRouter's browserHistory:
import { browserHistory } from 'react-router'
browserHistory.push("/contact")
Or perhaps this.props.history.push("/contact").
There are pros and cons to every method, you'll have to look into each and see which you prefer.
I got here for a similiar situation. It's possible use withRouter (https://reactrouter.com/web/api/withRouter) to handle that.
This example was tested with "react": "^16.13.1","react-router-dom": "^5.2.0" and "history": "^5.0.0" into "dependecies" sections in package.json file.
In App.js I have the BrowserRouter (usually people import BrowserRouter as Router, I prefer work with original names) with Home and Contact.
App.js
import React, { Component } from "react";
import {
BrowserRouter,
Switch,
Route,
} from "react-router-dom";
import Home from "./pages/Home";
import Contact from "./pages/Contact";
class App extends Component
{
// stuff...
render()
{
return (
<BrowserRouter>
<Switch>
<Route path="/contact">
<Contact />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</BrowserRouter>
);
}
}
export default App;
ASIDE 1: The Route with path="/contact" is placed before path="/" because Switch render the first match, so put Home at the end. If you have path="/something" and path="/something/:id" place the more specific route (with /:id in this case) before. (https://reactrouter.com/web/api/Switch)
ASIDE 2: I'm using class component but I believe (I didn't test it) a functional component will also work.
In Home.js and Contact.js I use withRouter associated with export keyword. This makes Home and Contact components receive the history object of BrowserRouter via props. Use method push() to add "/contact" and "/" to the history stack. (https://reactrouter.com/web/api/history).
Home.js
import React from "react";
import {
withRouter
} from "react-router-dom";
export const Home = ( props ) =>
{
return (
<div>
Home!
<button
onClick={ () => props.history.push( "/contact" ) }
>
Get in Touch
<button>
</div>
);
}
export default withRouter( Home );
Contact.js
import React from "react";
import {
withRouter
} from "react-router-dom";
export const Contact = ( props ) =>
{
return (
<div>
Contact!
<button
onClick={ () => props.history.push( "/" ) }
>
Go Home
<button>
</div>
);
}
export default withRouter( Contact );
Particularly, I'm using also in a BackButton component with goBack() to navigate backwards:
BackButton.js
import React from "react";
import {
withRouter
} from "react-router-dom";
export const BackButton = ( props ) =>
{
return (
<button
onClick={ () => props.history.goBack() }
>
Go back
<button>
);
}
export default withRouter( BackButton );
So I could modify the Contact to:
Contact.js (with BackButton)
import React from "react";
import BackButton from "../components/BackButton";
export const Contact = ( props ) =>
{
return (
<div>
Contact!
<BackButton />
</div>
);
}
export default Contact; // now I'm not using history in this file.
// the navigation responsability is inside BackButton component.
Above was the best solution for me. Other possible solutions are:
useHistory Hook (https://reactrouter.com/web/api/Hooks)
work with Router instead BrowserRouter - (https://reactrouter.com/web/api/Router)

React Where to place login page for a one page app with a side menu

I have a react web app with a sidemenu. Whenever a user clicks on the link in the sidemenu, they are routed to a page that is rendered at the right side of the sidemenu. My question is, how do I do login for such a usecase seeing as any page I route to renders to the right of the sidemenu. I want the login page to be full screen without the side menu showing. This is what App.js looks like.
import React, { Component } from "react";
import { HashRouter } from "react-router-dom";
import Navigation from "./pages/General/components/Navigation";
import SideMenu from "./pages/General/components/SideMenu";
import "../src/css/App.css";
class App extends Component {
render() {
return (
<div>
<HashRouter>
<div className="main-wrapper">
<SideMenu />
<Navigation />
</div>
</HashRouter>
</div>
);
}
}
export default App;
Here is Navigation.js
import React from "react";
import { Route } from "react-router-dom";
import CalendarPage from "../../Calendar/CalendarPage";
import DoctorsList from "../../Doctors/DoctorsList";
import PatientsList from "../../Patients/PatientsList";
import AdminUsersList from "../../AdminUsers/AdminUsersList";
import SpecialitiesList from "../../Specialities/SpecialitiesList";
const Navigation = () => {
return (
<div className="mainarea">
<Route exact path="/" component={CalendarPage} />
<Route exact path="/scheduler" component={CalendarPage} />
<Route exact path="/doctors" component={DoctorsList} />
<Route exact path="/patients" component={PatientsList} />
<Route exact path="/admin-users" component={AdminUsersList} />
<Route exact path="/specialities" component={SpecialitiesList} />
</div>
);
};
export default Navigation;
The best solution I can figure out in terms of a clean design, is to implement another router in your App.jsx, because you are implementing the routing inside your component, and you need another one for your login page.
Then, your App.jsx could be like this:
import React, { Component } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import LogIn from "./pages/General/components/Login";
import HomePage from "./pages/General/components/HomePage";
import "../src/css/App.css";
class App extends Component {
render() {
return (
<div>
<Switch>
<Route path={'/login'} component={LogIn} />
<Route path={'/'} component={HomePage} />
<Redirect to="/" />
</Switch>
</div>
);
}
}
export default App;
Then, for your HomePage do the following
import React, { Component } from "react";
import { HashRouter } from "react-router-dom";
import Navigation from "./pages/General/components/Navigation";
import SideMenu from "./pages/General/components/SideMenu";
import "../src/css/App.css";
class HomePage extends Component {
render() {
return (
<div>
<HashRouter>
<div className="main-wrapper">
<SideMenu />
<Navigation />
</div>
</HashRouter>
</div>
);
}
}
export default HomePage;
I hope it helps!
Here is my solution, it not exactly a solution, but it will give you a basic idea on how to implement this.
The idea is to place the Login component in app.js, and conditionally display it if the user is logged in.
You will have to pass a handler function to login component through which you will be able to control app.js state.
When login will be sucessfull, u can show the Navigation and Sidemenu component.
import { Fragment } from "react";
import Login from "path/to/login";
class App extends Component {
state = { isLoggedIn: false };
loginHandler = () => {
this.setState({
isLoggedIn: true
});
};
render() {
return (
<div>
<div className="main-wrapper">
{isLoggedIn ? (
<Fragment>
<SideMenu />
<Navigation />
</Fragment>
) : (
<Login loginHandler={this.loginHandler} />
)}
</div>
</div>
);
}
}
Also you need write a separate router file, which will contain the main app.
This is used to show the app component when navigated to /
import React from 'react';
import { HashRouter, Route } from 'react-router-dom';
import App from './app';
const MainRoute = () => (
<HashRouter>
<Route path="/" component={App} />
</HashRouter>
);
export default MainRoute;

react router v4 does not render any other route

I am trying to implement react router but having few problems. Please find the code below:
import React from 'react';
import ReactDOM from 'react-dom';
import { App, Home, Signup } from './containers';
import routes from './routes';
import { BrowserRouter , Route } from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
<App>
<Route path='/' component={Home} />
<Route path='/signup' component={Signup} />
</App>
</BrowserRouter>,
document.getElementById('app')
);
The App.js file which I am using as a Layout is as below:
import React, { Component } from 'react';
import { Cheader } from '../components';
class App extends Component {
render() {
return(
<div>
<Cheader/>
{this.props.children}
</div>
)
}
}
export default App;
It works when I navigate to http://localhost:3000/, but it does not work when i try to navigate to http://localhost:3000/signup. The page displays the following error:
Cannot GET /signup
The code for signup components and containers is as below:
components: index.js, signuppage.js
import Cheader from './cheader';
import Signuppage from './signuppage';
import Homepage from './homepage';
export {
Cheader,
Homepage,
Signuppage
}
import React from 'react';
import { Grid, Row, Col } from 'react-bootstrap';
const Signuppage = () => (
<Grid>
<Row>
<Col xs={12} sm={12} md={12}>
This is test
</Col>
</Row>
</Grid>
);
export default Signuppage;
containers: index.js, Signup.js
import App from './App';
import Home from './Home';
import Signup from './Signup';
export {
App,
Home,
Signup
}
import React, { Component } from 'react';
import { Signuppage } from '../components';
class Signup extends Component {
render () {
return (
<Signuppage/>
)
}
}
export default Signup;
It seems to be working for the default path, but not for any other route. am I missing any configuration here. Please note that I am using react-router v4. Can anyone please let me know.
Thanks
It is expalined in this: React-router urls don't work when refreshing or writting manually
When you try to go to the link directly, your server cannot find a file match to signup.
Solution 1: Use HashRouter to replace BrowserRouter.
Solution 2: Use fallback technique to setup server that fall back to '/' when no file matched is found.
you have to add exact to the first route
<App>
<Route exact path='/' component={Home} />
<Route path='/signup' component={Signup} />
</App>
another thing can be that you missing pushState settings in your dev server configuration
if you use webpack, add:
devServer: {
port: 3000,
historyApiFallback: true
}

Categories

Resources