React router consume my url request to back-end API - reactjs

I'm building an SPA react-redux app at client-side, asp.net core back-end API.
Everythings run perfectly with IISExpress when debugging.
But when I deploy to IIS as a web application nested default website with the alias "mysubdomain". Everything still runs ok except export function.
(1st case): Open browser, enter download API link: http://localhost/mysubdomain/api/v1/export?filterparam. A save dialogue open. That was my expectation.
(2nd case: normal case): open my site (homepage): http://localhost/mysubdomain then click export, a new window open by the link:
http://localhost/mysubdomain/api/v1/export?filterparam.
I was expecting that savefile popup opening similar to (1st case) but NO. Browse return my components/Layout rendered.
I don't know what happening with react router/route? Or I make something wrong? I guess that react-router just consume my URL-request then render my Component, instead of call to my back-end API.
My export function inside redux store:
export: (filterParams) => async (dispatch, getState) => {
const url = `api/v1/export?${filterParams}`;
window.open(url,'_blank');
}
Back-end API:
[HttpGet]
[Route("download")]
public async Task<IActionResult> Download(DailyDischargeUrlQuery urlQuery)
{
var stream = await _dailyDischargeRepository.ExportAsCsvStream(urlQuery.DischargeDate, urlQuery.MRN, urlQuery.GetCompanies(), urlQuery.GetOrders());
return File(stream, "text/csv", "dailydischarge.csv");
}
index.js
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const history = createBrowserHistory({ basename: baseUrl });
const initialState = window.initialReduxState;
const store = configureStore(history, initialState);
const rootElement = document.getElementById('root');
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
rootElement);
registerServiceWorker();
App.js
import 'core-js';
import React from 'react';
import {Route, Router} from 'react-router';
import Layout from './components/Layout';
import Home from './components/Home';
export default () => (
<Layout>
<Route exact path='/' component={Home}/>
</Layout>
);

The problem has been resolved!
That because of service worker built-in create-react-app.
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.

Related

Let only one page be server-side rendered with Meteor

My Meteor/React application should render one static page besides the reactive one pagers with reactive UIs. The static package does not even need to be "hydrated" with the React magic after displayed in the browser. Though the server-side rendering on the server will be dynamic with React components.
I got it working, but I'm not sure if it is the intended official way to do it.
File import/client/routes.js
...
<Route path="/reactive/pages/:id" component={ReactiveComponent} />
<Route path="/static_url" />
...
File server/main.jsx
...
onPageLoad((sink) => {
if (sink.request.path === '/static_url) {
sink.renderIntoElementById('app', renderToString(
<StaticPage />,
));
}
});
...
File client/main.js
...
import { Routes } from '../imports/client/routes';
Meteor.startup(() => {
...
if (window.location.pathname !== '/offer_pdf') {
render(Routes, document.getElementById('app'));
}
});
...
Especially when rendering dependent on the URI, it seems a little bit hacky to me. Does a more elegant solution exist?
I don't think there is anything official, but in general, of course, it's a good idea to use a router for rendering different pages, so I thought it worth pointing out that you can use react-router on the server as well:
import React from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";
import { StaticRouter } from 'react-router-dom';
import App from '/imports/ui/app.jsx';
onPageLoad(sink => {
const context = {};
sink.renderIntoElementById("app", renderToString(
<StaticRouter location={sink.request.url} context={context}>
<App />
</StaticRouter>
));
/* Context is written by the routes in the app. The NotFound route, used
when, uhm, no route is found, sets the status code. Here we set it on the
HTTP response to get a hard 404, not just a soft 404. Important for Google bot.
*/
context.statusCode && sink.setStatusCode(context.statusCode);
// add title to head of document if set by route
sink.appendToHead(`<title>${context.title || 'My page'}</title>`);
});
In App you can then use the usual Switch and Route tags for specifying different routes. In that you could, for instance, only specify routes that you want to be server-rendered.

Gatsby wildcard route

