React router 4 - how to handle big apps without nesting routes - reactjs

Context: React app with React Router 4.0
Now that the best practice changed and you should not nest routes, but rather include them in Components as seen in the example, I'm wondering how this will turn out in the long run once the app grows. Will it not be superhard to follow the routes / components if you don't have one central place to look for them?
Have you used rr-4 for bigger apps? What's the best practice?
Pretty scary as I already had to do a lot of refactoring moving from rr-2 to rr-4, I wouldn't want to redo everything when it's much more expensive.

meet the problem too, here is the solution that migrate from react-router 3.0:
app.js
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import routes from './routes';
import createBrowserHistory from 'history/createBrowserHistory';
import injectTapEventPlugin from 'react-tap-event-plugin';
import "./css/style.less";
import {Provider} from 'react-redux';
import {ConnectedRouter, routerMiddleware} from 'react-router-redux';
import {Route} from 'react-router';
import store from './store';
import App from 'components/App';
import {load} from 'lib/utils';
import Dispatcher from 'views/Dispatcher';
injectTapEventPlugin();
export const browserHistory = createBrowserHistory();
export const middleware = routerMiddleware(browserHistory);
class MainApp extends React.Component {
render() {
return (
<Provider store={ store }>
<ConnectedRouter history={ browserHistory }>
<Route path="/" render={ (props) => {
routes.onEnter(props);
const { match } = props;
return (
<App>
<Route path={match.url} exact component={ load("Home") }/>
{
routes.childRoutes.map((route,idx) => {
return <Route
path={`${match.url}/${route.path}`} component={ route.component }
key={idx}
exact={ !!route.exact }
/>
})
}
</App>
);
} }/>
</ConnectedRouter>
</Provider>
)
}
}
ReactDOM.render(<MainApp />, document.getElementById("app"));
router.js
import React from 'react';
import App from 'components/App';
const load = (moduleName) => {
const module = require(`views/${moduleName}/index.js`);
return module.default;
};
export default {
path: '/',
component: App,
indexRoute: { component: load('Home'), },
childRoutes: [
{ path: 'home', component: load('Home'), }
]
}

Related

Context and Provider not accessible via Modal

I do not control the modal components in my company. The I am using redux-toolkit so I am wondering if this is the issue. I have tried a few different iterations trying to get this to work and tried mimicing another app that it works with (standard redux app).
The below code is me trying to mimic the other app that has it working.
my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'proxima-nova/scss/proxima-nova.scss'
import './css/styles.scss';
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit';
import { RcReducer } from './Store/RC/Events';
import { RcDataReducer } from './Store/RC/RcData';
const middleware = [...getDefaultMiddleware()];
export const ReduxStore = configureStore({
reducer: {
RcEvents: RcReducer,
RcData: RcDataReducer,
},
middleware,
});
ReactDOM.render(<App store={ReduxStore} />, document.getElementById('app'));
My app.tsx
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Wrapper from './Components/Wrapper/Wrapper';
import './App.scss';
import Loader from './Components/Common/Loader/Loader';
import { Provider } from 'react-redux';
import StoreProvider from './Store/Store';
const Home = lazy(() => import('./Components/Pages/Home/Home'));
const RcDashboard = lazy(() => import('./Components/Pages/RC/RC'));
const EccDashboard = lazy(() => import('./Components/Pages/ECC/Dashboard/Dashboard'));
const App: React.FunctionComponent<{ store: any }> = ({ store }: { store: any }) => {
return (
<Provider store={store}>
<StoreProvider>
<Router>
<div id='root'>
<Wrapper>
<Suspense fallback={<Loader overlay={true} fullscreen={false} size='xl' />}>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/rc' component={RcDashboard} />
<Route path='/ecc' exact component={EccDashboard} />
</Switch>
</Suspense>
</Wrapper>
</div>
</Router>
</StoreProvider>
</Provider>
);
};
export default App;
I am calling a component that is provided for use within my company. I personally do not have control over how this component acts. It is a modal. The modal does not just append itself to the component it is called in and automatically appends itself to the body tag.
For some reason the neither my context nor my redux store are showing up. I instead get this.
I am currently at a loss as to how I can get this working. I appreciate any help provided.
I would assume that that modal if somehow forwarding the context value, but probably only legacy context. So it might work with an older version or react-redux, but not with a current one.
You'll have to either use a Portal in the modal instead of creating a completely new ReactDOM instance (which is probably happening right now) or forward the current value to a new in the modal, like this
function YourComponent(){
const store = useStore()
return <Modal><Provider store={store}>whatEverGoesIntoYourModal</Provider></Modal>
}

