How to use React in Node? - javascript

I want to try Server Side Render in React, but my code doesn't work.
I should be missing something. But I don't know what it is as I'm new to react.
Here's the code as well:
server/app.js
import express from 'express';
import { renderToString } from 'react-dom/server';
import Home from '../src/index.js';
const app = express();
const content = renderToString(<Home />);
app.get('/', function (req, res) {
res.send(
`
<html>
<head>
<title>ssr</title>
</head>
<body>
<div id="root">${content}</div>
</body>
</html>
`
);
})
app.listen(3001, () => {
console.log('listen:3001')
})
src/index.js
import React from 'react';
const Home = () => {
return (
<div>
<div>Hello World</div>
</div>
)
}
export default Home
package.json
{
"scripts": {
"start": "nodemon --exec babel-node server/app.js"
}
}
.babelrc
{
"presets": [
"env"
],
"plugins": []
}

You can not render react page from nodejs like ejs because react is not like any template. it is a library.But if you actually want it, then you need server side rendering. (SSR)

const content = renderToString(<Home />);
you are using jsx here but not transpiling jsx into js. you need to install
npm i #babel/preset-react
also instead of "babel-preset-env" use this
npm i #babel/preset-env
then
{
"presets": ["#babel/preset-react", ""#babel/preset-env]
}

Related

How do I hydrate a react component from rendered html

I need to hydrate a root element without actually importing the component in my client side javascript. I am rendering the component server-side, where each route will render a different full-page react component.
Here is my server.js:
import express from 'express'
import path from "path"
import React from "react"
import ReactDOMServer from "react-dom/server"
import About from "./about"
import Home from "./home"
const app = express()
const port = 3000
app.get('/', (req, res) => {
render(res, <Home />)
})
app.get('/about', (req, res) => {
render(res, <About />)
})
function render(res, component) {
res.send(`
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>SSR App</title>
</head>
<body>
<div id="root">${ReactDOMServer.renderToString(component)}</div>
<script src="client.js"></script>
</body>
</html>
`)
}
app.use(
express.static(path.resolve(__dirname, "..", "public"))
)
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
You can see I am importing the Home and About components for each of their respective routes. These are full page react components.
Here is what home.js looks like, for example:
import React from "react"
export default function Home() {
const [times, setTimes] = React.useState(0)
return (
<div>
<h1>Home</h1>
<p>clicked {times} times</p>
<button onClick={() => setTimes((times) => times + 1)}>add</button>
</div>
)
}
Now here is the part I need to change. I have a client-side script, and right now, I am importing the Home component there. However, obviously this doesn't work when I visit the /about route, because I don't have hydration code for that particular component. I basically need to dynamically hydrate the root element based on the server-side route rendered html.
Example client.js:
import React from "react"
import ReactDOM from "react-dom/client"
import Home from "./home"
ReactDOM.hydrateRoot(document.getElementById("root"), <Home />)
What I need it to look like:
import React from "react"
import ReactDOM from "react-dom/client"
ReactDOM.hydrateRootDynamically(document.getElementById("root"))
Here are the scripts I am using:
"scripts": {
"client": "esbuild src/client.js --bundle --outfile=public/client.js --loader:.js=jsx",
"server": "esbuild src/server.js --bundle --outfile=public/server.js --loader:.js=jsx --platform=node",
"dev": "npm run client && npm run server && node public/server.js"
},
Like I said this currently works for the / home page, but I want to add more express routes that point to different react full-page components, and I don't want to have to create client-side hydration scripts for every single route.
How do I do this?

taking the user from localhost:3000 to localhost:3000/auth/google using proxy target is not working

when a user clicks on Sign in using Google, the user is to be taken to the Google OAuth flow for the sign-in procedure. But on clicking the browser just changes its url to localhost:3000/auth/google and nothing happens.
It works fine if I explicitly provide the complete href i.e
http://localhost:5000/auth/google
App component:
import './App.css';
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div className="App">
Sign in using google
</div>
);
}
}
export default App;
package.json
{
"name": "client",
"version": "0.1.0",
"private": true,
"proxy": {
"/auth/google": {
"target": "http://localhost:5000/"
}
},
delete your proxy on package.json and try this
create setupProxy.js on your src directory then npm install http-proxy-middleware
const { createProxyMiddleware } = require("http-proxy-middleware");
const proxy = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
createProxyMiddleware(
"/auth/google",
// replace with your endpoint
{ target: "http://localhost:5000" } // replace with your target
)
);
};

How to configure runtime compilation in Vue and Snowpack

