How can I use React Router in a page with multiple components? - javascript

I have this .js file that creates several divs and renders a few components and assigns them to a div. How can I use react router in this case if it only renders one component? Do I need to change this original file?
HomePage.jsx
import React from 'react';
import 'bootstrap-webpack';
import BigPic from './components/Jumbotron';
import Major from './components/Major';
import Footer from './components/Footer';
import GA from './components/GA';
var gA = require('react-google-analytics');
function googleAnalytics() {
var ga = document.createElement('div');
document.body.appendChild(ga);
React.render(<GA />, ga);
gA('create', 'UA-XXXX-Y', 'auto');
gA('send', 'pageview');
}
function jumbotron() {
//jumbotron
var wrapper = document.createElement('div');
//set jumbotron id and class
wrapper.id = "big";
wrapper.className = "site-wrapper";
//append div
document.body.appendChild(wrapper);
const jumbotron = document.getElementById('big');
React.render(<BigPic />, jumbotron);
}
function features() {
//features
var feature = document.createElement('div');
//set features id
feature.id= "featured-wrapper";
// append div to body
document.body.appendChild(feature);
const major = document.getElementById('featured-wrapper');
React.render(<Major />, major);
}
function footer() {
//footer
var bottom = document.createElement('footer');
//set footer id
bottom.id = 'footer';
bottom.className = "footer-basic-centered";
//append footer to bottom
document.body.appendChild(bottom);
const footer = document.getElementById('footer');
React.render(<Footer />, footer);
}
function homepage() {
jumbotron();
features();
footer();
googleAnalytics();
}
homepage();

