I'm using the latest ASP.NET 2.0 react SPA template, which is created with the command dotnet new react. It works great out of the box, with hot module replacement (code edits update in browser automatically).
Then I wanted to add a top level app container, called App to hold application state, as described in this blog post:
react without redux article. Should be simple, right? Note that the article uses javascript, but I'm using Typescript, since that's how the template was designed. But after adding a simple top level container component and updating the hot loader config, it doesn't work. I get an error that the component doesn't know how to reload itself. This is the code that is working (from the template):
import './styles/site.scss';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from './routes';
let routes = RoutesModule.routes;
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
ReactDOM.render(
<AppContainer>
<BrowserRouter children={ routes } basename={ baseUrl } />
</AppContainer>,
document.getElementById('react-app')
);
}
renderApp();
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./routes', () => {
routes = require<typeof RoutesModule>('./routes').routes;
renderApp();
});
}
And this is the code after my changes, that is NOT working:
import './styles/site.scss';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from './routes';
import App from './components/App';
import * as AppModule from './components/App'
let routes = RoutesModule.routes;
const render = (Component: any) => {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
//const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>,
document.getElementById('react-app')
);
}
render(App);
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./components/App', () => {
const nextRoot = (require('./components/App') as typeof AppModule).default;
render(nextRoot);
});
}
For reference, here's the ./routes file (I did not change this file):
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
export const routes = <Layout>
<Route exact path='/' component={ Home } />
<Route path='/counter' component={ Counter } />
<Route path='/fetchdata' component={ FetchData } />
</Layout>;
And here's my new App container component:
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from '../routes';
import { ReactElement } from "react";
let routes = RoutesModule.routes;
interface AppState {
stateDescription: string;
}
export default class App extends React.Component<{}, {}> {
constructor(props: any) {
super(props);
this.state = { stateDescription: 'My Cool App State' };
}
public render() {
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
return (
<BrowserRouter children = { routes } basename = { baseUrl } />
);
}
}
Any ideas or suggestions would be much appreciated!
I was able to resolve this by reverting my boot.tsx file to the original template version, and placing my App component in the routes.tsx file instead. You can see I just wrapped everything in the new App element. Here's the new routes.tsx file:
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { App } from './components/App';
export const routes = <App>
<Layout>
<Route exact path='/' component={ Home } />
<Route path='/counter' component={ Counter } />
<Route path='/fetchdata' component={ FetchData } />
</Layout>
</App>;
And here's my modified App component that works with this version:
export class App extends React.Component<AppProps, {}> {
constructor(props: AppProps) {
super(props);
this.state = { stateDescription: 'My Cool App State' };
}
public render() {
return (
<div className='appContainer'>
{ this.props.children }
</div>
);
}
}
Related
I have an app which just loads a blank page on start up, yet refreshing the page causes it to load correctly.
This is what I'm trying:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './Assets/css/app.css';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
window.processIntent = intent => {
...cordova stuff
};
window.setupAltIntent = () => {
...cordova stuff
};
function startApp() {
window.intentUrlToPrint = '';
window.setupAltIntent();
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
}
if (!window.cordova) {
startApp();
} else {
document.addEventListener('deviceready', startApp, false);
}
App.js
import React, { Component } from 'react';
import AppActivity from './Views/activity/AppActivity';
import Header from './Components/header';
import AppRouter from './AppRouter';
class App extends Component {
componentDidMount() {
if (navigator.splashscreen) {
navigator.splashscreen.hide();
}
}
render() {
return (
<div>
<div>
<Header />
<AppRouter />
</div>
<AppActivity />
</div>
);
}
}
export default App;
AppRouter.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './Pages/Home';
import Contact from './Pages/Contact';
import LogInForm from './Pages/RegisterAndLogin/LogIn';
const AppRouter = () => (
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/contact" component={Contact} />
<Route path="/login" component={LogInForm} />
</Switch>
</div>
);
export default AppRouter;
Header.js
import React, { Component } from 'react';
import HeaderNotLoggedIn from './Headers/HeaderNotLoggedIn';
import HeaderLoggedIn from './Headers/HeaderLoggedIn';
class Header extends Component {
constructor(props) {
super(props);
this.isLoggedOn = this.isLoggedOn.bind(this);
}
isLoggedOn() {
return localStorage.getItem('user');
}
render() {
const header = this.isLoggedOn() ? <HeaderLoggedIn /> : <HeaderNotLoggedIn />;
return <div>{header}</div>;
}
}
export default Header;
Quite new to react, is there anything I'm missing here?
Why does the app only load correctly on page refresh?
I don't have a way to to solve your exact problem, because there are a lot of components, logic, and connections I don't have access to, and so I have to make guesses about. I assume that setupAltIntent returns a promise (though it doesn't totally matter if it does something else). In App I set it up to conditionally render Header once the data has been loaded into the state, which happens when setState is called after the promise is resolved.
Hopefully this gets you far enough to figure out what exactly you need for your own solution.
Link to my solution:
Unfortunately, I can't get React Router to work in my custom meteor boilerplate and I really can't figure out why. Here's all the files that could potentially be relevant to the problem:
\client\main.js:
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { renderRoutes } from '../imports/startup/client/routes.jsx';
Meteor.startup(() => {
render(renderRoutes(), document.getElementById('app'));
});
\imports\startup\client\routes.jsx:
import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
// route components
import App from '../../ui/App.jsx';
export const renderRoutes = () => (
<Router>
<div>
<Route path="/" component={App} />
</div>
</Router>
);
\imports\ui\App.jsx
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>Hey!</h1>
);
}
}
export default withTracker(() => {
return {
};
})(App);
Any idea why the error message might occur? Thanks!
Not sure what version of meteor and react you are using but tha's how i did it on the last project i had.
Try this changes:
import {Router, Route, browserHistory} from 'react-router';
export const renderRoutes = () => (
<Router history={browserHistory}>
<div>
<Route path="/" component={App} />
</div>
</Router>
);
I am trying to render a specific component inside of another component based on React, React-Router v4, and Redux in my main 'panel' wrapped in a fixed header and sidebar component.
For example when I select an item from the sidebar, I to render the Detail panel and and load the details based on the id, like: <Route path='/item/:id' component={ItemDetail} />
routes.js
import React, { Component } from 'react';
import { RouteHandler, Switch, Route, DefaultRoute } from 'react-router';
import App from './containers/App';
import Login from './containers/Login';
import LobbyDetail from './components/LobbyDetail';
export default (
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/login" component={Login} />
</Switch>
);
app.js:
import React, { Component } from 'react'
import { Router, Route, Link } from 'react-router'
import { connect } from 'react-redux'
import PropTypes from 'prop-types';
import auth from '../actions/auth';
import Sidebar from '../Components/Sidebar'
class App extends Component {
static propTypes = {
};
/**
*
*/
render() {
const { ... } = this.props
return (
<div className="container-fluid">
<div className="row">
{* I WANT TO RENDER DYNAMIC COMPONENT HERE *}
</div>
<Sidebar currentUser={currentUser}
logout={logout}
/>
</div>
);
}
}
// ...
export default connect(mapStateToProps, mapDispatchToProps)(App)
index.js (basically main app):
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createMemoryHistory } from 'history';
import routes from './routes';
import configureStore from './store/store.js';
import { AppContainer } from 'react-hot-loader';
const syncHistoryWithStore = (store, history) => {
const { routing } = store.getState();
if (routing && routing.location) {
history.replace(routing.location);
}
};
const initialState = {};
const routerHistory = createMemoryHistory();
const store = configureStore(initialState, routerHistory);
syncHistoryWithStore(store, routerHistory);
const rootElement = document.querySelector(document.currentScript.getAttribute('data-container'));
const render = () => {
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<ConnectedRouter history={routerHistory}>
{routes}
</ConnectedRouter>
</Provider>
</AppContainer>,
rootElement
);
}
render();
if (module.hot) { module.hot.accept(render); }
What you're looking for is parameterized routing. Make a <Route/> like the following: <Route path='/item/:id' component={ MyComponent } />.
Now in MyComponent you can use the value of props.match.params.id to conditionally render, or if you're trying to load async data based on the value of :id; You can use the componentWillReceiveProps life cycle method and dispatch an action based on the value of this.props.match.params.id.
Note: <Link to='/item/some-item'/> will set the value of match.params.id to 'some-item'.
I need the match object at the navbar. I am using React, Redux and TypeScript.
Here is my boot-client file:
import './css/site.css';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createBrowserHistory } from 'history';
import configureStore from './configureStore';
import { ApplicationState } from './store/initialState';
import * as RoutesModule from './routes';
let routes = RoutesModule.routes;
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
const history = createBrowserHistory({ basename: baseUrl });
// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = (window as any).initialReduxState as ApplicationState;
const store = configureStore(history, initialState);
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing configuration
// and injects the app into a DOM element.
ReactDOM.render(
<AppContainer>
<Provider store={ store }>
<ConnectedRouter history={ history } children={ routes } />
</Provider>
</AppContainer>,
document.getElementById('react-app')
);
}
renderApp();
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./routes', () => {
routes = require<typeof RoutesModule>('./routes').routes;
renderApp();
});
}
This is my boot-server file:
import * as React from 'react';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { replace } from 'react-router-redux';
import { createMemoryHistory } from 'history';
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
import { routes } from './routes';
import configureStore from './configureStore';
export default createServerRenderer(params => {
return new Promise<RenderResult>((resolve, reject) => {
// Prepare Redux store with in-memory history, and dispatch a navigation event
// corresponding to the incoming URL
const basename = params.baseUrl.substring(0, params.baseUrl.length - 1); // Remove trailing slash
const urlAfterBasename = params.url.substring(basename.length);
const store = configureStore(createMemoryHistory());
store.dispatch(replace(urlAfterBasename));
// Prepare an instance of the application and perform an inital render that will
// cause any async tasks (e.g., data access) to begin
const routerContext: any = {};
const app = (
<Provider store={ store }>
<StaticRouter basename={ basename }
context={ routerContext }
location={ params.location.path }
children={ routes } />
</Provider>
);
renderToString(app);
// If there's a redirection, just send this information back to the host application
if (routerContext.url) {
resolve({ redirectUrl: routerContext.url });
return;
}
// Once any async tasks are done, we can perform the final render
// We also send the redux store state, so the client can continue execution where the server left off
params.domainTasks.then(() => {
resolve({
html: renderToString(app),
globals: { initialReduxState: store.getState() }
});
}, reject); // Also propagate any errors back into the host application
});
});
This is the Layout file:
import * as React from 'react';
import {RouteComponentProps} from 'react-router-dom';
import {withRouter} from 'react-router';
import {connect} from 'react-redux';
import * as actions from '../Features/LanguageChanger/actions/languageChanger';
import LayoutProps from '../propertyTypes/LayoutProps';
import {ApplicationState} from '../store/initialState';
import Navbar from '../Features/Navbar/Navbar';
import NavbarContainer from '../containers/NavbarContainer';
class Layout extends React.Component<LayoutProps, {}> {
public render() {
return <div className='main-container'>
<NavbarContainer {...this.props}/>
{this.props.children}
</div>;
}
}
export default withRouter(connect(
(state: ApplicationState) => state.language,
actions.actionCreators,
)(Layout));
And the routes file:
import * as React from 'react';
import {Route} from 'react-router-dom';
import {Switch} from 'react-router';
import {Redirect} from 'react-router';
import Layout from './components/Layout';
import Home from './Features/Home/Home';
import About from './Features/About/About';
import ProductInfo from './Features/ProductInfo/ProductInfo';
import Questions from './Features/Questions/Questions';
import Shop from './Features/Shop/Shop';
import Contacts from './Features/Contacts/Contacts';
export const routes =
<Layout>
<Switch>
<Route path='/:language/about' component={About}/>
<Route path='/:language/product-info' component={ProductInfo}/>
<Route path='/:language/questions' component={Questions}/>
<Route path='/:language/shop' component={Shop}/>
<Route path='/:language/contact-us' component={Contacts}/>
<Route path='/:language' component={Home}/>
<Route path='/' component={() => (<Redirect to={'/en'}/>)}/>
</Switch>
</Layout>;
I need the match object because I need to make requests based on the language in the URL. My languageChanger component is in the Navbar. My solution for the problem is to extract manually the things I nedd from the URL. Has anyone had the same problem?
To get the correct route props in your component you should wrap withRouter with connect
export default connect(/* ... */)(withRouter(Layout);
Which will update your component if the location changed and is not blocked by connect. (You can read more about in the React Router documentation
To get the placeholder :langauge from your path you have to wrap your component in a route component which has the pattern as path property.
Inside of your Layout component:
/* ... */
return (
<div className='main-container'>
<Router path="/:language" render={({ match ) => (
<NavbarContainer {...this.props} language={match.params.language} />
)} />
{this.props.children}
</div>;
);
/* ... */
This way the navbar will be only rendered if it matches the path pattern. You could change render to children but then you have to handle a possible match which is null. React Router has a nice documentation about the possibilities of Route.
Anyway you have to make sure that the value of match.params.language is a real language code because somebody could enter the path /foo in the address bar and your language will be foo.
My app uses a react-leaflet for generating a Map with Markers and Popups. And I need to give a link to the other page from Popup by <Link/> component from react-router.
/* app.js */
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
import App from './components/App';
import Map from './components/Map';
const Root = () =>
<Router history={browserHistory}>
<Route path='/' component={App}>
<Route path='map' component={Map} />
</Route>
<Router>
render(<Root />, document.getElementById('root'));
/* components/Map/index.js */
import React from 'react';
import { Router, Route, browserHistory } from 'react-router';
import App from './components/App';
import Map from './components/Map';
const Map = () =>
<Map>
<Marker position={[10, 10]}>
<Popup>
<div>
<Link to="/">Main page</Link>
</div>
</Popup>
</Marker>
<Map>
export default Map;
But passing through the link I get an error:
<Link>s rendered outside of a router context cannot navigate.
It is because the content of opened Popup is removed from router context and is placed below.
I suppose, that I can to put router.push() into Popup. But maybe is it possible to use a <Link/>?
Thanks!
So, I created ContextProvider component-creator:
import React, { PureComponent, PropTypes } from 'react';
export default function createContextProvider(context) {
class ContextProvider extends PureComponent {
static propTypes = {
children: PropTypes.node,
};
static childContextTypes = {};
getChildContext() {
return context;
}
render() {
return this.props.children;
}
}
Object.keys(context).forEach((key) => {
ContextProvider.childContextTypes[key] = PropTypes.any.isRequired;
});
return ContextProvider;
}
And used it in the creation of maps marker:
import React, { PureComponent, PropTypes } from 'react';
import { Marker, Popup } from 'react-leaflet';
import createContextProvider from '../ContextProvider';
export default class SomeComponent extends PureComponent {
static contextTypes = {
router: PropTypes.object,
// Place here any other parameters from context
};
render() {
const { position, children, ...props } = this.props;
const ContextProvider = createContextProvider(this.context);
return (
<Marker {...props} position={position}>
<Popup>
<ContextProvider>
<div>
{children}
</div>
</ContextProvider>
</Popup>
</Marker>
);
}
}