Is it not possible to route to the same component with a wildcard path?
If in React I have something like:
<Router>
<Switch>
<Route path="/path/:id" children={<Component />} />
</Switch>
</Router>
all the requests:
/path/123
/path/123/p
/path/123/p/1
will route to the same /path/123
How can I tell Gatsby to do the same?
createPage({
path: `/path/123/*`,
component,
context
})
Or what is the solution to this problem, a redirect engine of some sorts?
I think you are looking for client-only routes. Given a page (or template if it's created from gatsby-node.js) you can:
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/Layout"
import SomeComponent from "../components/SomeComponent"
const App = () => {
return (
<Layout>
<Router basepath="/app">
<SomeComponent path="/path" />
</Router>
</Layout>
)
}
export default App
Note: assuming a src/pages/app/[...].js page (File System Route API structure).
When a page loads, Reach Router looks at the path prop of each component nested under <Router />, and chooses one to render that best matches window.location (you can learn more about how routing works from the #reach/router documentation).
Alternatively, you can use an automated approach (plugin: gatsby-plugin-create-client-paths) by:
{
resolve: `gatsby-plugin-create-client-paths`,
options: { prefixes: [`/path/*`] },
},
Which will validate all routes under /path.
Or for a more customizable approach, in your gatsby-node.js:
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/path/)) {
page.matchPath = "/path/*"
// Update the page.
createPage(page)
}
}
Disclaimer: These routes will exist on the client only and will not correspond to index.html files in an app’s built assets. If you’d like site users to be able to visit client routes directly, you’ll need to set up your server to handle those routes appropriately.

Refresh or manual URL typing in SSR React App changes a className

The App was initially made with CRA and converted into SSR. The app works fine but the problem arises only when I refresh the app or manually type in the URL to a certain page. The middle div's classname is then overridden to the classname of the middle div of the base route.
It changes for example from this:
<div id="root">
<div class="header">..</div>
<div class="newlist">..</div>
<div class="footer">..</div>
</div>
to this:
<div id="root">
<div class="header">..</div>
<div class="main-flex-container">..</div>
<div class="footer">..</div>
</div>
For extra information here is my src/index.js:
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import configureStore, { history } from './redux/store/index';
import AppRouter from './routers/AppRouter';
import './styles/styles.scss';
const store = configureStore();
require('dotenv').config();
const isServer = typeof window !== 'undefined';
if (isServer) {
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<AppRouter />
</BrowserRouter>
</Provider>,
document.getElementById('root'),
);
}
and here is my server.js:
import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import configureStore from '../src/redux/store/index';
import AppRouter from '../src/routers/AppRouter';
import { StaticRouter } from 'react-router-dom';
const app = express();
const store = configureStore();
app.use(express.static(path.resolve(__dirname, '..', 'build')));
app.use('/*', (req, res, next) => {
fs.readFile(path.resolve('./build/index.html'), 'utf-8', (err, data) => {
if (err) {
console.log(err);
return res.send(500).send('Error happened!');
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
<AppRouter />
</StaticRouter>
</Provider>,
)}</div>`,
),
);
});
});
app.listen(3000, () => {
console.log('Listening...');
});
In case this isn't enough information, here is the link to the project on GitHub.
So the crux of your problem here seems to be that when rendered on the server, the router isn't rendering the correct component based on the url as we would expect.
What is happening is not that the class name is changed, instead we are actually always rendering the root page and then react replaces the content with the "new task" page on the client before you get a chance to have a look at it in the inspector.
Summary: When using app.use with a path in express, we are mounting an express middleware at the specified path, and express removes the "mount point" from the req.url provided to the middleware. Among other solutions, we can use req.originalUrl to get the actual url. req.originalUrl is a property provided by express that gives the url as it was before any middleware changed it.
Let's investigate!
If we extract the code that renders your app to a separate variable and log that, we will see that the logged HTML string is the page with the "New List" button on it.
const renderedApp = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
<AppRouter />
</StaticRouter>
</Provider>,
);
console.log('Rendered app', renderedApp);
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${renderedApp}</div>`,
),
);
Visiting localhost:3000/l/new and looking in the log:
Rendered app <div class="header"><a class="header__button" id="header__colabico" href="/">COLABI.CO</a><div class="header__right"><button class="header__button" id="header__tweet">TWEET</button><button class="header__button" id="header__logout">LOGOUT</button></div></div><div class="main-flex-container"><a class="home__button" href="/l/new"> <!-- -->NEW LIST<!-- --> </a><p class="home__infotext"> <!-- -->Start by pressing that big button up there!<!-- --> </p></div><div class="footer"><div class="footer__left"><a class="footer__button" href="/privacy">PRIVACY</a><a class="footer__button" href="/terms">TERMS</a></div><p class="footer__colabico"> © colabi.co</p></div>
This looks very much like the root page instead of the new list page.
Aside: Putting the rendering on a separate line instead of inline in the template string also helps with syntax highlighting, as a nice bonus.
Aside 2: By adding this logging you'll also see that your code is not run when hitting the root url. The reason for this is probably because of the static middleware at the start, which will match on the index.html page in your build folder.
Now that we are fairly certain what the problem is, let's try to see why it might be.
The StaticRouter accepts the location as a prop, and we are passing in req.url for that. Let's verify that this is what we think it is.
console.log('URL:', req.url);
When visiting localhost:3000/l/new, this is logged:
URL: /
That is certainly not what we expected!
I went to look up the documentation for req.url to see what the problem might be, and found this method:
req.originalUrl
I couldn't see the documentation for req.url, but there's a helpful box here explaining why that is (req.url comes from Node's http module).
Logging req.originalUrl too just to see what that is for us, and sure enough:
original URL: /l/new
URL: /
req.originalUrl has what we need!
At this point I searched around for references to explain why req.url might be / in our case, and it turns out it's because of this:
When we are using app.use, we are mounting an express middleware at the specified path, rather than setting up a route handler (that would be app.get or similar), and middlewares remove the path they are mounted at from their own url, because they consider that their root. When we specify the middleware to be mounted at /*, the wildcard matches all routes and the whole url is removed.
In other words, we can fix this in a few different ways:
Keep it as it is, but use req.originalUrl instead
Remove the path from app.use (app.use((req, res, next) => {/* your code */}))
Change from app.use to app.get
Hope that helps!

