I'm trying to use React with my Express backend. Currently, i'm using React as my template language.
My views are .jsx files.
/* Express */
const express = require('express');
const app = express();
const server = require('http').createServer(app);
const session = require('express-session');
...
/* Express application parameters */
app
.set('views', __dirname + '/views')
.set('view engine', 'jsx')
.engine('jsx', require('express-react-views').createEngine())
.use(bodyParser.urlencoded({ extended: false }))
.use(sessionStorage)
.use(express.static('public'));
// Express routing defined in ./routes/index.js
require('./routes')(app);
index.js
/* Express routing */
module.exports = function (app) {
app.get('/subscribe', require('./subscribe').get);
app.post('/subscribe', require('./subscribe').post);
app.get('/', require('./login').get);
app.post('/', require('./login').post);
app.all('/userlist', require('./home').all);
app.get('/lobby', require('./lobby').get);
app.get('/playdario', require('./game').get);
app.post('/playdario', require('./game').post);
app.all('/logout', require('./logout').all);
}
This is my route handler for the subscription page :
subscribe.js
exports.get = (req, res) => {
res.render('subscribe', { 'title': 'Subscription' });
}
I'm trying as a test (like in React tutorial) to refresh a Clock component at each second.
subscribe.jsx
var React = require('react');
var ReactDOM = require('react-dom');
var DefaultLayout = require('./layouts/default');
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<DefaultLayout>
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
</DefaultLayout>
);
}
}
module.exports = Clock;
All of my views are composed by a default component which contains the html tags.
default.jsx
var React = require('react');
class DefaultLayout extends React.Component {
render() {
return (
<html>
<head><title>{this.props.title}</title></head>
<body>
{this.props.children}
</body>
</html>
);
}
}
module.exports = DefaultLayout;
But the problem is that the clock is displayed only one time, when I reach the page. But clock is not refreshing at all. I tried another example with a button that is changing text value placed in his state but it's not working too...
Must I use **.jsx* files in my res.render ? When i send an html file like I did at beginning of my project, i did not found how to send informations from Express through my html file like I can do with views like .twig or .jsx...
What I did wrong?
I don't believe that express-react-views actually mounts the components. As per its documentation:
This is an Express view engine which renders React components on
server. It renders static markup and does not support mounting those
views on the client.
So no components are mounted and only the markup generated is rendered. That means that componentDidMount is never called because the component itself is never instantiated in browser. If you want the React component to interact with elements, you'll need to render it client-side.
Since it is not re-rendering, I assume tick() is not triggered, because the state is updated there. Try doing the following:
componentDidMount() {
this.timerID = setInterval(
this.tick,
1000
);
}
or
componentDidMount() {
var self = this
this.timerID = setInterval(
() => self.tick(),
1000
);
}
since this is not in the scope of the arrow function
Related
in my react native main file (App.js) I have a handler for firebase notifications, in the foregroundNotifications listener I want to check current route name but problem is I don't have access to navigation prop, that's why I created a ref to navigation in another file which I then import in App.js.
In my RootNavigation.js
import * as React from 'react';
export const isReadyRef = React.createRef();
export const navigationRef = React.createRef();
export function navigate(name, params) {
if (isReadyRef.current && navigationRef.current) {
// Perform navigation if the app has mounted
navigationRef.current.navigate(name, params);
} else {
// You can decide what to do if the app hasn't mounted
// You can ignore this, or add these actions to a queue you can call later
}
}
This allowed me to use navigate (added the method following another stackoverflow post) , but how can I get current route name from NavigationRoot.js?
How I use it in my App.js (I added navigate, but how can I add getCurrentRoute?)
import { navigationRef, isReadyRef } from '#env/RootNavigation.js';
RootNavigation.navigate('NewScreen');
<NavigationContainer ref={navigationRef} onReady={ () => { isReadyRef.current = true; } }>
<ROOTSTACK1></ROOTSTACK1>
</NavigationContainer>
Your RootNavigation file should be like in this Example.
add this below function in RootNavigation file
function getCurrentRoute(){
if (navigationRef.isReady()) {
const route=navigationRef.getCurrentRoute();
console.log(route);
// sample output {key:"Home-k2PN5aWMZSKq-6TnLUQNE",name:"Home"}
return route.name;
}
}
And wherever you want to use this function
// any js module
import * as RootNavigation from './path/to/RootNavigation.js';
// ...
RootNavigation.getCurrentRoute();
1st time everything, so thanks in advance.
I'm trying to get is so that when I change my "database" (I'll explain the quotations down below), the API request also auto updates. Currently, when I change the "database", the API request is still only retrieving the old data.
const express = require("express");
const app = express();
const workoutPlan = {
ID: 1,
Name: "My Workout Plan - Update"
};
app.get("/api/workout-plan", function(req, res) {
res.json(workoutPlan);
});
app.listen(3001, () => console.log(`Server listening on port 3001`));
So this would be my "database".
Then using a clickHandler, I'm requesting for the Name.
import React from "react";
import Workout from "./Workout";
import axios from "axios";
class WorkoutPlan extends React.Component {
constructor() {
super();
this.state = {
workoutPlan: {}
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
axios
.get("/api/workout-plan")
.then(response => this.setState({ workoutPlan: response.data }));
}
render() {
const { workoutPlan } = this.state;
// const workoutPlan = this.state.workoutPlan;
return (
<div>
<h1>{workoutPlan.Name}</h1>
<button className="button" onClick={this.handleClick}>
Click Me
</button>
<Workout />
</div>
);
}
}
export default WorkoutPlan;
At the moment, clicking the button, properly populates "My Workout Plan - Update", but if I change that in my const workoutPlan to "My Workout Plan - No Update", clicking that button still retrieves "My Workout Plan - Update".
Let me know what I can do to help makes this clearer/easier!
You need to restart the express application each time you make changes to the server. Until you do that, the server is still serving the old constants.
During development, you might want to consider options to make this automatic. I've used nodemon in the past with some success. I'm sure there are plenty of other options too.
I think you have got a cached content from url. U can try add append random query or add header { 'Cache-Control': 'no-cache' } in url to prevent cached data.
Exam: /api/workout-plan?random=12312312.
Bit of an odd situation here - I have a website written in Vue and I want to demo a library I've written in react. I can avoid server side rendering (SSR) by wrapping ReactDOM.hydrate(ReactApp, document.getElementById('react'area')) but I don't want to do that. I want to render everything SSR, but I don't see how it's possible.
Here is my renderOnServer.js for vue:
process.env.VUE_ENV = 'server'
const fs = require('fs')
const path = require('path')
const filePath = './App/dist/server.js'
const code = fs.readFileSync(filePath, 'utf8')
const vue_renderer = require('vue-server-renderer').createBundleRenderer(code)
//prevent XSS attack when initialize state
var serialize = require('serialize-javascript')
var prerendering = require('aspnet-prerendering')
module.exports = prerendering.createServerRenderer(function (params) {
return new Promise((resolve, reject) => {
const context = {
url: params.url,
absoluteUrl: params.absoluteUrl,
baseUrl: params.baseUrl,
data: params.data,
domainTasks: params.domainTasks,
location: params.location,
origin: params.origin,
xss: serialize("</script><script>alert('Possible XSS vulnerability from user input!')</script>")
}
const serverVueAppHtml = vue_renderer.renderToString(context, (err, _html) => {
if (err) { reject(err.message) }
resolve({
globals: {
html: _html,
__INITIAL_STATE__: context.state
}
})
})
})
});
So basically I'm configuring SSR above to read server.js:
import { app, router, store } from './app'
export default context => {
return new Promise((resolve, reject) => {
router.push(context.url)
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
if (!matchedComponents.length) {
return reject(new Error({ code: 404 }))
}
Promise.all(matchedComponents.map(Component => {
if (Component.asyncData) {
return Component.asyncData({ store, context })
}
}))
.then(() => {
context.state = store.state
resolve(app)
})
.catch(reject)
}, reject)
})
}
and server.js above is just looking for the right vue component and rendering. I have a test react component:
import React from 'react'
export default class ReactApp extends React.Component {
render() {
return (
<div>Hihi</div>
)
}
}
and my vue component:
<template>
<div id="page-container">
<div id="page-content">
<h3 class="doc-header">Demo</h3>
<div id="react-page">
</div>
</div>
</div>
</template>
<script>
<script>
import ReactApp from './ReactApp.jsx'
import ReactDOM from 'react-dom'
export default {
data() {
return {
}
},
}
ReactDOM.hydrate(ReactApp, document.getElementById('#react-page'))
</script>
But obviously it won't work because I can't use document in SSR.
Basically, the purpose of hydrate is to match react DOM rendered in browser to the one that came from the server and avoid extra render/refresh at load.
As it was pointed in the comments hydrate should be used on the client-side and React should be rendered on the server with renderToString.
For example, on the server it would look something like this:
const domRoot = (
<Provider store={store}>
<StaticRouter context={context}>
<AppRoot />
</StaticRouter>
</Provider>
)
const domString = renderToString(domRoot)
res.status(200).send(domString)
On the client:
<script>
const domRoot = document.getElementById('react-root')
const renderApp = () => {
hydrate(
<Provider store={store}>
<Router history={history}>
<AppRoot />
</Router>
</Provider>,
domRoot
)
}
renderApp()
</script>
Technically, you could render React components server-side and even pass its state to global JS variable so it is picked up by client React and hydrated properly. However, you will have to make a fully-featured react rendering SSR support(webpack, babel at minimum) and then dealing with any npm modules that are using window inside (this will break server unless workarounded).
SO... unless it is something that you can't live without, it is easier, cheaper and faster to just render React demo in the browser on top of returned Vue DOM. If not, roll up your sleeves :) I made a repo some time ago with react SSR support, it might give some light on how much extra it will be necessary to handle.
To sum everything up, IMO the best in this case would be to go with simple ReactDOM.render in Vue component and avoid React SSR rendering:
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="my-compiled-react-bundle.js"></script>
<script>
function init() {
ReactDOM.render(ReactApp, document.getElementById('#react-page'))
}
init();
</script>
I was wondering if anyone else has had the problem where they have created a Server side rendering NodeJS app which works perfectly locally but then doesn't load server side once deployed to heroku.
I have created an app using Jared Palmer's awesome RazzleJS in combination with Redux, React Router and React Router Config.
The way it works is that in my server.js file I check the component that is loading for a static function called fetchData, if the function exists then the function is run which is a promise based request to an API using axios in a thunk.
In my server.js file another function then runs that checks all promises have completed before finally rendering the HTML for the page.
Locally this works perfectly and even with Javascript disabled the page is loading complete with the data fetched.
Having deployed this to heroku (single dyno - hobby plan) however if I disable javascript the page is loading with the data missing, suggesting that page is being rendered before the promises resolve. The data is then being loaded correctly using the equivalent ComponentDidMount dispatch for the data.
I currently have the following code:
Server.js
function handleRender(req, res) {
const sheet = new ServerStyleSheet();
const store = createStore(rootReducer, compose(applyMiddleware(thunk)));
const branch = matchRoutes(Routes, req.url);
const promises = branch.map(({ route }) => {
let fetchData = route.component.fetchData;
return fetchData instanceof Function
? fetchData(store, req.url)
: Promise.resolve(null);
});
return Promise.all(promises).then(() => {
const context = {};
const html = renderToString(
sheet.collectStyles(
<Provider store={store}>
<StaticRouter context={context} location={req.url}>
{renderRoutes(Routes)}
</StaticRouter>
</Provider>
)
);
const helmet = Helmet.renderStatic();
const styleTags = sheet.getStyleTags();
const preloadedState = store.getState();
if (context.url) {
res.redirect(context.url);
} else {
res
.status(200)
.send(renderFullPage(html, preloadedState, styleTags, helmet));
}
});
}
Example React Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchProductData } from '../thunks/product-data';
class Test extends Component {
static fetchData(store, url) {
store.dispatch(fetchProductData());
}
componentDidMount() {
if(this.props.productData.length === 0 ) {
this.props.fetchProductData() // Successfully fetches the data
}
}
render() {
return (
<div>
{ this.props.productData && this.props.productData.map( (product, i) => {
return <div key={i}>{product.title}</div>
})}
</div>
);
}
}
const mapStateToProps = state => {
return {
productData: state.productData
}
};
const mapDispatchToProps = dispatch => {
return {
fetchProductData(){
dispatch(fetchProductData());
}
}
};
export const TestContainer = connect(mapStateToProps, mapDispatchToProps)(Test);
This is just an example of the component layout as the ones I actually have are quite complex but in this instance productData would be set to [] in defaultState.
Also all reducers and actions are working correctly locally it is only when deployed to Heroku on a hobby plan that the server side rendering seems to no longer work?
So after a morning of research the reason that it wasn't working in my live environment was because I have a HOC wrapping component for the purposes of tracking analytics.
React-router-config however can't handle the fact that the fetchData function is another level deeper in the hierarchy and so all my promises were resolved with null.
Now that I have removed the HOC component again, server side rendering is once again working properly :)
I am following this tutorial: https://crypt.codemancers.com/posts/2017-06-03-reactjs-server-side-rendering-with-router-v4-and-redux/ which i think is the 'standard' way of doing server side rendering in react (?).
Basically what happens is i use react router (v4) to make a tree of all the components that are about to get rendered:
const promises = branch.map(({ route }) => {
return route.component.fetchInitialData
? route.component.fetchInitialData(store.dispatch)
: Promise.resolve();
});
Wait for all those promises to resolve and then call renderToString.
In my components i have a static function called fetchInitialData which looks like this:
class Users extends React.Component {
static fetchInitialData(dispatch) {
return dispatch(getUsers());
}
componentDidMount() {
this.props.getUsers();
}
render() {
...
}
}
export default connect((state) => {
return { users: state.users };
}, (dispatch) => {
return bindActionCreators({ getUsers }, dispatch);
})(Users);
And all this works great except that getUsers is called both on the server and the client.
I could of course check if any users are loaded and not call getUsers in componentDidMount but there must be a better, explicit way to not make the async call twice.
After getting more and more familiar with react i feel fairly confident i have a solution.
I pass a browserContext object along all rendered routes, much like staticContext on the server. In the browserContext i set two values; isFirstRender and usingDevServer. isFirstRender is only true while the app is rendered for the first time and usingDevServer is only true when using the webpack-dev-server.
const store = createStore(reducers, initialReduxState, middleware);
The entry file for the browser side:
const browserContext = {
isFirstRender: true,
usingDevServer: !!process.env.USING_DEV_SERVER
};
const BrowserApp = () => {
return (
<Provider store={store}>
<BrowserRouter>
{renderRoutes(routes, { store, browserContext })}
</BrowserRouter>
</Provider>
);
};
hydrate(
<BrowserApp />,
document.getElementById('root')
);
browserContext.isFirstRender = false;
USING_DEV_SERVER is defined in the webpack config file using webpack.DefinePlugin
Then i wrote a HOC component that uses this information to fetch initial data only in situations where it is needed:
function wrapInitialDataComponent(Component) {
class InitialDatacomponent extends React.Component {
componentDidMount() {
const { store, browserContext, match } = this.props;
const fetchRequired = browserContext.usingDevServer || !browserContext.isFirstRender;
if (fetchRequired && Component.fetchInitialData) {
Component.fetchInitialData(store.dispatch, match);
}
}
render() {
return <Component {...this.props} />;
}
}
// Copy any static methods.
hoistNonReactStatics(InitialDatacomponent, Component);
// Set display name for debugging.
InitialDatacomponent.displayName = `InitialDatacomponent(${getDisplayName(Component)})`;
return InitialDatacomponent;
}
And then the last thing to do is wrap any components rendered with react router with this HOC component. I did this by simply iterating over the routes recursively:
function wrapRoutes(routes) {
routes.forEach((route) => {
route.component = wrapInitialDataComponent(route.component);
if (route.routes) {
wrapRoutes(route.routes);
}
});
}
const routes = [ ... ];
wrapRoutes(routes);
And that seems to do the trick :)