How to use jest & enzyme to test code splitting (code splits at the routes level using react-loadable)

I have a React project where I am using react-loadable to split a code at the route level. I’m also using Redux to manage the app’s store.
What I'm looking for is the way I can use Jest & Enzyme in order to test my routes (for the routes I’m using connected-react-router and react-loadable for splitting code).
An example of the route below:
import Loadable from 'react-loadable';
const AsyncExampleScreen = Loadable({
loader: () => import("ExampleScreen"),
loading: LoadingComponent
});
<Route
exact
path='/'
component={AsyncExampleScreen}
/>
My index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore, { history } from 'store';
import { ConnectedRouter } from 'connected-react-router';
import App from 'App';
const STORE = configureStore();
const render = () => {
ReactDOM.render(
<Provider store={STORE}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
}
render();`
Example of a test (in the case I’m not using react-loadable for splitting at the route level)
import React from 'react';
import { mount } from 'enzyme';
import { MemoryRouter } from 'react-router';
import { Provider } from 'react-redux';
import configureStore, { history } from 'store';
import App from 'App';
import Example from 'ExampleScreen';
const STORE = configureStore();
describe('routes using memory router', () => {
it('should show Home component for route /', () => {
const component = mount( <MemoryRouter initialEntries = {['/']} >
<Provider store={STORE}>
<App />
</Provider>
</MemoryRouter>
);
expect(component.find(Example)).toHaveLength(1);
})
})

Render React Component based on React-Route

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'.

Access match object at navbar - React, Redux and TypeScript

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.

How include a Layout template in react router?

I am working in a react application using redux, react-router and react-router-redux. So it is a simple App and in this application I need use a simple, route view. I have read the documentation and can made mi route without problems, but at moment of added a Template at routes I had problems. I need pass the dispatched and State of the reducers to the views and the Template for can manipulate spinners and other elements in the Template. I don't sure the how make it or if I am making correctly.
Could you help me, please?
This is my router code:
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {Route} from 'react-router';
import {ConnectedRouter} from 'react-router-redux';
import {Switch} from 'react-router-dom';
import {store, history} from './store/index';
import Layout from './components/Layout';
import './bootstrap-3/css/bootstrap.min.css';
/* Components */
import Home from './components/abouts/home';
import About from './components/abouts/about';
import NoMatch from './components/abouts/nomatch';
import Artists from './components/artists';
const NodeId = document.getElementById('root');
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Layout>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/artists" component={Artists}/>
<Route component={NoMatch}/>
</Switch>
</Layout>
</ConnectedRouter>
</Provider>, NodeId);
So I don't sure of how pass the redux State and the dispatched to component of route, So I am doing something like that in each component, I thing that there is one better form of do it.
import {connect} from 'react-redux';
import * as artistActions from './actions/artistActions';
import * as globalConfigActions from './actions/globalConfigActions';
class Abouts extends Component {
render() {
console.log('Abouts Component: ', this);
return (
<div>
<h1>About</h1>
</div>
);
}
}
const mapStaeToProps = (state) => {
return {
artist: state.artist,
globalConfig: state.globalConfig
}
};
const mapDispatchToProps = (dispatch) => {
return {
fetchArtist: () => {
return dispatch(artistActions.fetchArtist());
},
globalConfigActions: {
showSpinner: () => {
dispatch(globalConfigActions.showSpinner());
},
hiddenSpinner: () => {
dispatch(globalConfigActions.hiddenSpinner());
}
}
}
};
export default connect (mapStaeToProps, mapDispatchToProps)(About);
Of that form I can pass the State and the dispatched to component to each main component but I need pass the State and the dispatched to the Template, when I have tried do it with the template the routes doesn't work and the view doesn't change.
If I use one simple template without the "mapDispatchToProps" and "mapStaeToProps" of redux it works perfect but the layout doesn't have access to methods or state of redux.
Someone could say me how I can make it correctly, please?
Thank you very much

Resources