React Context: Provide instance of an Api Manager

I want to keep a single instance of a third party api manager class in my react app.
I'm trying to achieve this using using the React Context Api like this:
Api Context:
import * as React from "react";
import { ApiManager } from "../ApiManager";
const defaultValue = new ApiManager()
const ApiContext = React.createContext(defaultValue);
const ApiProvider = ApiContext.Provider;
const ApiConsumer = ApiContext.Consumer;
const withApi = (enhanced) => {
return (
<ApiConsumer>
{api => enhanced }
</ApiConsumer>
)
}
export default ApiContext;
export {ApiContext, ApiProvider, ApiConsumer, withApi};
in my client I would do something like this:
const api = new ApiManager({
host: apiHost,
baseUrl: apiBaseUrl,
key: apKey,
sessionToken: persistedSessionToken
});
ReactDOM.hydrate(
<Provider store={store}>
<PersistGate loading={<h1>loading...</h1>} persistor={persistor}>
<BrowserRouter>
<ApiProvider value={api}>
<Main />
</ApiProvider>>
</BrowserRouter>
</PersistGate>
</Provider>, document.querySelector('#app')
);
I have 3 questions:
Does this even make sense?
How would I set the sessionToken if it gets renewed by the user?
How do I rehydrate the api instance after a page reload using redux-persist?
If you just want to use one instance of ApiManager then I don't think you need Context API. Just create an instance and export/import it in files where you need it.
You can wrap that third party api manager to add some methods you need like setSessionToken to set/update sessionToken when app loads or when user receives/refreshes it.
Why bother with redux-presist in this case (even if you decide to use Context API) if Redux is not involved. If you want to store sessionToken so store it in localStorage or any other storage you want, it has simple API to set/retrieve values, you don't need to use a library for it.

Meteor, React Router 4, and Authentication