I'm trying to get a Vue project setup with runtime compilation, but I'm not quite sure how to configure this in Snowpack.
Basically currently when I run the project I get a blank screen and the usual "[Vue warn]: Component provided template option but runtime compilation is not supported in this build of Vue. Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".
Currently my files look like below:
snowpack.config.js:
/** #type {import("snowpack").SnowpackUserConfig } */
module.exports = {
mount: {
public: '/',
src: '/_dist_',
},
plugins: [
'#snowpack/plugin-vue',
'#snowpack/plugin-dotenv'
],
...
}
index.js:
import { createApp } from "vue";
// import App from "./App.vue";
import First from "./First.vue";
// const app = createApp(App);
const app = createApp({
data() {
return {
message: 'duck',
}
}
});
app.component('first', First);
app.component('ducks', {
props: ['todo'],
template: '<li>{{ todo }}</li>'
});
app.mount("#app");
// Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
// Learn more: https://www.snowpack.dev/#hot-module-replacement
if (import.meta.hot) {
import.meta.hot.accept();
import.meta.hot.dispose(() => {
app.unmount();
});
}
index.html:
...
<body>
<div id="app">
<p>stuff should be fine:</p>
<p>{{message}}</p>
<ul>
<li>hello</li>
<ducks todo="testing"></ducks>
<ducks todo="goats"></ducks>
<ducks todo="canoes"></ducks>
</ul>
</div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/_dist_/index.js"></script>
</body>
...
I've tried adding an alias but that doesn't seem to do anything:
snowpack.config.js
module.exports = {
...
plugins: [
'#snowpack/plugin-vue',
'#snowpack/plugin-dotenv'
]
...
alias: {
'vue': 'vue/dist/vue.esm-bundler.js'
}
Anybody know how I can get runtime compilation setup?
Thanks,
Matt
I managed to fix this, by using import { createApp, h } from "vue/dist/vue.cjs.prod.js";.
But I'm not sure if this will create other issues in the future.

Error importing React component in jest

I am trying to test a basic react component with Jest but am getting an error when importing the component into my test. It works fine outside of my tests.
The error is:
Invariant Violation: _registerComponent(...): Target container is not a DOM element.
App.js
const App = () => (
<h1>Hello World</h1>
)
app.test.js
import React from 'react'
import App from '../app'
test('App renders', () => {
...
}
Add the following to your config.js or package.json :
{
"env": {
"development": {
"plugins": ["transform-es2015-modules-commonjs"]
},
"test": {
"plugins": ["transform-es2015-modules-commonjs", "#babel/plugin-proposal-class-properties"],
"presets": [
"#babel/preset-react"
]
}
}
}

Testing React: Target Container is not a DOM element

I'm attempting to test a React component with Jest/Enzyme while using Webpack.
I have a very simple test #
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';
it('App', () => {
const app = shallow(<App />);
expect(1).toEqual(1);
});
The relative component it's picking up is :
import React, { Component } from 'react';
import { render } from 'react-dom';
// import './styles/normalize.css';
class App extends Component {
render() {
return (
<div>app</div>
);
}
}
render(<App />, document.getElementById('app'));
However, running jest causes a failure:
Invariant Violation: _registerComponent(...): Target container is not a DOM element.
With errors #
at Object.<anonymous> (src/App.js:14:48)
at Object.<anonymous> (src/App.test.js:4:38)
The test files references line 4, which is the import of <App />, that causes a fail. The stack trace says line 14 of App.js is the reason for the failure -- which is nothing more than the render call from react-dom, something I've never had a challenge with (the app renders properly from my Webpack setup).
For those interested (Webpack code):
module.exports = {
entry: './src/App',
output: {
filename: 'bundle.js',
path: './dist'
},
module: {
loaders: [
{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['react', 'es2015']
}
},
{
test: /\.css$/,
loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
},
{
test: /\.scss$/,
loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!sass'
}
]
}
}
And my package.json:
{
"name": "tic-tac-dux",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --devtool eval --progress --colors --inline --hot --content-base dist/",
"test": "jest"
},
"jest": {
"moduleNameMapper": {
"^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"^.+\\.(css|sass)$": "<rootDir>/__mocks__/styleMock.js"
}
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.17.0",
"babel-jest": "^16.0.0",
"babel-loader": "^6.2.5",
"babel-polyfill": "^6.16.0",
"babel-preset-es2015": "^6.16.0",
"babel-preset-react": "^6.16.0",
"css-loader": "^0.25.0",
"enzyme": "^2.4.1",
"jest": "^16.0.1",
"jest-cli": "^16.0.1",
"node-sass": "^3.10.1",
"react-addons-test-utils": "^15.3.2",
"react-dom": "^15.3.2",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
"webpack": "^1.13.2",
"webpack-dev-server": "^1.16.2"
},
"dependencies": {
"react": "^15.3.2",
"react-dom": "^15.3.2"
}
}
Oh, and if anyone is going to say that the div element isn't being loaded before the script, here's my index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>App</title>
</head>
<body>
<div id="app"></div>
<script src="/bundle.js"></script>
</body>
</html>
What could be the reason for this peculiar rendering problem? Something to do with a new Jest update to 15.0?
For anyone else that was combing through the internet like I've been - looking for a solution to this when testing with Jest - I will back up the answer by #biphobe by saying Jest will cause this error to occur when you export something inside the same file that is calling ReactDOM.render.
In my case, I was exporting an object within my index.js where I was also calling ReactDOM.render. I removed this export and voila!
App.jsx is supposed to export the App class and do nothing more, render should be called elsewhere.
If you remove the render call from the App.jsx error should disappear, it pops up because the test environment doesn't supply the DOM with an app id.
As I see, this error arises in many cases and requires different approaches to solve it. My scenario is not the same as the example above, I use redux & router, although I was struggling with the same error. What helped me to solve this problem is to change index.js from:
ReactDOM.render(
<Provider store={store}>
<AppRouter />
</Provider>,
document.getElementById("root")
);
registerServiceWorker();
to:
ReactDOM.render(
(<Provider store={store}>
<AppRouter/>
</Provider>),
document.getElementById('root') || document.createElement('div') // for testing purposes
);
registerServiceWorker();
I found a solution for this error to my use case: Using the same Redux store React is using outside of React.
In trying to export my React's Redux store from index.tsx to be used somewhere else outside of the React application, I was getting the same error while running Jest tests (which make use of Enzyme) in the App.tsx file.
The error
The initial code that didn't work when testing React looked like this.
// index.tsx
import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import { applyMiddleware, compose, createStore } from "redux";
import App from "./components/App";
import { rootReducer } from "./store/reducers";
import { initialState } from "./store/state";
const middlewares = [];
export const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middlewares)),
);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root"),
);
The solution that worked for me
Separate the Redux store logic into a new file named store.ts, then create a default export (to be used by index.tsx, i.e., the React application) and a non-default export with export const store (to be used from non-React classes), as follows.
// store.ts
import { applyMiddleware, compose, createStore } from "redux";
import logger from "redux-logger";
import { rootReducer } from "./store/reducers";
import { initialState } from "./store/state";
const middlewares = [];
export const store = createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middlewares)),
);
export default store;
// updated index.tsx
import * as React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
import App from "./components/App";
import store from "./store";
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root"),
);
Using the Redux store in non-React classes
// MyClass.ts
import { store } from "./store"; // store.ts
export default class MyClass {
handleClick() {
store.dispatch({ ...new SomeAction() });
}
}
The default export
A small note before you go. Here is how to use the default and the non-default exports.
default export store; is used with import store from "./store";
export const store = ... is used with import { store } from "./store";
Hope this helps!
https://nono.ma/says/solved-invariant-violation-target-container-is-not-a-dom-element
Make sure in your test file you have well imported the render component.
It should be imported from #testing-library/react not from react-dom:
import { render } from '#testing-library/react';
Well we cant stop the developers from exporting component from any file and test it in isolation even if it have a react-dom import or usage in it .I mean what's wrong in it . We are not trying to disturb the whole file and test out some pieces of it as long as that is a valid piece of code .
Jest does not have an issue with react-dom , however conceptually they are diff . Jest is supposedly a browserless virtual test environment . React-DOM is a library which does the stitching of virtual DOM to real DOM for react components .
So obvious enough we can/should not test it in a normal way . But that is not the discussion for now. we are fine as long as our exported components are testable .
So Lets mock it
I did the mock in the testSetup file configured with "setupFilesAfterEnv" in jest config .
jest.mock("react-dom", () => {
return ({
"render": jest.fn()
})
})
That is pretty much worked for me. My react and react-dom code now happily go together in one file , works in browser and in the testing environment as well .
I have not encountered any issues because of this . If there is any I will be looking into the comment section
This solution worked for me. Just render if the element is there:
const root = document.getElementById('root');
if (root) {
render(
<App />,
root,
);
}
I found out this error can also be thrown when working with Portals in your tests. If you want to skip the error you can either mock Portals or add the Portal container element in your render method:
render (
<div>
<TestedComponent />
<div id="portal" />
</div>
)

Categories

Resources