Rails - React Router not keeping its props.children - ruby-on-rails

I am trying to set up a React Router in Rails for the first time. The links on the router works and changes the hash as expected, but it does not render. I have narrowed down the issue with debugger, and find the following:
MyRoutes (line 22, before React creates the router) has props.children.
App (line 6, when rendering) does not have props.children.
What is going on?
var Route = ReactRouter.Route,
Link = ReactRouter.Link;
var App = React.createClass({
render: function(){
//line 6
return (
<div>
<h1>App</h1>
<ul>
<li><Link to="/todo">Todo</Link></li>
<li><Link to="/comment">Comment</Link></li>
</ul>
{this.props.children}
</div>
)
}
})
$(function(){
var MyRoutes = (
<Route path="/" handler={App}>
<Route path="/todo" name="todo" handler={TodoList} />
<Route path="/comment" name="comment" handler={Comment} />
</Route>
);
//line 22
ReactRouter.run(MyRoutes, function (Handler) {
React.render(<Handler/>, $("#application").get(0));
});
})

I took a look at the react-router-rails repository. Replace this.props.children by <RouteHandler {...this.props}/>. But remember you have to "import" RouteHandler as follows: var RouteHandler = ReactRouter.RouteHandler;. That should solve it.

Related

Uncaught Error: You should not use <Route> or withRouter() outside a <Router>

I'm using the react-rails gem (v 2.3) with react-router-dom (v 4.2.2) and mounting my root component App on my root Rails route.
This is how it's being mounted:
<%= react_component("App") %>
And this is the component itself:
const Router = ReactRouterDOM.BrowserRouter,
Route = ReactRouterDOM.Route;
class App extends React.Component{
render() {
return (
<Router>
<Route path="/" component={Index}>
</Route>
</Router>
);
}
}
I keep getting the following error:
Uncaught Error: You should not use <Route> or withRouter() outside a <Router>
Am I not using the Route component within a Router component above?
Is there an error perhaps with the Index component?
Thanks in advance for any help :)
Maybe it's not correct error ..
Try override your App like that
class App extends React.Component{
render() {
return (
<Router>
<Route path="/" component={Index} />
</Router>
);
}
}
Or wrapp you Route in div
class App extends React.Component{
render() {
return (
<Router>
<div>
<Route path="/" component={Index} />
</div>
</Router>
);
}
}
Here is docs
I ran into a similar error message, but in a different context. I'm posting my fix here just in case someone else encounters the same situation I did.
My issue was that I'd had an App.js with two elements added, but I had forgotten to update my index.js file. It had previously been:
ReactDOM.render(<App />, document.getElementById('root'))
which I updated to:
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'))
which also had to add the following import statement:
import { BrowserRouter } from 'react-router-dom'

create-react-app: Using the network for certain service worker navigations?

