I'm using the react engine react-engine on GitHub to create a node, express app with react for the views.
For the most part, my app is rendered on the server. However, on one page/express route I require the view to be rendered server-side and then for the React to be fully interactive on the client.
So far I've got the view rendering server-side and then being re-loaded/re-mounted by React on the client.
My problem is that I'm now getting the following error:
bundle.js:357 Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) <section data-reactroot="" data-reactid
(server) <section cl
Here's what my code looks like:
class FormCreate extends React.Component {
render() {
return (
<ReactBlank title="Create new application form" messages={this.props.messages} authUser={this.props.authUser}>
<script dangerouslySetInnerHTML={{
__html: 'window.PROPS=' + JSON.stringify(this.props)
}} />
<div id="app-content">
<Main {...this.props}/>
</div>
</ReactBlank>
);
}
}
FormCreate.propTypes = {
messages: React.PropTypes.array,
authUser: React.PropTypes.object,
form: React.PropTypes.object
};
module.exports = FormCreate;
The above is initially rendered on the server and then the following re-mounts it on the client:
var React = require('react');
var ReactDOM = require('react-dom');
var Main = require('./app/views/shared/builder/Main.jsx');
document.addEventListener('DOMContentLoaded', function onLoad() {
const propScript = document.getElementById('react-engine-props');
const props = window.PROPS;
ReactDOM.render(
<Main {...props} />,
document.getElementById('app-content')
);
});
Can anyone see a problem here?
Related
I'm learning react and node js with express framework and I'm working on a project where I need to retrieve API data from express to react.
I retrieved data from backend express js where I made a simple json value. My backend server.js code is given below.
server.js
const express = require('express')
const app = express()
const PORT = 3001;
app .get('/api/contents',(req,res)=>{
const contents=[
{
"id":0,
"heading":"Joshua Tree Overnight Adventure",
"content":"A sight in the blue sea..."
},
{
"id":1,
"heading":"Catch waves with an adventure guide",
"content":"Lorem.."
},
{
"id":2,
"heading":"Catch waves with an adventure guide",
"content":"Lorem epsum ..."
}
];
res.json(contents)
})
app.listen(PORT,()=>{
console.log("express server is running...")
})
In react app, I used axios to retrieve those values from backend and tried to pass the api values of content with id= 0 as props in "TheLatestArticles" component. I have updated proxy in package.json in react to connect backend. The below code is the mainhomepage component where it is enclosed with TheLatestArticles component with props value
MainHomePage.js
import axios from 'axios';
import {useState,useEffect} from 'react'
function MainHomePage(){
const [content,setContent]=useState([]);
useEffect(()=>{
const fetchPosts = async ()=>{
const res =await axios.get("/api/contents")
setContent(res.data)
console.log(res)
}
fetchPosts()
},[])
return (
<>
<TheLatestArticle content={content} />
</>
);
}
export default MainHomePage;
TheLatestArticle.js
import cardImage from "./../../Images/card.jpg"
import './TheLatestArticleCard.css';
const TheLatestArticleCard=(props)=>{
console.log(props)
return(
<>
<div className="card">
<div className="image">
<img src={cardImage} alt="cardimg"/>
</div>
<div className="content">
<p className="heading">{props.content.heading}</p>
<p className="body-content">{props.content.content}</p>
<div className="details">
<p className="details-info">Travel <span className="details-info-2">/ August 21 2017</span></p>
</div>
</div>
</div>
</>
)
}
export default TheLatestArticleCard;
When I run the application, It displayed all the api values in the screen given below.
I console.log the api values inside useEffect and it displayed all the api values perfectly.
But when I refresh the page, the api value became undefined and gave me this error
Can you please solve me this issue with the reason behind this error? Thanks a lot!
Try it like this
{(content && content.length > 0) && <TheLatestArticle content={content} />}
Since your API call is async there won't be any data in content initially. After a while, your API is called and data is fetched. Then you will have data. To prevent TheLatestArticle to blow up we add some conditions when to show that component. The error in the screenshot is when you try to use a property heading from content where content is empty.
Now with the condition, TheLatestArticle will not render until there is some data.
Update
You are using <TheLatestArticle content={content} /> and content is assumed to be an object. But as per your code, it's an array. If you are not already using content.map((c)=> <TheLatestArticle content={c} />) you should do that.
Trying to understand the best method to send html,css, js files back to the client via a Post request.
I'm running express,react.
What I have so far is a basic post route, that returns the compiled component with data (using handlebars) as a response. However the event handlers, css and js are absent. I'm not sure how to serve these along with the HTML as a response to an AJAX POST request on another domain.
I'm using webpack for SSR and figured this would work much the same but it doesn't.
Here is what i have so far...this just returns the html from my react component.
app.post("/", async (req, res) => {
const theHtml = `
<html>
<head><title>My First SSR</title>
<link href='http://localhost:8080/app.css'></link>
<script src="http://localhost:8080/app.js" charset="utf-8"></script>
<script src="http://localhost:8080/vendor.js" charset="utf-8"></script>
</head>
<body>
<h1>My First Server Side Render</h1>
<div id="reactele">{{{reactele}}}</div>
</body>
</html>
`;
const hbsTemplate = hbs.compile(theHtml);
const reactComp = renderToString(<App />);
const htmlToSend = hbsTemplate({ reactele: reactComp });
res.send(htmlToSend);
});
The above works and is returned just without js,css event handlers etc..
here is the App component
class App extends React.Component {
constructor() {
super();
this.handleButtonClick = this.handleButtonClick.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
this.handleReset = this.handleReset.bind(this);
this.state = {
name: "",
msg: ""
};
}
//Handlers
handleButtonClick = e => {
const nameLen = this.state.name.length;
if (nameLen > 0) {
this.setState({
msg: `You name has ${nameLen} characters including space`
});
}
};
handleTextChange = e => {
this.setState({ name: e.target.value });
};
handleReset = () => {
this.setState({ name: "", msg: "" });
};
//End Handlers
render() {
let msg;
if (this.state.msg !== "") {
msg = <p>{this.state.msg}</p>;
} else {
msg = "I have a state of none";
}
return (
//do something here where there is a button that will replace the text
<div>
<header className="App-header">
{/* <img src={logo} className="App-logo" alt="logo" /> */}
{/* <img src={logo} className="App-logo" alt="logo" /> */}
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<label>Your name </label>
<input
type="text"
id="txtName"
name="txtName"
value={this.state.name}
onChange={this.handleTextChange}
/>
<button id="btnSubmit" onClick={this.handleButtonClick}>
Calculate Name Length
</button>
<button id="btnReset" onClick={this.handleReset}>
Reset All
</button>
<hr />
{msg}
</div>
);
}
}
export default App;
On the client side i'm just appending the html to a blank page.
A few questions,
How do I maintain the eventhandlers on the requested html?
How do send the .css and .js along as well?
For some context, I'd like to avoid having to place and maintain 'client' code on my front-end server? My hope was something like webpack would handle this for me?
Thanks for any tips/suggestions.
EDIT:: To clarify this works if I access the route directly. I get the correlating js and css. Just not via a post request from another domain. I assume I'm missing some fundamental udnerstanding how the dom is maintained and scripts run.
You just need a static directory and a view renderer.
In your app.js or where ever you have initialized your express instance, add the following
var hbs = require('hbs');
var path = require('path');
var express = require('express');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
Now you can just put your html in a hbs file in a folder called 'views' and just return it like
app.post("/", (req, res) => {
res.render('index.hbs', {variable: 'value'});
});
For the static assets add the following lines
app.use(express.static(path.join(__dirname, 'public')));
and put your js and css or images or whatever static files you want in a folder named 'public' in your project root. These will be accessible at http://localhost:8080/app.js
So my issue was on client end (my second domain). It was how i was loading the html into the DOM, my old method was inneHTML on the body. Simple answer is this doesnt work.
I had to load it in another way, I chose to use jquery's .html(), this triggers the dom to evaluate the scripts etc.
#RohithBalaji your comment helped me find my issue.
Most AJAX APIs use the browser's .innerHTML to set the content. And
that will strip the and tags, but the scripts
will execute. But, since the head is stripped I am guessing they
aren't loading?
new to React development.
I am building an API client for the BitTrex Exchange API, using their node.js wrapper: node.bittrex.api
With the objective of simply testing the websocket subscription for orderbook updates within the ReactJS framework, here are the steps I have taken:
-Used create-react-app to create the app.
-used npm to install the node.bittrex.api
-added the bittrex client object to the top of the default App.js component and configured options with proper API keys
-added a button (with handler, binded to the button according to React docs) to the default App.js main component,
-inside the handler function, initiated the websocket subscription according to the example code in the node.bittrex.api docs.
The app comes up, but when I press the button, I get an error saying that TypeError Websocketclient() is not a constructor, in the line inside the SignalR.js that creates the websocket connection:
Now I suspect that there is just something specific with the React framework that is screwing this up. Can anyone please help me understand the intricacies? Thanks. Here is my App.js:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
var bittrex = require('../node_modules/node.bittrex.api/node.bittrex.api.js');
bittrex.options({
'apikey' : process.env.REACT_APP_BTREX_API_KEY,
'apisecret' : process.env.REACT_APP_BTREX_SECRET_KEY,
'verbose' : true,
'cleartext' : false,
'baseUrl' : 'https://bittrex.com/api/v1.1'
});
class App extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
bittrex.websockets.subscribe(['BTC-ETH','BTC-ANS','BTC-GNT'], function(data) {
if (data.M === 'updateExchangeState') {
data.A.forEach(function(data_for) {
console.log('Market Update for '+ data_for.MarketName, data_for);
});
}
});
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<br/>
<button onClick={this.handleClick}>Start WS Sub</button>
</div>
);
}
}
export default App;
I've been trying to figure out how to render react on the server (node/express) and finally found a simple enough tutorial to understand what's going on. But now, after setting everything up, I'm getting an error in the React.render method:
here's my component file:
var React = require('react');
var box = React.createClass({
render: function() {
return (
<div style='padding: 10px'>
this.props.text
</div>
);
}
});
React.render(<box text='testing server side'/>, document.body);
module.exports = box;
I get an error when I run npm start:
document is not defined
how do I get around this? do I need or not need the render method?
to give more context, this box component is being required by another component:
var React = require('react');
var Box = require('../react-jsx/box.js'); //this is the box component
var Component = React.createClass({
render: function() {
return (
<html>
<head>
<title>
React Server Rendering
</title>
</head>
<body>
<Box text='testing'/>
<script src="public/bundle.js"></script>
</body>
</html>
);
}
});
module.exports = Component;
and this is all being used in index.js
require('node-jsx').install();
var React = require('react');
var Component = require('../custom-modules/test-react-server-module.js');
var express = require('express');
var router = express.Router();
router.get('/react', function(req, res, next) {
var markup = React.renderToString(Component());
res.send(markup);
});
module.exports = router;
and if I remove the render method I get this error in the browser:
Cannot read property '__reactAutoBindMap' of undefined
I saw some people saying that may be due to the jsx transformer being old, but I think I have the latest version
I'm out of ideas
First of all, I'd recommend update your React version. The current version exposes two different Top-Level API's: React, which is generally used to create components and ReactDOM, which exposes DOM-specific methods to be used at the top level of your app.
There is to things to point out here:
You are trying to run an code that is supposed to be executed only at the browser. There is no document in NodeJS. I'd suggest using webpack to pack this component files and serve them on browser.
For an isomorphic React application, you need to have a client.js file that calls the render function for the same component you are trying to render inside index.js. Got it?
Understand the ReactDOM.render, as the documentation states:
Render a ReactElement into the DOM in the supplied container and return a reference to the component (or returns null for stateless components).
If the ReactElement was previously rendered into container, this will
perform an update on it and only mutate the DOM as necessary to
reflect the latest React component.
Keep in mind, again, that ReactDOM.render should be only used a few times and generally at the top level of your app, just one time.
Having said this, your box.js should look like:
var React = require('react');
var box = React.createClass({
render: function() {
return (
<div style='padding: 10px'>
this.props.text
</div>
);
}
});
module.exports = box;
For this to properly work, you will need to create a main component main-component-file.js:
var React = require('react');
var Box = require('../react-jsx/box.js'); //this is the box component
var Component = React.createClass({
render: function() {
return (
<html>
<head>
<title>
React Server Rendering
</title>
</head>
<body>
<Box text='testing'/>
<script src="public/bundle.js"></script>
</body>
</html>
);
}
});
module.exports = Component;
Inside your bundle.js you need to make sure that this is being called so the main component tries to re-render:
var React = require('react');
var ReactDOM = require('react-dom');
var Component = require('main-component-file.js');
ReactDOM.render(<Component/>, document.body);
At last but not least: index.js, the server file. Change React.renderToString to ReactDOMServer.renderToString, create a React Element from your main component and use it:
var element = React.createElement(Component)
router.get('/react', function(req, res, next) {
var markup = ReactDOMServer.renderToString(element);
res.send(markup);
});
Don't forget to include the npm package react-dom/server at your index.js.
References used in the article:
React.createElement
ReactDOM.render() docs
ReactDOMServer docs
You should delete this
React.render(<box text='testing server side'/>, document.body);
There is no need for it. What you are essentially telling React to do is render it right then and there.
document.body doesn't exist yet because you are rendering it server-side and you also don't need it because you render the component in the renderToString function upon request in the router. (Also I think #PeterLyons is correct so take a look at his answer too).
Also if you are strictly using React for views only, you might want to take a look at express-react-views. They have a good tutorial of how to use React with Express and you essentially can use it for server-side rendering only. I don't think it's as ideal as using react-router depending on what you're building but it illustrates how React is handling the server-side rendering and works with Express.
You need to use the react-dom/server package's renderToString function in the server environment. This will return the HTML as a string which you can send in your express response.
From here :
"The only way to get a handle to a React Component instance outside of React is by storing the return value of React.render."
I need to render a React component outside React and the reason for it I'm going to mention below.
In my node.js, expressJS app, I am using 'react-router-component' and 'react-async'.
In app.js -the file which is supposed to be run ,
var url=require('url');
var App=require('./react/App.jsx');
var app = express();
app.get('*',function(req,res){
//}); SEE EDIT 1 BELOW
var path = url.parse(req.url).pathname;
ReactAsync.renderComponentToStringWithAsyncState(App({path:path}),function(err, markup) {
res.send('<!DOCTYPE html>'+markup);
});
});
In App.jsx,
PostList = require('./components/PostList.jsx');
var App = React.createClass({
render: function() {
return (
<html>
<head lang="en">
</head>
<body>
<div id="main">
<Locations path={this.props.path}>
<Location path="/" handler={PostList} />
<Location path="/admin" handler={Admin} />
</Locations>
<script type="text/javascript" src="/scripts/react/bundle.js"></script>
<script type="text/javascript" src="/scripts/custom.js"></script>
</body>
</html>
});
bundle.js is the browserified file from all the .jsx files.
In PostList.jsx,
var PostList = React.createClass({
mixins: [ReactAsync.Mixin],
getInitialStateAsync: function(cb) {
if(typeof this.props.prods==='undefined'){
request.get('http://localhost:8000/api/cats_default', function(response) {
cb(null, {items_show:response.body});
});
}
},
setTopmostParentState: function(items_show){
this.setState({
items_show:items_show
});
},
render: function() {
return (
<div className="postList" id="postList">
**// Things go here**
<div className="click_me" >Click me</div>
</div>
}
});
PostListRender=function(cart_prods){
var renderNow=function(){
//return <PostList cart_prods={cart_prods}></PostList>
React.renderComponent(<PostList cart_prods={cart_prods}></PostList>,document.getElementById('postList') );
};
return {
renderNow:renderNow
}
};
module.exports=PostList;
In custom.js:
$('.click_me').click(function(){
PostListRenderObj=PostListRender(products_cart_found);
PostListRenderObj.renderNow();
$('odometer').html('price');// price variable is calculated anyhow
});
The page shows well.
EDIT 3 Starts
Now I want to render the PostList component on clicking the click_me div .
EDIT 3 Ends
But when I click on the click_me element, the browser shows script busy, console shows
ReactJS - ReactMount: Root element has been removed from its original container. New container
And the Firebug log limit exceeds.
So why I want to render on click from outside react.js:
I have to run the jQuery Odomoeter plugin on clicking the click_me div. The plugin was not developed as a node middleware although it can be installed the way a middleware is installed and the plugin codebase is saved inside node_modules folder.
Edit2 Starts:
As the plugin is not a node middleware, I cannot require it from inside node. However I can perform the click event (code not shown ) from inside node and run the following code there as well :
$('odometer').html('price');// price variable is calculated anyhow
In this case I include the plugin in the browser with <script /> tag and the browserified bundle.js comes after the plugin script . But the animation is not properly shown. So I take to the client side click event in the custom.js.
If I do not require the plugin to be a middleware from inside node
and just include it in the page before the browserified JS file and
perform the click event inside React, then the odometer animation is
not properly shown.
Edit2 Ends:
So what is the way to render the PostList React component outside React ?
EDIT 1 The }); was quite mistakenly placed there
I cannot understand your question description, but this answers the title question:
How you render React components outside of react?
MyComponent = require('MyComponent')
element = document.getElementById('postList');
renderedComp = ReactDOM.render(MyComponent,{...someProps},element);
// => render returns the component instance.
$(document).on('something, function(){
renderedComp.setState({thingClicked: true})
})
Inside of react you can just call the component.