I've been scouring the inet trying to find anywhere that defines how to handle authentication in meteor and react router 4 .
Basically, I want certain routes to only be available to authenticated users. Is there any documentation on it?
Aseel
Meteor has a very well developed User Accounts system. It provides ready libraries for OAuth authentication with Twitter, Facebook, etc. as well as a basic but useful UI packages. Check Meteor's official guide here first.
For implementing routing you need to track Meteor.userId() and change route via Meteor's reactive system called Tracker. Meteor.userId() returns a userId if currently connected user is logged in, and null otherwise. I provide an example code where React Router is used for routing, below. Notice that you'll will also need the historypackage to be installed and imported while working with React Router v4.
In your client/main.js;
import { Meteor } from 'meteor/meteor';
import React from 'react';
import ReactDOM from 'react-dom';
import { Tracker } from 'meteor/tracker'
import {onAuthChange, routes} from "../imports/routes/routes";
Tracker.autorun(function(){
const authenticated = !! Meteor.userId();
onAuthChange(authenticated);
});
Meteor.startup(() => {
ReactDOM.render(routes, document.getElementById('app'));
});
And in your routes.js file;
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory'
import Home from './../ui/components/Home';
import Login from './../ui/components/Login';
import NotFound from './../ui/components/NotFound';
import Signup from './../ui/components/Signup';
const history = createBrowserHistory();
const unauthenticatedPages = ['/', '/signup'];
const authenticatedPages = ['/link'];
const publicPage = function () {
if (Meteor.userId()) {
history.replace('/link');
}
};
const privatePage = function () {
if(! Meteor.userId()) {
history.replace('/');
}
};
export const routes = (
<Router history = {history}>
<Switch>
<Route exact path='/:id' component= {Login} onEnter={publicPage}/>
<Route exact path='/signup' component={Signup} onEnter={publicPage}/>
<Route exact path='/link' render={ () => <Home greet='User'/> } onEnter={privatePage} />
<Route component={NotFound}/>
</Switch>
</Router>
);
export const onAuthChange = function (authenticated) {
console.log("isAuthenticated: ", authenticated);
const path = history.location.pathname;
const isUnauthenticatedPage = unauthenticatedPages.includes(path);
const isAuthenticatedPage = authenticatedPages.includes(path);
if (authenticated && isUnauthenticatedPage) { // pages: /signup and /
console.log(`Authenticated user routed to the path /link`);
history.replace('/link');
} else if (!authenticated && isAuthenticatedPage) {
console.log(`Unauthenticated user routed to the path /`);
history.replace('/');
}
};
Here's a neat way to have public routes and authenticated routes:
https://gist.github.com/lucnat/643988451c783a8428a2811dbea3d168
public components are visible by everyone, they use the PublicLayout
authenticated components are visible by authenticated users only -
they use the AuthenticatedLayout
We could have an arbitrary number of layouts. In the example above, there are two layouts - each with it's own navbar.
I've been trying to get a more updated method using functional components.
I've tried implementing a conditional check similar to the documentation of React-router.
This was working after giving history.push to a desired route after waiting for Meteor.loginWithPassword to complete.
But refreshing the browser ended up rendering login page again.
Meteor is having an intermediate state of Meteor.loggingIn().
Handling this state in the Authentication check fixed this issue.
Feel free to give feedback.
I've created a gist with an implementation for authentication of routes in Meteor - React-router stack with functional components and hooks.
Check this gist with basic structure of the implementation.
https://gist.github.com/rinturj84/0ef61005bf3a4ca5fb665dfc5f77e3d1
Actually, the best idea to do that is to create Multiple separated routers because you can take benefits from using Meteor Reactive-var.
This is a sample :
export default function App() {
if (Meteor.user()) {
return (
<React.StrictMode>
<Global_Router Client={null} About={About} HomeNavbar={HomePageNav} HomePage_Home={HomePage_Home}
HomeFooter={Footer} Homepage_Contacts={Homepage_Contacts}/>
</React.StrictMode>
);
}
else {
return(
<RouterClient/>
);
}
Router client
export const RouterClient = () => {
return (
<Router className="container-fluid">
<Switch >
<Route exact path="/Client" >
<HomeCLient/>
</Route>
<Route path="*">
<Redirect to="/Client" />
</Route>
</Switch>
</Router>
)
};
Respectively,you can create a router for the admin too. In general, that is the most efficient way to do that.

Resources