Your main app will need to be changed to look something like this:
var routes = (
<Route handler={App} path='/'>
<Route name='major' handler={Major} path='major'>
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
You will then need to update App to include your jumbotron, footer and GA code:
React.createClass({
render: function(){
return (
<div>
<Jumbotron />
<RouteHandler />
<Footer />
<GA />
</div>
);
}
});
The key bit here is RouteHandler as this will render the subroute's component here.

Related

How can i render a React JS component directly & without a Target container

I would like to render a component directly without a Target container, i need to get the content of the component right in the place where i put the generated JS file.
Here is my index.js file, i have removed the "document.getElementById('target-container')" is there a way to render this component without a target container or a way to append a target container without inserting it in the HTML template file.
var React = require('react');
var ReactDOM = require('react-dom');
import axios from 'axios';
import '../node_modules/cleanslate/cleanslate.css';
import './style.scss';
class Widget extends React.Component {
constructor(props){
super(props);
this.state = {}
}
componentDidMount() {
let id = this.props.id;
axios.get(`https://api.com/${id}`)
.then((res) => {
const brand = res.data;
this.setState({
rating: brand.rating,
logo : brand.logo,
name: brand.name,
stars: brand.stars,
url: brand.url
});
});
}
render() {
return (
<div className="cleanslate">
<a href={this.state.url} target="_blank">
<img src="https://img/.svg" className="" alt="" />
<div className="rating-box">
<img src={this.state.logo} className="logo" alt={this.state.name} />
<span className="note">{this.state.rating}/10</span>
<div id="Selector" className={`selected-${this.state.stars}`}></div>
</div>
</a>
</div>
)
}
}
ReactDOM.render(
<Widget id="7182" />
)
Here is an example (https://github.com/seriousben/embeddable-react-widget) of appending the component in another one :
import React from 'react';
import ReactDOM from 'react-dom';
import Widget from '../components/widget';
import '../../vendor/cleanslate.css';
export default class EmbeddableWidget {
static el;
static mount({ parentElement = null, ...props } = {}) {
const component = <Widget {...props} />;
function doRender() {
if (EmbeddableWidget.el) {
throw new Error('EmbeddableWidget is already mounted, unmount first');
}
const el = document.createElement('div');
el.setAttribute('class', 'cleanslate');
if (parentElement) {
document.querySelector(parentElement).appendChild(el);
} else {
document.body.appendChild(el);
}
ReactDOM.render(
component,
el,
);
EmbeddableWidget.el = el;
}
if (document.readyState === 'complete') {
doRender();
} else {
window.addEventListener('load', () => {
doRender();
});
}
}
static unmount() {
if (!EmbeddableWidget.el) {
throw new Error('EmbeddableWidget is not mounted, mount first');
}
ReactDOM.unmountComponentAtNode(EmbeddableWidget.el);
EmbeddableWidget.el.parentNode.removeChild(EmbeddableWidget.el);
EmbeddableWidget.el = null;
}
}
You could generate a container for your app with JS before calling ReactDOM.render (for instance with appendChild as described here) and then call ReactDOM.render passing just generated element as container.
UPD:
Even though it feels strange, you actually can get the script tag of your bundle before ReactDOM.render is called.
Knowing this, you could do something like:
// Create a container dynamically
const appContainer = document.createElement('div');
// Get all <script>s on the page and put them into an array.
const scriptTags = Array.from(document.scripts);
// Filter scripts to find the one we need.
const targetScript = scriptTags.filter(
scriptTag => scriptTag.src === 'https://example.com/bundle.js'
);
// Uh oh, we got either too many or too few,
// it might be bad, better stop right here.
if (targetScript.length !== 1) {
return;
}
// Inserts app container before the script.
document.body.insertBefore(appContainer, targetScript[0]);
// Renders things inside the container
ReactDOM.render(
<MyComponent />,
appContainer
);

React Router - Mounting component inside a nested component

I am having a hard time figuring out how to mount components inside a nested components with react router v1.0. I have an App component that loads a Layout component. The Layout component then loads two components, Menu and Content. I want to load different components inside the Content component based on the route.
Below is my sample code.
var App = React.createClass({
render : function(){
return <div><Layout/></div>
}
});
var Layout = React.createClass({
render : function(){
return(
<div>
<Menu/>
<Content/>
</div>
)
}
});
var Content = React.createClass({
render : function(){
return <div>This is where i want to mount my components</div>
}
});
var List = React.createClass({
render : function(){
return <div>some list goes here</div>
}
});
var Graph = React.createClass({
render : function(){
return <div>some graph goes here</div>
}
});
<Router>
<Route path='/' component={App}>
<Route path='/list' component={List}/>
<Route path='/graph' component={Graph}/>
</Route>
</Router>
Any help/pointers will be highly appreciated.
Thanks
It's all the same as basic React components. When you nest them, they're available on this.props.children. So you would end up with something like this:
var App = React.createClass({
render : function(){
return <div><Layout>{this.props.children}</Layout></div>
}
});
var Layout = React.createClass({
render : function(){
return(
<div>
<Menu/>
<Content>{this.props.children}</Content>
</div>
)
}
});
var Content = React.createClass({
render : function(){
return <div>{this.props.children}</div>
}
});

React component this.props is always empty

I've followed a couple of examples in an attempt to get access to a parameter from a Route in the React component that handles it. However the result of console.log on this.props from inside the render or componentDidMount is always {} when I'd expect it to contain the gameId from the gamesView route.
client.js which starts the Router:
// HTML5 History API fix for local
if (config.environment === 'dev') {
var router = Router.create({ routes: routes });
} else {
var router = Router.create({ routes: routes, location: Router.HistoryLocation });
}
router.run(function(Handler) {
React.render(
<Handler />,
document.getElementById('app')
);
});
routes.js with some routes removed for simplicity:
var routes = (
<Route name='home' path='/' handler={app}>
<DefaultRoute handler={home} location="/" />
<Route name='gamesView' path='/games/:gameId' handler={gamesView} />
</Route>
);
module.exports = routes;
...and app.js which wraps the other routes, I've tried it both with and without {...this.props} in the RouteHandler. If I console.log(this.props) from inside the render function here is also returns {}:
var App = React.createClass({
render: function() {
return (
<div className='container'>
<div className='row'>
<RouteHandler {...this.props} />
</div>
</div>
);
}
});
module.exports = App;
Finally the gamesView React component that I expect to see the props object. Here this.props is also {} and the following results in the error TypeError: $__0 is undefined var $__0= this.props.params,gameId=$__0.gameId;:
var GamesView = React.createClass({
render: function() {
var { gameId } = this.props.params;
return (
<div>
<h1>Game Name</h1>
<p>{gameId}</p>
</div>
);
}
});
module.exports = GamesView;
Anybody have any ideas?
You won't see those params until you are at the component defined in your router. App won't know anything about them. If you put the console.log(this.props.params) in your gamesView component, however, you should see them.
After discussing on the React Router (RR) Github it turned out this was due to me using an older version of RR (v0.11.6).
Looking at the example in the docs for that release it showed that I needed to use a Router.State mixin and then get the expected param via var gameId = this.getParams().gameId;.
Without upgrading RR the working version of my original example for GamesView becomes:
var React = require('react');
var Router = require('react-router');
var { Route, RouteHandler, Link } = Router;
var GamesView = React.createClass({
mixins: [ Router.State ],
render: function() {
var gameId = this.getParams().gameId;
return (
<div>
<h1>Game Name</h1>
<p>{gameId}</p>
</div>
);
}
});
module.exports = GamesView;

Dynamically load a stylesheet with React

I'm building a CMS system for managing marketing landing pages. On the "Edit Landing Page" view, I want to be able to load the associated stylesheet for whichever landing page the user is editing. How could I do something like this with React?
My app is fully React, isomorphic, running on Koa. My basic component heirarchy for the page in question looks something like this:
App.jsx (has `<head>` tag)
└── Layout.jsx (dictates page structure, sidebars, etc.)
└── EditLandingPage.jsx (shows the landing page in edit mode)
Data for the landing page (including the path of the stylesheet to load) is fetched asynchronously in EditLandingPage in ComponentDidMount.
Let me know if you need any additional info. Would love to get this figured out!
Bonus: I'd also like to unload the stylesheet when navigating away from the page, which I assume I can do the reverse of whatever answer comes my way in ComponentWillUnmount, right?
Just update stylesheet's path that you want to be dynamically loaded by using react's state.
import * as React from 'react';
export default class MainPage extends React.Component{
constructor(props){
super(props);
this.state = {stylePath: 'style1.css'};
}
handleButtonClick(){
this.setState({stylePath: 'style2.css'});
}
render(){
return (
<div>
<link rel="stylesheet" type="text/css" href={this.state.stylePath} />
<button type="button" onClick={this.handleButtonClick.bind(this)}>Click to update stylesheet</button>
</div>
)
}
};
Also, I have implemented it as react component. You can install via npm install react-dynamic-style-loader.
Check my github repository to examine: https://github.com/burakhanalkan/react-dynamic-style-loader
I think that Burakhan answer is correct but it is weird to load <Link href = "" /> inside the body tag. That's why I think it should be modified to the following [ I use React hooks]:
import * as React from 'react';
export default MainPage = (props) => {
const [ stylePath, setStylePath ] = useState("style1.css");
const handleButtonClick = () => {
setStylePath({stylePath: 'style2.css'});
}
useEffect(() => {
var head = document.head;
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = stylePath;
head.appendChild(link);
return () => { head.removeChild(link); }
}, [stylePath]);
return (
<div>
<button type="button" onClick={handleButtonClick}>
Click to update stylesheet
</button>
</div>
);
};
This is prime mixin teritority. First we'll define a helper to manage style sheets.
We need a function that loads a style sheet, and returns a promise for its success. Style sheets are actually pretty insane to detect load on...
function loadStyleSheet(url){
var sheet = document.createElement('link');
sheet.rel = 'stylesheet';
sheet.href = url;
sheet.type = 'text/css';
document.head.appendChild(sheet);
var _timer;
// TODO: handle failure
return new Promise(function(resolve){
sheet.onload = resolve;
sheet.addEventListener('load', resolve);
sheet.onreadystatechange = function(){
if (sheet.readyState === 'loaded' || sheet.readyState === 'complete') {
resolve();
}
};
_timer = setInterval(function(){
try {
for (var i=0; i<document.styleSheets.length; i++) {
if (document.styleSheets[i].href === sheet.href) resolve();
} catch(e) { /* the stylesheet wasn't loaded */ }
}
}, 250);
})
.then(function(){ clearInterval(_timer); return link; });
}
Well $#!#... I was expecting to just stick an onload on it, but nope. This is untested, so please update it if there are any bugs – it's compiled from several blog articles.
The rest is fairly straight forward:
allow loading a stylesheet
update state when it's available (to prevent FOUC)
unload any loaded stylesheets when the component unmounts
handle all the async goodness
var mixin = {
componentWillMount: function(){
this._stylesheetPromises = [];
},
loadStyleSheet: function(name, url){
this._stylesheetPromises.push(loadStyleSheet(url))
.then(function(link){
var update = {};
update[name] = true;
this.setState(update);
}.bind(this));
},
componentWillUnmount: function(){
this._stylesheetPromises.forEach(function(p){
// we use the promises because unmount before the download finishes is possible
p.then(function(link){
// guard against it being otherwise removed
if (link.parentNode) link.parentNode.removeChild(link);
});
});
}
};
Again, untested, please update this if there are any issues.
Now we have the component.
React.createClass({
getInitialState: function(){
return {foo: false};
},
componentDidMount: function(){
this.loadStyleSheet('foo', '/css/views/foo.css');
},
render: function(){
if (!this.state.foo) {
return <div />
}
// return conent that depends on styles
}
});
The only remaining todo is checking if the style sheet already exists before trying to load it. Hopefully this at least gets you on the right path.
I use react-helmet, in render function....
{inject ?
<Helmet>
<link rel="stylesheet" href="css/style.css" />
</Helmet> : null}
This is how I add style dynamically:
import React, { Component } from "react";
class MyComponent extends Component {
componentDidMount() {
const cssUrl = "/public/assets/css/style.css";
this.addStyle(cssUrl);
}
addStyle = url => {
const style = document.createElement("link");
style.href = url;
style.rel = "stylesheet";
style.async = true;
document.head.appendChild(style);
};
render() {
return <div> textInComponent </div>;
}
}
export default MyComponent;
On my approach i use this:
const TenantSelector = ({ children }) => {
// imagine its value from a json config
const config = {
custom_style: 'css/tenant.css'
}
require(`./assets/${config.custom_style}`)
return (
<>
<React.Suspense fallback={<></>}>
</React.Suspense>
{children}
</>
)
}
ReactDOM.render(
<TenantSelector>
<YourApp>
</TenantSelector>,
document.getElementById("root")
)
Instead of creating elements for stylesheet, you can also try importing your css based on some condition. ECMAScript provides a proposal that enables dynamic module imports, that works as follows:
if (condition) {
import('your css path here').then((condition) => {});
}

Nested React components and routing

Outer component (page layout):
var Layout = React.createClass({
render() {
return (
<div className="container">
<header>
<h1>{this.props.title}</h1>
</header>
<section>
{this.props.children}
</section>
</div>
);
}
});
Component one (page 1):
var PageOne = React.createClass({
render() {
return (
<Layout title="Component One">
<p>This is component one.</p>
</Layout>
);
}
});
Component two (page 2):
var PageTwo = React.createClass({
render() {
return (
<Layout title="Component Two">
<p>This is component two.</p>
</Layout>
);
}
});
Now, if we render these components to document.body dynamically, based on which page user is located (using HTML5 History API), how would that impact performance (as opposed to switching just Page components without re-rendering the outer (layout) component)?
var React = require('react');
var {Router} = require('director');
var render = (page) => { React.renderComponent(page(), document.body); };
var routes = {
'/page-one': () => { render(require('./pages/PageOne')); },
'/page-two': () => { render(require('./pages/PageTwo')); }
};
Router(routes).configure({html5history: true}).init();
P.S.: The HTML markup in these sample components is intentionally simplified. On StackOverflow.com example, there could be page components such as Questions, Tags, Users, Badges, AskQuestion, all contained inside a layout component which itself contains header, footer, navigation, sidebar.
You can't do this without rerender because this two page components are not similar. You can avoid lot of mutations if you create one component with some calculated parameters.
Component Page:
var Page = React.createClass({
render() {
return (
var _component_title = "Component" + this.props.componentName
<Layout title={_component_title}>
<p>This is {_component_title}</p>
</Layout>
);
}
});
Router:
var Page = require('./pages/Page')
var render = (page) => { React.renderComponent(Page({componentName: page}), document.body); };
var routes = {
'/page-one': () => { render('One'); },
'/page-two': () => { render('Two'); }
};
React will rerender only nodes with component name.
But you no need wory about rerender, React do this really fast.

Categories