I am using the create-react-app template. I have the service worker installed at the root directory. Therefore, any clicked link(a tag not Link tag) to my domain will call the service worker. The service worker will then return the resource from the cache if available otherwise it will make a call to the network. This is correct right?
Assuming this is correct, I now have a slightly different scenario, I want to add a /documentation route to my app. This route will server the index.html file create by jsdocs using node js(see route I have in node js code).
The problem is that it appear the service worker takes this route, never calls the node js backend, and sends this to my react router. The react router, since it has no /documentation route, just shows the navigation and footer component attached to all / routes.
I have a two question:
1. How do I specify that a certain route should not be handled my the service worker? I think I can use fetch but I am unsure how to implement it correctly
2. Why does the service worker not see that it does not have a /documentation route saved and therefore just call the index.html files from the server?
Node js
const path = require('path');
const bodyParser = require('body-parser');
const fs = require('fs');
const MongoClient = require('mongodb').MongoClient;
const stringToObject = require('mongodb').ObjectID
const mongoStoreFactory = require("connect-mongo");
const compression = require('compression');
const helmet = require('helmet');
var app = express();
app.use(helmet());
//Compress here since we do not want to change the build tools.
//This will use a bit more CPU as it needs to compress each request and response.
app.use(compression())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.set("port", process.env.PORT || 3001);
console.log("Port: " + process.env.PORT + " mode: " + process.env.NODE_ENV);
app.use(express.static("client/build"));
app.use(express.static("client/out"));
var accountsCollection = null;
/**
We don't need to specify index since this will be served automatically with static files.
*/
MongoClient.connect("mongodb://db:27017", function(err, db) {
if(!err) {
console.log("We are connected");
db.collection('accounts', function(err, collection) {
if(!err){
console.log("Accessed account collection");
accountsCollection = collection
}
});
//app.get('/', function (req, res) {
// console.log("Get index!");
// res.sendFile(path.join(__dirname+'/client/build/index.html'));
//});
app.get('/about', function (req, res) {
console.log("Get about!");
res.sendFile(path.join(__dirname+'/client/build/about.html'));
});
app.get('/services', function (req, res) {
console.log("Get services!");
res.sendFile(path.join(__dirname+'/client/build/services.html'));
});
app.get('/work', function (req, res) {
res.sendFile(path.join(__dirname+'/client/build/work.html'));
});
app.get('/skills', function (req, res) {
res.sendFile(path.join(__dirname+'/client/build/skills.html'));
});
app.get('/documentation', function (req, res) {
console.log("Get docs!");
res.sendFile(path.join(__dirname+'/client/out/index.html'));
});
app.listen(app.get("port"), () => {});
}
});
Call to documentation route in react component skills
<article className={"skills__skill"}>
<a href={"/documentation"}> See Documentation </a>
</article>
My complex router
<div className={"app"}>
<Router>
<div>
<Route render={({location, history, match}) => {
return (
<RouteTransition
pathname={location.pathname}
atEnter={{ opacity: 0 }}
atLeave={{ opacity: 0 }}
atActive={{ opacity: 1 }}
>
<Switch key={location.key} location={location}>
<Route exact path={"/"} render={()=>{
handlePageChange(history);
return <Start/>
}
}/>
<Route path={"/"}
render={({location, history, match})=>{
return (
<RouteTransition
pathname={location.pathname}
atEnter={{ opacity: 0}}
atLeave={{ opacity: 0}}
atActive={{ opacity: 1}}
>
<FadeBackground >
<Clouds>
<Switch key={location.key} location={location}>
<Route exact path={"/services"}
render={(props)=>{
handleAuthentication(props);
handlePageChange(history);
return <Home />
}
}/>
<Route exact path={"/about"} component={Profile}/>
<Route exact path={"/work"}
render={()=>{
handlePageChange(history);
return <Work />
}}
/>
<Route exact path={"/skills"}
render={()=>{
handlePageChange(history);
return (
<Skills />
)
}}
/>
</Switch>
</Clouds>
</FadeBackground>
</RouteTransition>
)
}}/>
</Switch>
</RouteTransition>
)}
}/>
<Nav
links={[
{name:"Welcome",location:"/"},
{name:"About Me",location:"/about"},
{name:"My Services",location:"/services"},
{name:"My Work",location:"/work"},
{name:"My Skills",location:"/skills"}
]}
/>
<footer className={"footer"}>
</footer>
</div>
</Router>
</div>
Under the hood, create-react-app uses sw-precache, run via sw-precache-webpack-plugin.
In order to customize the behavior of the service worker, like with other customizations in create-react-app, you need to first eject.
Once you have access to the underlying configuration options, the property you want to configure is navigateFallbackWhitelist, inside of webpack.config.prod.js. You can adjust the default configuration to include a different regular expression; any navigations that match one of the RegExps will trigger the service worker to respond, so the idea would be that you can set a RegExp that will match the paths that should be handled via your SPA HTML, and not match documentation/ or anything else that should be handled via your backend.

react-router - no component rendered on hash change

