React and i18n - translate by adding the locale in the URL - reactjs

Hello I'm building a demo application just to learn React and I'm kinda stuck with the translation proccess. What I'm trying to do is have a multi-language website with default language the "Greek" and secondary "English". When Greek are enabled the URL shouldn't contain any locale in the URL but when English are, the URLS should be rewritten with /en/.
i18n Config
import translationEn from './locales/en/translation';
import translationEl from './locales/el/translation';
import Constants from './Utility/Constants';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'el',
debug: true,
ns: ['translations'],
defaultNS: 'translations',
detection: {
order: ['path'],
lookupFromPathIndex: 0,
},
resources: {
el: {
translations: translationEl
},
en: {
translations: translationEn
}
},
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
}
}, () => {
// Why the fuck this doesnt work automatically with fallbackLng ??
if (!Constants.allowed_locales.includes(i18n.language)) {
i18n.changeLanguage(Constants.default_locale);
}
return;
});
i18n.on('languageChanged', function (lng) {
// if somehow it get injected
if (!Constants.allowed_locales.includes(i18n.language)) {
i18n.changeLanguage(Constants.default_locale);
}
// if the language we switched to is the default language we need to remove the /en from URL
if (Constants.default_locale === lng) {
Constants.allowed_locales.map((item) => {
if (window.location.pathname.includes("/" + item)) {
let newUrl = window.location.pathname.replace("/" + item, "");
window.location.replace(newUrl);
}
})
} else { // Add the /en in the URL
// #todo: add elseif for more than 2 langs because this works only for default + 1 more language
let newUrl = "/" + lng + window.location.pathname;
window.location.replace(newUrl);
}
});
export default i18n;
I used the Language Detector plugin with detection from path so it can parse the locale from URL. Now without the callback function I added at the initialization, the LanguageDetector would set correctly the language if the url was www.example.com/en/ or www.example.com/el or www.example.com/en/company. BUT if I directly accessed the www.example.com/company (before visiting first the home so the locale would be set) i18n would set the locale/language to "company" !!!
There is an option for fallbackLng that I thought that would set the language to what you config it if the LanguageDetector dont detect it, but seems that there isnt an option to set available languages or default language to i18n ( or I'm an idiot and couldnt find it ) so LanguageDetector set whatever he finds in the URL. To fix this I added a Constants file and the callback function above.
Contants.js
const Constants = {
"allowed_locales": ['el','en'],
"default_locale": 'el'
}
export default Constants;
Also I added an event Handler that fires on LanguageChange so it will rewrite the URL with /en/ if English is active or remove the /el/ if Greek is.
index.js
ReactDOM.render(
<BrowserRouter>
<I18nextProvider i18n={i18n}>
<App/>
</I18nextProvider>
</BrowserRouter>
,
document.getElementById('root')
);
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<Suspense fallback="loading">
<Header {...this.props}/>
<Routes {...this.props} />
</Suspense>
</div>
);
}
}
export default withTranslation('translations')(App);
Nothing special for index.js and App.js
Header Component
class Header extends React.Component {
linkGenerator(link) {
// if the current language is the default language dont add the lang prefix
const languageLocale = this.props.i18n.options.fallbackLng[0] === this.props.i18n.language ? null : this.props.i18n.language;
return languageLocale ? "/" + languageLocale + link : link;
}
render() {
return (
<div className="header">
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand className="logo" href="/"> <Image src="/assets/logo.png" rounded/>
</Navbar.Brand>
{/*Used For Mobile Navigation*/}
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
<Navbar.Collapse id="basic-navbar-nav" className="float-right">
<Nav className="ml-auto">
<Nav.Link as={NavLink} exact to={this.linkGenerator("/")}>{this.props.t('menu.home')}</Nav.Link>
<Nav.Link as={NavLink} to={this.linkGenerator("/company")}>{this.props.t('menu.company')}</Nav.Link>
</Nav>
<Nav className="mr-auto">
{this.props.i18n.language !== "el" ? <button onClick={() => this.props.i18n.changeLanguage('el')}>gr</button>
: null}
{this.props.i18n.language !== "en" ? <button onClick={() => this.props.i18n.changeLanguage('en')}>en</button>
: null}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
</div>
)
}
}
export default Header
In order to create the urls of the Menu with the locale, I created the linkGenerator function
And Finally in my Routes Component which handle all the routing, I added a constant before the actual url so it will work for all of theese /page , /el/page , /en/page
Routes Component
import React from 'react';
import {Switch, Route} from 'react-router-dom';
import CompanyPage from './Pages/CompanyPage';
import HomePage from './Pages/HomePage';
import NotFound from './Pages/NotFound';
class Routes extends React.Component {
render() {
const localesString = "/:locale(el|en)?";
return (
<Switch>
<Route exact path={localesString + "/"} component={HomePage}/>
<Route path={localesString + "/company"} component={CompanyPage}/>
<Route component={NotFound}/>
</Switch>
);
}
}
export default Routes
The code somehow works but is full of hacks like :
Extra config file ( constants.js )
Callback function to change the language from "company" to default locale. ( this triggers 2 page reloads)
functions to handle the locale in the menu and routes
etc..
Isnt there any "build-in" functionality or a better approach in order to achieve the same thing without the above hacks?

