ReactDOM Hydrate Renders HTML as Text on Client Page - reactjs

I am trying to do a simple test and I know I am missing something simple.
I have two applications, one hosted on node (localhost:3000) and another is hosted on another site (http://development.azure). The section site is not running under node, its standard IIS.
In my node server application i have the following
import { renderToString } from "react-dom/server"
app.get("/", (req, res) => {
const name = 'Marvelous Peter'
res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
res.write("<div id='content'>");
res.write(renderToString(<Hello name={name} />));
res.write("</div></body></html>");
res.end();
});
app.listen(3000)
In my IIS application I am attempting to load the react component using xhr and then hydrate it.
const xhr = require("xhr");
xhr({
method: "GET",
uri: "http://localhost:3000",
}, (err, resp, body) => {
// check resp.statusCode
ReactDOM.hydrate(body, document.getElementById("test"), () => {
});
});
I am seeing the output on my page, however, it is not HTML encoded. I renders as text
<!DOCTYPE html><html><head><title>My Page</title></head><body><div id='content'><div data-reactroot=""><h1>Hello, <!-- -->Marvelous Peter<!-- -->!</h1></div></div></body></html>
I tried returning only the renderToString value and not HTML and still get the same thing, specifically since i have tags in the component
import React from 'react'
const Hello = (props) => (
<div><h1>Hello, {props.name}!</h1></div>
)
export default Hello
I know I am missing something, but I am finding it hard to find info on doing this and would appreciate any help!
Thanks in advance!
EDIT:
Per the suggestion below, I tried to create a simple component and then hydrate it but I still get the same response.
Here is the new component:
import * as React from "react";
import * as ReactDOM from "react-dom";
class Hello extends React.Component {
public load() {
const xhr = require("xhr");
let res = null;
xhr({
method: "get",
sync: true,
uri: "http://localhost:3000"
}, (err, resp, body) => {
res = body;
});
return res;
}
public render(): JSX.Element {
const val = this.load()
return val;
}
}
const target: any = document.querySelector("#main");
if (target) {
ReactDOM.hydrate(<Hello />, target);
}
I am rather stumped right now. Could it have to do with the response header type from the xhr request?
Any suggestions?

JSX !== HTML they may appear the same but they are fundamentally different and cannot be used interchangeably.
<div/> in JSX is React.createElement("div");
<div></div> in HTML is document.createElement("div");
You cannot use HTML (or a HTML string) in place of a function in React that expects JSX (mainly ReactDOM.hydrate, ReactDOM.render, React.createElement("div", null, NEED JSX HERE).
In order to render it not as a string you'll have to use the only API implementation that React provides to turn a HTML string into something resembling JSX which is...
dangerouslySetInnerHTML={{__html: YOUR STRING HERE}}
try this..
ReactDOM.render(<div dangerouslySetInnerHTML={{__html: YOURSTRINGHERE}}/>, document.body)

It's expects an Element, not a string.
ReactDOM.hydrate(
<MyComponent />,
document.getElementById('root')
)
Take that HTML response and create a react component out of it. Then pass it in your .hydrate method.

Related

AWS-React: The specified key does not exist

I made one react app. My app works as expected. This app's target is practice AWS-COGNITO. For Cognito validation I am using amazon-cognito-identity-js package. I made one helper function where I validate the Congnito. and reuse it in different component. I split my Nav bar into two components. From Congnito current user I made one callback function and use it in useEffect, and dependencies put the callback function, by default getAuthenticatedUser is null. I add condition where it fetch the data, if getAuthenticatedUser then redirect to signin and signup page. I deployed my app to s3 bucket and this the link. This app runs first time, When I refresh it then got error: 404 Not Found. I really don't know what is the issue and somehow the path react path get disappear. I share my code in code-sandbox.
This is my conditional path
import React from "react";
import SigninLinks from './SigninLinks';
import SignoutLinks from './SignoutLinks';
import useHandlder from '../configHandler/useHandler';
const Nav = () => {
const { getAuthenticatedUser } = useHandlder();
const Links = getAuthenticatedUser() === null ? <SignoutLinks /> : <SigninLinks />
return (
<nav className="nav-wrapper grey darken-3">
<div className="container">
<h2 className="brand-logo">Logo</h2>
{
Links
}
</div>
</nav>
);
};
export default Nav;
This is my handler functions
import React, { useCallback, useEffect } from 'react';
import { CognitoUserPool } from 'amazon-cognito-identity-js';
const Pool_Data = {
UserPoolId: "us-east-1_9gLKIVCjP",
ClientId: "629n5o7ahjrpv6oau9reo669gv"
};
export default function useHandler() {
const userPool = new CognitoUserPool(Pool_Data)
const getAuthenticatedUser = useCallback(() => {
return userPool.getCurrentUser();
},
[],
);
useEffect(() => {
getAuthenticatedUser()
}, [getAuthenticatedUser])
const signOut = () => {
return userPool.getCurrentUser()?.signOut()
}
return {
userPool,
getAuthenticatedUser,
signOut
}
};
It's paths issue. You get 404 on /path not in root /. Check S3 settings for hosting static sites. On S3 make sure static website hosting is enabled:
You react app loads on /index.html JavaScript then redirects and takes over the path. You need S3 to resolve path to index.html, then it will work.

how to Redirect to another page in next.js based on css media query?

im brand new to Next.js and i have the following situation. i want to redirect the user to the route /messages if he type route /messages/123 based on css media query so if he is mobile we will not redirect and if he in browser then redirect .
i have tried the following code
import React, { useLayoutEffect } from 'react';
import { useRouter, push } from 'next/router';
import useMediaQuery from '#material-ui/core/useMediaQuery';
import Layout from '../components/Customer/Layout/Layout';
import Chat from '../components/Customer/Chat/Chat';
const Messages = () => {
const { pathname, push } = useRouter();
const matches = useMediaQuery('(min-width:1024px)');
useLayoutEffect(() => {
console.log('I am about to render!');
if (matches && pathname === '/messages') {
console.log('match!');
push('/');
}
}, [matches, pathname, push]);
return (
<Layout currentURL={pathname}>
<Chat />
</Layout>
);
};
export default Messages;
the problem is the component render twice before redirect
But You should probably be using useEffect since you are not trying to do any DOM manipulations or calculations.
useLayoutEffect: If you need to mutate the DOM and/or DO need to perform measurements
useEffect: If you don't need to interact with the DOM at all or your DOM changes are unobservable (seriously, most of the time you should use this).
You should see immediate action.
Edit:
You can use Next JS getInitialProps to check the request headers and determine if the request if from mobile or desktop then redirect from there.
getInitialProps({ res, req }) {
if (res) {
// Test req.headers['user-agent'] to see if its mobile or not then redirect accordingly
res.writeHead(302, {
Location: '/message'
})
res.end()
}
return {}
}

How to test API calls in react using jest and enzyme?

I have a React container in which I am making the API call and would like to be able to test this using jest and enzyme but unsure how to.
This is my code:
import React from "react";
import Search from "../../components/Search";
import { API_KEY } from "../../../config";
class SearchContainer extends React.Component {
state = {
articles: []
};
performSearch = event => {
fetch(
`http://content.guardianapis.com/search?q=${event}&api-key=${API_KEY}`
)
.then(response => response.json())
.then(data => this.setState({ articles: data.response.results }));
};
render() {
return (
<Search
performSearch={this.performSearch}
articles={this.state.articles}
/>
);
}
}
export default SearchContainer;
That's a great thing about unit testing, they force you to write better code. So to properly test this component, you should do the following:
Extract performSearch from The component into a separate file e.g. api.js
Mock performSearch in your api.js file (jest: mock a module)
Now you can test that the fetch function was called.
Note that with this code organization you could separately test your API calls and your SearchContainer without calling your API service.
I would approach this by extracting performSearch out into a module that wraps fetch. See this great article on testing things you don't own.
After that, you may not need SearchContainer any more if you store the articles state within the Search component. Since you're already using dependency injection with the performSearch property, you can pass in a mock object in place of it and use jest.fn() to ensure it is called.
For example:
const fakePerformSearch = jest.fn();
const component = Shallow(<Search performSearch={fakePerformSearch}/>);
expect(fakePerformSearch).toHaveBeenCalled();
And then test your new fetch wrapper as you would any JavaScript.
A lot of the other answers recommend using Jest's import mocker or a mock function, however, this tests implementation over behavior.
It's better to stub the environment instead of the tools. Let's write a test using an HTTP interceptor like nock. The beauty of this is you can migrate to different data fetching tools or make changes the fetch behavior and get feedback from your tests.
// src/SearchContainer/SearchContainer.test.js
import React from "react";
import nock from "nock";
import {mount} from "enzyme";
import Search from "../../components/Search";
import { API_KEY } from "../../../config";
describe('<SearchContainer />', async () => {
it('searches for articles', () => {
const scope = nock('http://content.guardianapis.com')
.get('/search')
.query({'api-keys': API_KEY, {q: 'some article'}})
.reply(200, {
results: [...]
})
const wrapper = mount(<SearchContainer />);
const searchInput = wrapper.find('[data-test-id="search-input"]');
await searchInput.simulate('change', { target: { value: 'some article' } });
const articles = wrapper.find('[data-test-id="articles"]');
expect(articles.length > 0).toBe(true);
expect(scope.isDone()).toBe(true);
});
});
For a deeper dive on testing API calls, I wrote a blog post Testing Components that make API calls.

React component rendered twice using server side rendering

I have an app where I configured server side rendering. Everything is working nice and my component is rendered on the server. The problem is that I get my component rendered twice on the screen. One comes from <div id="content"><%- content %></div>that I am using for server rendering and one comes from <script src="http://localhost:3001/bundle.js"></script>. I use webpack to make two bundles for my server and client. Why is this happening and how can I fix this?
views/index.ejs
<body>
<div id="app"></div>
<div id="content"><%- content %></div>
<script src="http://localhost:3001/bundle.js"></script>
</body>
index.js
app.use(Express.static(path.join(__dirname, '../', 'dist')))
app.use(serverRenderer)
app.get('*', (req: Object, res: Object) => {
res.render('index', {content: req.body})
})
serverRender
import React from 'react'
import ReactDOM from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from '../client/routes.js'
async function render (component) {
const content = ReactDOM.renderToString(component)
return content
}
async function getMatchParams (routes, currentUrl) {
return new Promise((resolve, reject) => {
match({routes: routes, location: currentUrl}, (err, redirect, props) => {
if (err) {
return reject(err)
}
return resolve(props)
})
})
}
export default async(req, res, next) => {
const renderProps = await getMatchParams(routes, req.url)
if (renderProps) {
const component = (
<RouterContext {...renderProps} />
)
req.body = await render(component)
next()
}
}
Ok. I have found a problem. I was referring to the bundle and server rendered string with two separate <div>. Inside my app.js I was doing this
render(
<Router history={browserHistory}>
{routes}
</Router>,
document.getElementById('app')
)
Thats why I should have been sending the string to the template like this.
app.use(Express.static(path.join(__dirname, '../', 'dist')))
app.use(serverRenderer)
app.get('*', (req: Object, res: Object) => {
res.render('index', {app: req.body})
})
And finally my views/index.js should look like this
<body>
<div id="app"><%- app %></div>
<script src="http://localhost:3001/bundle.js"></script>
</body>
I also faced that problem and found a solution.
On package.json,
"start": "npm-run-all --parallel dev:*",
It will run webpack and node build/bundle.js.
Then 2 things happened simultaneously, webpack build project, node build/bundle.js
After webpack built project, then node build/bundle.js runs again since bundle.js is changed.
So there was twice calls on both server and client side. I solved this problem very easily.
First run npm run build, then run node build/bunde.js . Then it will run everything once :)

contentful api markdown conversion to HTML

Is there any simple way to convert markdown text from contentful api to render into html code to be display on html page. I have tried using pagedown and some similar techniques , but none seem to work for me .
I'm a customer success manager at Contentful -
You can check out a list of recommended parsers by language on the our FAQ.
Also, feel free to send us messages on Intercom via our UI by clicking the 'Talk to Us' link :)
I know I'm late but here's the solution using handlebars:
var marked = require('marked');
marked.setOptions({
renderer: new marked.Renderer(),
sanitize: true,
smartLists: true,
smartypants: true
});
//Home
router.get('/', (req, res) => {
client.getEntry('<ENTRY_ID>')
.then( (entry)=> {
entry.fields.body = marked(entry.fields.body);
res.render('static/index',
{
entry: entry,
user: req.user
});
}).catch( (err) => {
console.log(err);
})
});
Then in our index.hbs template we can call the markdown variable in this case (entry.fields.body) by using {{{}}} to prevent escaping.
{{{entry.fields.body}}}
Here's how I did it with React:
class NewsDetail extends React.Component {
render() {
const body = marked(this.props.body || "");
return (
<div className="news-detail">
<h2>{this.props.title}</h2>
<div dangerouslySetInnerHTML={ { __html: body } }></div>
</div>
);
}
}
The markdown content is stored in the body attribute of the NewsDetail tag (via a short function that maps contentful data structure to my app structure).
The HTML page has this script tag to pull in the marked function:
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.6/marked.min.js"></script>
I have used ReactMarkdown module: in case you also have a react app: https://github.com/rexxars/react-markdown
Example: npm install --save react-markdown
const React = require('react')
const ReactDOM = require('react-dom')
const ReactMarkdown = require('react-markdown')
const input = '# This is a header\n\nAnd this is a paragraph'
ReactDOM.render(
<ReactMarkdown source={input} />,
document.getElementById('container')
)
I am passing markdown through props and using this module inside of my child component.
I also did the same as margarita in a react app but in the child component and I pulled my markdown from contentful.
I installed react-markdown package
with
npm install react-markdown
import React from "react";
import ReactMarkdown from "react-markdown";
const AboutIntro = (props) => {
return (
<div>
<h2 className="about__intro-title">
{props.aboutTitle}
</h2>
<ReactMarkdown>
{props.aboutCopy}
</ReactMarkdown>
</div>
)
}
export default AboutIntro;
hope this helps

Resources