I am using Rails with react gem and react-router.js inside assets. I am using webpack in order to use require
I have my routes inside app.js:
var Router = require('react-router').Router;
var Route = require('react-router').Route;
var Link = require('react-router').Link;
var ReactDOM = require('react-dom');
var browserHistory = require('react-router').browserHistory;
ReactDOM.render((
<Router history={browserHistory}>
<Route path="/" component={Welcome}>
<Route path="hello" component={Hello}/>
<Route path="welcome" component={Welcome}/>
<Route path="about" component={About}/>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.getElementById('root'));
other.es6.jsx:
var Hello = React.createClass({
render: function () {
return (
<div>
<h3>Hello component</h3>
About
</div>
);
}
});
var Welcome = React.createClass({
render: function () {
return (
<div>
<h3>Welcome component</h3>
Hello
</div>
);
}
});
var About = React.createClass({
render: function () {
return (
<div>
<h3>About component</h3>
Welcome
</div>
);
}
});
var NoMatch = React.createClass({
render: function () {
return (
<h3>No Match</h3>
);
}
});
When I visit controller_name/index it renders by default the 'Welcome component' text with link to '#hello' but when clicking, it does not do anything. It's still saying 'welcome component'
What did I do wrong?
You are rendering only welcome content not any child component. I hope this will fix it.
var Welcome = React.createClass({
render: function () {
if(this.props.children){
return this.props.children
}else{
return (
<div>
<h3>Welcome component</h3>
Hello
</div>
);
}
}
});

React/Rails - getInitialState not called

I'm following this intro to React guide as well as the original fb react tutorial, using Rails as a backend. I've split out my files and they all work together correctly up to this point.
Upon trying to set the getInitialState to provide props for my CommentBox, I get errors of Cannot read property 'map' of undefined, meaning React can't find the empty array [] that was allegedly set, and sent to CommentList. How do I ensure getInitialState actually sets the data prop?
var CommentBox = React.createClass({
getInitialState: function(){
return {data: []};
},
render: function(){
return(
<div className="commentBox">
<h1> Comments! </h1>
<CommentList data={this.props.data} />
<CommentForm />
</div>
);
}
});
var ready = function(){
React.render(
<CommentBox />,
document.getElementById('content')
);
};
$(document).ready(ready);
Then entirety of the code is hosted in this repo. Thanks!
EDIT: CommentList code:
var CommentList = React.createClass({
commentNodes: function(){
var nodes = this.props.data.map(function(d){
return(
<Comment author={d.author}>
{d.text}
</Comment>
);
});
return nodes;
},
render: function(){
return(
<div className="commentList">
This is the comment list.
{this.commentNodes()}
</div>
);
}
});
Figured it out. Essentially:
I was trying to pass in a data props, which doesn't exist because it is a state.
Upon rendering the CommentList, it then becomes a prop and it can be accessed from there.
Learnings were had.

React-router: component undefined

I've been running through the react-router tutorial found here and I'm currently puzzled...
React-router doesn't recognise my component.
(I'm using React.js with Rails)
Here's the code:
var DefaultRoute = ReactRouter.DefaultRoute;
var Link = ReactRouter.Link;
var Route = ReactRouter.Route;
var RouteHandler = ReactRouter.RouteHandler;
var App = React.createClass({
getInitialState: function () {
return {
showTags: false,
current_user: this.props.current_user
};
},
_handleToggleTags: function() {
this.setState({
showTags: !this.state.showTags
})
},
render: function () {
return <div>
<Header
onToggleTags={ this._handleToggleTags }
user={this.props.current_user}
/>
<RouteHandler/>
<div id="images">
<ImageBox/>
</div>
</div>;
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="tags" handler={TagsBox}/>
</Route>
);
ReactRouter.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
If I move TagsBox before App it works, though nobody else seems to be doing this. What am I missing?
If it makes a different, the current structure of my components is:
app.js.jsx
Tags
_tags_box.js.jsx
In your position it seems best to run the router once all scripts are loaded.
Try wrapping the run method in this code:
document.addEventListener("DOMContentLoaded", function() {
ReactRouter.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
}

Resources