I needed the same thing and I found out that you can set the whitelistproperty of i18n.init options and specify the supported languages. After that, if you set checkWhitelist: true inside your detection options, the LanguageDetector will only match the language if it exists on the whitelist array.
Anyway, you still need to define the languageChanged event in order to redirect the page when matching the default language but, you no longer need to redirect if it is another supported language (at least I don't need).
Last thing that I did differently is that I defined the languageChanged event first and only then called the i18n.init, so that it would trigger the event already for the first time that it sets the language.
Here's my code:
i18n.js
import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n.on('languageChanged', function (lng) {
// if the language we switched to is the default language we need to remove the /en from URL
if (lng === i18n.options.fallbackLng[0]) {
if (window.location.pathname.includes('/' + i18n.options.fallbackLng[0])) {
const newUrl = window.location.pathname.replace('/' + i18n.options.fallbackLng[0], '')
window.location.replace(newUrl)
}
}
})
i18n
.use(LanguageDetector)
.init({
resources: {
en: {
translation: require('./translations/en.js').default
},
pt: {
translation: require('./translations/pt.js').default
}
},
whitelist: ['en', 'pt'],
fallbackLng: ['en'],
detection: {
order: ['path'],
lookupFromPathIndex: 0,
checkWhitelist: true
},
interpolation: {
escapeValue: false,
formatSeparator: '.'
}
})
export default i18n
App.js
import { Route, Switch } from "react-router-dom";
import AboutPage from "./AboutPage";
import HomePage from "./Homepage/HomePage";
import NotFoundPage from "./NotFoundPage";
import PropTypes from "prop-types";
import React from "react";
import { hot } from "react-hot-loader";
import {
Collapse,
Navbar,
NavbarToggler,
NavbarBrand,
Nav,
NavItem,
NavLink } from 'reactstrap';
import i18n from "../i18n";
const baseRouteUrl = "/:locale(pt|en)?";
export const baseUrl = i18n.language === 'en' ? '' : '/'+i18n.language;
class App extends React.Component {
state = {
isOpen: false
}
render() {
return (
<div>
<div>
<Navbar color="grey" expand="md">
<NavbarBrand href="/">Testing</NavbarBrand>
<Nav className="ml-auto" navbar>
<NavItem>
<NavLink href={baseUrl + "/"}>Home</NavLink>
</NavItem>
<NavItem>
<NavLink href={baseUrl + "/about/"}>About</NavLink>
</NavItem>
</Nav>
</Navbar>
</div>
<Switch>
<Route exact path={baseRouteUrl + "/"} component={HomePage} />
<Route path={baseRouteUrl + "/about"} component={AboutPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
);
}
}
App.propTypes = {
children: PropTypes.element
};
export default hot(module)(App);
In my case, when I need to translate something, I import my i18n.js and call the respective key like this:
<div>{i18n.t('home.bannerStart')}</div>

When you define a basename for the Router you don't need to add to every Route.
// in your case use el instead of en
export const baseUrl = () => return (i18n.language === "en" ? "" : "/" + i18n.language);
And the router should be something like this:
<Router basename={baseUrl()}>
...
</Router>
You can call the baseUrl() whenever you need to create a link. Also note that <Link> already takes the basename into regard, so you do not have to define it.
For the language change check my other answer here

Related

Do not understand why duplicate data is being copied into my url in react component

In my App.js (or main component) I am rendering my Navbar component
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import Navbar from './components/layout/navbar/Navbar';
import './App.css';
const App = () => {
return (
<Router>
<Navbar />
</Router>
);
};
export default App;
In my Navbar I am rendering my NavLinks component and passing in as props the menu
import React from 'react';
import NavLinks from './NavLinks';
const menu = [
{ id: 1, label: 'Home', url: 'https://www.google.com/?client=safari' },
{ id: 2, label: 'Contact us', url: 'https://stackoverflow.com' },
];
const Navbar = () => {
return (
<nav>
<NavLinks items={menu} />
</nav>
);
};
export default Navbar;
In my NavLinks I bring in as props the items which is the menu we saw before and map through it and pass in as props url and label.
import React from 'react';
import NavLink from './NavLink';
const NavLinks = ({ items }) => {
const links = items.map((item) => (
<NavLink key={item.id} url={item.url} label={item.label} />
));
return <ul>{links}</ul>;
};
export default NavLinks;
In my NavLink component I am creating a Link to the url
import React from 'react';
import { Link } from 'react-router-dom';
const NavLink = ({ url, label }) => {
return (
<li className='nav-item'>
<Link to={url}>{label}</Link>
</li>
);
};
export default NavLink;
For some reason my Link has a path of multiple google urls. The url to the google homepage is duplicated many times. I do not know why this is happening.
Link component is to Provides declarative, accessible navigation around your application
If you use the Link component for the external URL, this will keep appending your URL to the previous ones.
For navigating to an external URL, I would suggest you to use native HTML tag instead:
const NavLink = ({ url, label }) => {
return (
<li className="nav-item">
<a href={url}>{label}</a>
</li>
);
};
Working example:

React useTranslation alternative in Class Component

I am a total beginner in React (as I am in StackOverflow, sorry if the form of my post isn't quite right)
I have this SPA app that translates my content from french to english and back, works perfectly fine, but I would like to add an "active" className to my language toggle buttons so I can underline the active language in css, and what I found I had to do was dealing with the state of the component. Now I use a function component with useTranslation, and if I try and convert my function component to a class component, it says useTranslation can't be used in a class component.
Could any of you help me re-write my component so I can make it a class component and use a constructor and deal with the states, but still use the same hooks and functions I'm using now for translation?
I found previous aswers using withTranslation wrapper, but no example that would take my toggle buttons and changeLanguage function for navigation into account, just for {t} plain strings, so I'im a bit lost on how to write this correctly...
Here is my app code as a function component:
import React, { Component } from "react";
import {
Route,
NavLink,
HashRouter
} from "react-router-dom";
import Home from "./Home";
import Projets from "./Projets";
import Contact from "./Contact";
import Design from "./Design";
import Web from "./Web";
import ReactDOM from "react-dom";
import { useTranslation, Trans } from "react-i18next";
export default function App() {
const { t, i18n } = useTranslation();
const changeLanguage = lng => {
i18n.changeLanguage(lng);
};
return (
<div className="App">
<div className="App-header">
<h1>{t("Bienvenue sur notre Blog")}</h1>
</div>
<HashRouter>
<div className="main-container">
<div className="navigation">
<ul className="nav-list">
<li className="nav-links"><NavLink exact to="/"><Trans>Accueil</Trans></NavLink></li>
<li className="nav-links"><NavLink to="/projets"><Trans>Projets</Trans></NavLink></li>
<ul className="nav-sublist">
<li className="nav-sublinks"><NavLink to="/projets/graphisme-maquettes"><Trans>Graphisme</Trans></NavLink></li>
<li className="nav-sublinks"><NavLink to="/projets/web"><Trans>Web</Trans></NavLink></li>
</ul>
<li className="nav-links"><NavLink to="/contact"><Trans>Contact</Trans></NavLink></li>
</ul>
<div class='langSelect'>
<button onClick={() => changeLanguage("fr")} class='langBtn' value='lang'>fr</button>
<button onClick={() => changeLanguage("en")} class='langBtn' value='lang'>en</button>
</div>
</div>
<div className="content">
<Route exact path="/" component={Home}/>
<Route path="/projets" component={Projets}/>
<Route path="/projets/graphisme-maquettes" component={Design}/>
<Route path="/projets/web" component={Web}/>
<Route path="/contact" component={Contact}/>
</div>
</div>
</HashRouter>
</div>
);
}
And here is my i18n.js file:
import i18n from "i18next";
// import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
i18n
// .use(LanguageDetector)
.use(initReactI18next)
.use(LanguageDetector)
.init({
// we init with resources
resources: {
en: {
translations: {
Accueil:"Home",
Projets:"Work",
Graphisme:"Design",
Web: "Web",
Contact:"Contact",
// "Projets":"Work",
"Bienvenue sur notre Blog":"Welcome to our Blog"
}
},
fr: {
translations: {
Accueil:"Accueil",
Projets:"Projets",
Graphisme:"Graphisme",
Web: "Web",
Contact:"Contact",
// "Projets":"Projets",
"Bienvenue sur notre Blog":"Bienvenue sur notre Blog"
}
}
},
fallbackLng: "fr",
debug: true,
// have a common namespace used around the full app
ns: ["translations"],
defaultNS: "translations",
keySeparator: false, // we use content as keys
interpolation: {
escapeValue: false
}
});
export default i18n;
Now, what I would like to achieve is something like this:
import React, { Component } from "react";
import {
Route,
NavLink,
HashRouter
} from "react-router-dom";
import Home from "./Home";
import Projets from "./Projets";
import Contact from "./Contact";
import Design from "./Design";
import Web from "./Web";
import ReactDOM from "react-dom";
import { useTranslation, Trans } from "react-i18next";
class App extends Component {
constructor(){
super();
this.state = {
underline: false
}
}
render {
const { t, i18n } = useTranslation(); => OBVIOULSY USE SOMETHING ELSE
const changeLanguage = lng => {
i18n.changeLanguage(lng);
}; => ADD/REMOVE THE CLASSNAME HERE AT THE SAME TIME I CHANGE LANGUAGE?
return (
<div className="App">
<div className="App-header">
<h1>{t("Bienvenue sur notre Blog")}</h1>
</div>
<HashRouter>
<div className="main-container">
<div className="navigation">
<ul className="nav-list">
<li className="nav-links"><NavLink exact to="/"><Trans>Accueil</Trans></NavLink></li>
<li className="nav-links"><NavLink to="/projets"><Trans>Projets</Trans></NavLink></li>
<ul className="nav-sublist">
<li className="nav-sublinks"><NavLink to="/projets/graphisme-maquettes"><Trans>Graphisme</Trans></NavLink></li>
<li className="nav-sublinks"><NavLink to="/projets/web"><Trans>Web</Trans></NavLink></li>
</ul>
<li className="nav-links"><NavLink to="/contact"><Trans>Contact</Trans></NavLink></li>
</ul>
<div class='langSelect'>
<button onClick={() => changeLanguage("fr")} class='langBtn' value='lang'>fr</button>
<button onClick={() => changeLanguage("en")} class='langBtn' value='lang'>en</button>
</div>
</div>
<div className="content">
<Route exact path="/" component={Home}/>
<Route path="/projets" component={Projets}/>
<Route path="/projets/graphisme-maquettes" component={Design}/>
<Route path="/projets/web" component={Web}/>
<Route path="/contact" component={Contact}/>
</div>
</div>
</HashRouter>
</div>
);
}
}
export default App;
Thanks in advance, I hope you have everything your need, I really "just" need guidance in how to write all of this without changing the way it works so much, I hope it's actually doable...

How do I route between pages in Embedded React App?

Background:
I am trying to create some links in my embedded Shopify app.
I understand that I cannot use the simple <a> tag due to the fact that Shopify embedded apps are rendered as iframes.
I made some headway with this tutorial, but I am stuck: https://theunlikelydeveloper.com/shopify-app-bridge-react-router/
What I am trying to do:
I have 3 pages (index.js, dashboard.js, and support.js). I would like to allow the user to navigate from one page to another (with links and/or buttons).
My Code:
By following the tutorial above, I've gotten this far:
// index.js
import { Page, Frame } from "#shopify/polaris";
const Index = () => {
return (
<Page>
<Frame>
{/* LINK TO DASHBOARD PAGE*/}
{/* LINK TO SUPPORT PAGE */}
</Frame>
</Page>
);
};
export default Index;
// routes.js
import React from "react";
import { Switch, Route, withRouter } from "react-router";
import { ClientRouter, RoutePropagator } from "#shopify/app-bridge-react";
function Routes(props) {
const { history, location } = props;
return (
<>
<ClientRouter history={history} />
<RoutePropagator location={location} />
<Switch>
<Route path="/dashboard">
<Dashboard />
</Route>
<Route path="/support">
<Support />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</>
);
}
export default withRouter(Routes);
// link.js
import React from "react";
import { Link as ReactRouterLink } from "react-router";
const IS_EXTERNAL_LINK_REGEX = /^(?:[a-z][a-z\d+.-]*:|\/\/)/;
function Link({ children, url = "", external, ref, ...rest }) {
if (external || IS_EXTERNAL_LINK_REGEX.test(url)) {
rest.target = "_blank";
rest.rel = "noopener noreferrer";
return (
<a href={url} {...rest}>
{children}
</a>
);
}
return (
<ReactRouterLink to={url} {...rest}>
{children}
</ReactRouterLink>
);
}
export default Link;
Additional:
I believe I'm supposed to implement the following code somewhere, but I don't see how it fits into the picture of navigating between pages with a link or button.
<AppProvider linkComponent={Link}>
{/* App content including your <Route> components */}
</AppProvider>
Link to Shopify Docs: https://polaris.shopify.com/components/structure/app-provider#section-using-linkcomponent
At this time of building embedded app you can make client-side navigation using app-bridge utilities as referred to in this answer
You just need to edit _app file and consider making client-side navigation from your components(can't use a normal Link)
import {useEffect} from 'react';
import Router, { useRouter } from "next/router";
import { RoutePropagator as ShopifyRoutePropagator } from "#shopify/app-bridge-react";
function RoutePropagator () {
const router = useRouter();
const { route } = router;
const app= useAppBridge();
useEffect(() => {
app.subscribe(Redirect.Action.APP, ({ path }) => {
Router.push(path);
});
}, []);
return app && route ? (
<ShopifyRoutePropagator location={route} />
) : null;
}
Then use this component in your _app file
_app.tsx
class MyApp extends App {
render() {
const { Component, pageProps, host } = this.props as any;
return (
<PolarisProvider i18n={translations}>
<ShopifyBridgeProvider
config={{
apiKey: API_KEY,
host,
forceRedirect: true,
}}
>
<RoutePropagator />
<ApolloClientProvider Component={Component} {...pageProps} />
</ShopifyBridgeProvider>
</PolarisProvider>
);
}
}
Now you've subscribed for routing events in _app file, we just require to make client-side navigation right in your pages
import {useAppBridge} from '#shopify/app-bridge-react';
import { Redirect } from '#shopify/app-bridge/actions';
function IndexPage(props) {
const app = useAppBridge();
return (
<>
<div>{'you are in main page'}</div>
<div onClick={() => {
app.dispatch(Redirect.toApp({
path: '/dashboard'
}));
}}>
{'to dashboard'}
</div>
</>
);
}
And for going back to the main page / route, I've found that it trigger an oauth again if not provided with the shop name, so we will use the shop query params for that
<div onClick={() => {
app.dispatch(Redirect.toApp({
path: '/?shop=<shop-name>.myshopify.com'
}));
}}>
{'to main'}
</div>

Client side routing (using BrowserRouter) with dynamic server in react does not work proprely

I'm building a react app, using client side routing (BrowserRouter).
Server side is written in python Flask (dynamic server).
For some reason, refreshing some of the pages / accesssing routes does not work properly, return only the index.html file but does not nevigate to the window.location.href URL.
Based on: https://facebook.github.io/create-react-app/docs/deployment
If you use routers that use the HTML5 pushState history API under the hood
(for example, React Router with browserHistory), many static file servers >will fail. For example, if you used React Router with a route for >/todos/42, >the development server will respond to localhost:3000/todos/42
properly, but an Express serving a production build as above will not.
This is because when there is a fresh page load for a /todos/42, the server >looks for the file build/todos/42 and does not find it. The server needs to >be configured to respond to a request to /todos/42 by serving index.html. >For example, we can amend our Express example above to serve index.html for >any unknown paths:
So I'm serving index.html for each unknown path requested by the browser.
Also - I've set the homepage propety in package.json to be ".".
The last thing I've tried adding -
devServer: {
historyApiFallback: true
}
to webpack.config.js
Note: I added logs to my app, and the value of this.props.location in App.js
is always undefined (not sure if it's should be like that).
index.js (I'm not using redux yet in my app):
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";
import { combineReducers, createStore } from "redux";
import { Provider } from "react-redux";
import targetsReducer from "./Reducers/targets-reducer";
import userReducer from "./Reducers/user-reducer";
import { BrowserRouter } from "react-router-dom";
const allReducers = combineReducers({
targets: targetsReducer,
user: userReducer
});
const store = createStore(
allReducers,
window.window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.window.__REDUX_DEVTOOLS_EXTENSION__()
);
// <Provider store={store}>
// </Provider>,
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
App.js:
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
import "./App.css";
import Login from "./Components/Login";
import SidebarTest from "./Components/Sidebar";
import Navbar from "./Components/Navbar";
import Campaigns from "./Components/Campaigns";
import Targets from "./Components/Targets";
class App extends Component {
constructor(proprs) {
super(proprs);
this.state = {
identifier: "",
isLoggedIn: false
};
this.setIsLoggedIn = this.setIsLoggedIn.bind(this);
}
componentDidMount() {
console.log("In App componentDidMount.");
console.log("current path by window:", window.location.href);
console.log("current path by react (props.location):", this.props.location);
console.log("component state:", this.state);
}
/*Function that passed to Login component that toggles the isLoggedIn state*/
setIsLoggedIn(identifier) {
this.setState(() => {
return {
identifier: identifier,
isLoggedIn: !this.state.isLoggedIn
};
});
}
render() {
console.log("In App render.");
console.log("current path by window:", window.location.href);
console.log("current path by react (props.location):", this.props.location);
console.log("component state:", this.state);
return (
<div className="App">
<div className="banner">
<h1 id="banner-text">
Uranus - Intelligence gathering and Attack system
</h1>
</div>
{!this.state.isLoggedIn ? (
<Route
exact
path="/"
component={() => <Login setIsLoggedIn={this.setIsLoggedIn} />}
/>
) : (
<div className="d-flex" id="page-container">
<div className="sidebar-container">
{this.state.isLoggedIn && <SidebarTest />}
</div>
<div className="navbar-and-data-container">
<div className="navbar-container">
{this.state.isLoggedIn && (
<Navbar identifier={this.state.identifier} />
)}
</div>
<div className="data-container">
<Switch>
<Route
exact
path="/Campaigns"
identifier={this.state.identifier}
component={Campaigns}
/>
<Route
exact
path="/Targets/:campaignName"
component={Targets}
/>
</Switch>
</div>
</div>
</div>
)}
</div>
);
}
}
export default App;
For example:
if as a user i'm in "/" and i refresh the page, i dont have any problem and the screen displayed is the Login page.
But if I'm in "/campaigns" and then refresh the page, I expect the app to display the component registered to "/campaigns" but insted i get page with only the Banner defined in App.js
Another case, when I press a buttun that open a new window with the URL=
"/Targets/:campaignName" again I expect to see the registered component to this URL defined in App.js file, insted i get page with only the Banner defined in App.js
You are probably losing the state isLoggedIn when refreshing / opening a new tab.
You can check that by removing the ternary and see that the right component is rendered on refresh.
How would you persist the fact that a user is logged in? It's usually done with a session cookie.

React class context set sometimes but not all

I'm very new to all of this (about three days in), so forgive my ignorance.
I'm using a combination of React, React-Router, and React-Bootstrap. Like this guy, I want to combine React-Router's <Link /> and React-Bootstrap's <NavItem />'s functionality so that the .active class is applied to the <li> element rather (or as well as) the <a> element.
What's really frustrating is I had this working, and then it stopped when I made, what seemed to me, some unrelated changes (that I believe I've undone, since).
Here's my code:
listlink.js
import React from 'react';
import Router from 'react-router';
import classNames from 'classnames';
let Link = Router.Link;
let ListLink = React.createClass({
contextTypes: {
router: React.PropTypes.func
},
/* copied from React-Bootstrap's NavItem.js */
propTypes: {
onSelect: React.PropTypes.func,
active: React.PropTypes.bool,
disabled: React.PropTypes.bool,
href: React.PropTypes.string,
title: React.PropTypes.node,
eventKey: React.PropTypes.any,
target: React.PropTypes.string
},
render: function () {
/* error happens here */
let isActive = this.context.router.isActive(this.props.to);
/* copied from React-Bootstrap's NavItem.js */
let {
disabled,
active,
href,
title,
target,
children,
...props } = this.props;
let classes = {
'active': isActive,
'disabled': disabled
};
return (
<li { ...props } className={ classNames(props.classNames, classes) }>
<Link to={ this.props.to }>{ this.props.children }</Link>
</li>
);
}
});
module.exports = ListLink;
app.js
import React from 'react';
import Router from 'react-router';
import classNames from 'classnames';
import ListLink from '../components/listlink';
import BS from 'react-bootstrap';
/* ... */
let DefaultRoute = Router.DefaultRoute;
let Route = Router.Route;
let RouteHandler = Router.RouteHandler;
let Nav = BS.Nav;
let Navbar = BS.Navbar;
let App = React.createClass({
render: function () {
return (
<div>
<Navbar brand={this.props.toonName} fixedTop>
<Nav bsStyle='tabs'>
<ListLink to="toon">Character</ListLink>
<ListLink to="spell-book">Spell Book</ListLink>
<ListLink to="on-your-turn">On Your Turn</ListLink>
<ListLink to="battle" disabled={true}>Battle</ListLink>
</Nav>
</Navbar>
<RouteHandler />
</div>
);
}
});
let routes = (
<Route name="app" path="/" handler={App} toonName="Ivellios">
<Route name="toon" handler={Toon} />
<Route name="spell-book" handler={SpellBook} />
<Route name="on-your-turn" handler={OnYourTurn} />
<Route name="battle" handler={Battle} />
<DefaultRoute handler={Toon} />
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler toonName="Ivellios" />, document.body);
});
module.exports = App;
When I set a breakpoint at let isActive =... in listlink.js, it breaks 5 times, one time for each <ListLink> and then a fifth time where this.props.to is toon again. It's this fifth time where this.context.router is undefined, and referencing .isActive throws an error.
I thought maybe it was because of the <DefaultRoute /> declaration, but that doesn't seem to be the case (commenting the line out doesn't make a difference). Regardless of which /#/[page] is in the URL, it's always toon on the fifth iteration, and it always fails with the same error.
Uncaught TypeError: Cannot read property 'isActive' of undefined
I don't know how to debug this any further. Ideas?

Resources