Routing in ReactJS with props - reactjs

I have one main component where I have all state.
And here I passed this states to two different components.
The problem is - I need to open this two components in two different links (<TimeTracker />, <TimeCalendar />).
Render them separately.
How can I made it with React-router? Is it possible?
Bellow is my code for main component
export default class App extends React.Component {
constructor () {
super();
this.initStorage();
this.state = {
startTime: this.getStoreItem('startTime') || 0,
currentTask: this.getStoreItem('currentTask') || '',
results: this.getStoreItem('results') || [],
calendarResults: this.getStoreItem('calendarResults') || []
};
}
/**
create an object in localStorage for timer data if it is not present
*/
initStorage () {
let data = localStorage.getItem('timeData');
if (!data) {
localStorage.setItem('timeData', JSON.stringify({}));
}
}
/**
* get item value from storage
* #param key - item name
*/
getStoreItem = (key) => {
const data = JSON.parse(localStorage.getItem('timeData'));
return data[key];
}
/**
* change item value in storage
* * #param key - item name
* #param value - new value for item
*/
setStoreItem = (key, value) => {
const data = JSON.parse(localStorage.getItem('timeData'));
data[key] = value;
localStorage.setItem('timeData', JSON.stringify(data));
this.setState({
[key]: value
});
}
render () {
const { startTime, currentTask, results, calendarResults } = this.state;
return (
<MuiThemeProvider>
<div>
<TimeTracker
results={results}
setStoreItem={this.setStoreItem}
startTime={startTime}
currentTask={currentTask} />
<TimeCalendar calendarResults={calendarResults} />
</div>
</MuiThemeProvider>
);
}
}
I am new in Routing and did not find some similar examples.
Please help to understand how to do it.
I can make routing for them, but if component do not have props.
But in my example I'm bewildered
Thank you in advance!

Here's an extract from my reactjs code that should help you out :
Router.jsx:
import React from 'react';
import { Router, Route } from 'react-router';
import createBrowserHistory from 'history/createBrowserHistory';
// route components
import { HomePage } from '../Pages/HomePage.jsx';
import { LoginPage } from '../Pages/LoginPage.jsx';
const browserHistory = createBrowserHistory();
export const renderRoutes = () => (
<Router history={browserHistory}>
<div>
<Route exact path="/" component={HomePage}/>
<Route exact path="/login" component={LoginPage}/>
</div>
</Router>
);
In this file, you start off by defining the components that will be rendered following the url adress you will visit on your page. Here is one of these two components:
HomePage.jsx :
import React, { Component } from 'react'
import { Menu, Segment } from 'semantic-ui-react'
import { AppBarEX } from '../components/Appbar.jsx'
export class HomePage extends Component {
render() {
return (
<div>
<AppBarEX />
</div>
)
}
}
HomePage is defined as the landing page in React Router thanks to the "/" path. So when you land on the website you will automatically be directed to the HomePage. In the <AppBarEX/> I use this:
import { Link } from 'react-router-dom'
<Link to = "/login">
The element allows you to define when and how you want to link to other pages, this is probably what you were looking for. In this situation the Link will send you to the login page. To place elements inside your link, you can wrap them within the Link: <Link> your element </Link>
Finally, the element you want to render in your main.jsx goes as follows :
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { renderRoutes } from './Router.jsx';
Meteor.startup(() => {
render(renderRoutes(), document.getElementById('main_body'));
});
This will allow you to render the renderRoutes defined in the router.jsx. You can find out more here:
https://github.com/reactjs/react-router-tutorial/tree/master/lessons/02-rendering-a-route
Hope this helped you out!
D.

Related

How can i get a parameter value in React to use it in the url of an api fetch? [duplicate]

I'm trying to load a details view based on a react-router-dom route that should grab the URL parameter (id) and use that to further populate the component.
My route looks like /task/:id and my component loads fine, until I try to grab the :id from the URL like so:
import React from "react";
import { useParams } from "react-router-dom";
class TaskDetail extends React.Component {
componentDidMount() {
let { id } = useParams();
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default TaskDetail;
This triggers the following error and I'm unsure where to correctly implement useParams().
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
The docs only show examples based on functional components, not class based.
Version <= 5:
You can use withRouter to accomplish this. Simply wrap your exported classed component inside of withRouter and then you can use this.props.match.params.id to get the parameters instead of using useParams(). You can also get any location, match, or history info by using withRouter. They are all passed in under this.props
Using your example it would look like this:
import React from "react";
import { withRouter } from "react-router";
class TaskDetail extends React.Component {
componentDidMount() {
const id = this.props.match.params.id;
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default withRouter(TaskDetail);
Simple as that!
import React, { Component } from "react";
import { useParams } from "react-router-dom";
function withParams(Component) {
return props => <Component {...props} params={useParams()} />;
}
class TaskDetail extends React.Component {
componentDidMount() {
let { id } = this.props.params;
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default withParams(TaskDetail);
Since hooks wont work with class based components you can wrap it in a function and pass the properties along:
class TaskDetail extends React.Component {
componentDidMount() {
const { id } = this.props.params;
// ...
}
}
export default (props) => (
<TaskDetail
{...props}
params={useParams()}
/>
);
But, like #michael-mayo said, I expect this is what withRouter is already performing.
Params get passed down through props on the match object.
props.match.params.yourParams
source: https://redux.js.org/advanced/usage-with-react-router
Here is an example from the docs destructing the props in the arguments.
const App = ({ match: { params } }) => {
return (
<div>
<AddTodo />
<VisibleTodoList filter={params.filter || 'SHOW_ALL'} />
<Footer />
</div>
)
}
You can not call a hook such as "useParams()" from a React.Component.
Easiest way if you want to use hooks and have an existing react.component is to create a function then call the React.Component from that function and pass the parameter.
import React from 'react';
import useParams from "react-router-dom";
import TaskDetail from './TaskDetail';
function GetId() {
const { id } = useParams();
console.log(id);
return (
<div>
<TaskDetail taskId={id} />
</div>
);
}
export default GetId;
Your switch route will still be something like
<Switch>
<Route path="/task/:id" component={GetId} />
</Switch>
then you will be able to get the id from from the props in your react component
this.props.taskId
In react-router-dom-v6 you can easily use useParams() in functional components but when it gets to the class component you have to create HOC (higher-order component) because hooks don't support class components:
import { useNavigate, useParams } from "react-router-dom";
export const withRouter = (WrappedComponent) => (props) => {
const params = useParams();
const navigate = useNavigate();
return <WrappedComponent {...props} params={params} navigate={navigate} />;
};
Then export your component from your HOC and give your component as a parameter. like below:
export default withRouter(YourComponentName);
After that you can easily access the url id with this.props.params.id and you can navigate to other components with this.props.navigate("/YourPath")
React Route v5
Query params can be read and processed as JSON using withRouter and queryString as follow:
import React from "react";
import { withRouter } from "react-router";
import queryString from 'query-string';
class MyComponent extends React.Component {
componentDidMount() {
const params = queryString.parse(this.props.location.search);
console.log('Do something with it', params);
}
render() {
return <div>Hi!</div>;
}
}
export default withRouter(MyComponent);
SmujMaiku is rigth!!! His answer works perfectly. This is how work today with react-router v6
enter code here
import React ,{Component} from 'react'
import { useParams } from "react-router-dom";
import PokeDescription from '../components/PokeDescription'
class PokeInfoConteiner extends Component{
render(){
let urlPokemon= "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/"
const {idPokemon} = this.props.params
console.log(idPokemon)
return(
<div>
<PokeDescription pokeImage={`${urlPokemon}${idPokemon}.png?raw=true`}/>
<p>{}</p>
</div>
)
}
}
export default (props) => (
<PokeInfoConteiner
{...props}
params={useParams()}
/>)
in React Router V6 :
import React, {Component} from 'react';
import {useParams} from 'react-router-dom';
/* This is a higher order component that
* inject a special prop to our component.
*/
function withRouter(Component) {
function ComponentWithRouter(props) {
let params = useParams()
return <Component {...props} params={params} />
}
return ComponentWithRouter
}
class TaskDetail extends React.Component {
state={
id : ""
}
componentDidMount() {
this.setState({
id : this.props.params.id
})
}
static getDerivedStateFromProps(nextProps) {
return {
id : nextProps.params.id
}
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
const HOCTaskDetail = withRouter(TaskDetail);
export default HOCTaskDetail;
React Route v6
My friends, I tried to use in class but I failed to find any doc about it. So after many hours of searching and trying hard this is (in function). Now (i.e when I'm writing this post) there is only limited resource about v6. But there are many for <v6.
Here I'm using useState,useEffect,useParams,axios.
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
const Post = () => {
let { post_id } = useParams();
const [posts, setPosts] = useState({ post: null, countSecrets: 0, ui: '' });
useEffect(() => {
if (posts.countSecrets === 0) {
const doAxe = (a) => {
axios.get('https://jsonplaceholder.typicode.com/posts/' + post_id)
.then((res) => {
setPosts(s => ({ ...s, value: res.data }));
doUI(res.data)
// console.log(res.data)
});
}
setPosts(s => ({ ...s, countSecrets: s.countSecrets + 1 }));
doAxe()
}
}, [posts, post_id]);
let doUI = (x) => {
// console.log('x' + x.title)
const finalPost = (x !== null) ? (
<div className="post">
<h4 className="center">{x.title}</h4>
<p>{x.body}</p>
</div>
) : (
<div className="center">Loading posts...</div>
);
setPosts(s => ({ ...s, ui: finalPost }));
}
return (
<div className="container">
{posts.ui}
</div>
);
}
export default Post;
NOTE:
I faced useEffect looping. I prevented it with a key.
HOPE: This may help someone!
Reference:
using useParams
state inside function
preventing loop from useEffect
In react-router-dom v6, there is no hook such as withRouter therefore my advice to you is to convert your class-based component to a functional component to use useParams hook in your component otherwise you can create a higher-order component to pass your class-based component.
as you know the useParams() is a hook for react-router-dom.
you can not use this inside the componentDidMount() or useEffect() because both of them are method that called during the Mounting phase of the React Life-cycle i.e after the component is rendered.
you have a solution:
create or define another function outside the componentDidMount() to define useParams then call it inside the componentDidMount.
know every thing will be ok.
This is my working example. :)
import React, { Component } from "react";
import { useParams } from "react-router-dom";
function withParams(Component) {
return (props) => <Component {...props} params={useParams()} />;
}
class ProductDetails extends Component {
handleSave = () => {
// Navigate to /products
};
render() {
return (
<div>
<h1>Product Details - {this.props.params.id}</h1>
<button onClick={this.handleSave}>Save</button>
</div>
);
}
}
export default withParams(ProductDetails);
Hooks only work on functional components,
you have to make that ocmponent a functional component
Fixed by creating a wrapping function
I needed to pass params to my SaxjaxApp.js from index.js using react-router-dom v6.
In v6 Switch has been changed to Routes
I got the useParams working with a class component by following Mohamed MAZEK's idea in post 20 using a wrapping function.
I needed to access the sessionId part of the url when it was available.
ie in localhost:3000/shared/123XYZId
I needed the 123XYZId part.
make note of this line : <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> in the index.js below.
:sessionId denotes that useParams has a property called sessionId, that can be accessed by:
const {sessionId} = useParams() from a functional component.
In my index.js file I did this:
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import "./styles/style.scss";
import SaxjaxAppWrapper from "SaxjaxAppWrapper";
import SaxjaxApp from "./SaxjaxApp";
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
//INFO: to learn about react-roue-dom v6 https://reactrouter.com/en/v6.3.0/upgrading/v5
root.render(
// <React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} />
<Route path="/" element={<SaxjaxApp />} />
</Routes>
</BrowserRouter>
// </React.StrictMode>
);
This line <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> calls my wrapping function, whereas the default path / just calls the class component.
I had to create a separate file to hold the wrapping function I don't know why:
import React from "react";
import { useParams } from "react-router-dom";
import SaxjaxApp from "SaxjaxApp";
function SaxjaxAppWrapper() {
//I use the params here and store them to pass as props
let { sessionId } = useParams();
return (
//I pass the sessionId from the url params as a prop to my SaxjaxApp class component here
<SaxjaxApp sessionId={sessionId} />
);
}
export default SaxjaxAppWrapper;
My class component:
import React, { Component } from "react";
import "./styles/style.scss";
class SaxjaxApp extends Component {
state = {
octave: 4,
};
constructor(props) {
super(props);
//... initialise stuff
}
//... a lot of methods
render() {
//Access the param here
const { sessionId } = this.props;
<>
<div>
keybordId={sessionId ? sessionId : "no id was passed"}
</div>
</>
);
}
}
export default SaxjaxApp;

react-router-dom useParams() inside class component

I'm trying to load a details view based on a react-router-dom route that should grab the URL parameter (id) and use that to further populate the component.
My route looks like /task/:id and my component loads fine, until I try to grab the :id from the URL like so:
import React from "react";
import { useParams } from "react-router-dom";
class TaskDetail extends React.Component {
componentDidMount() {
let { id } = useParams();
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default TaskDetail;
This triggers the following error and I'm unsure where to correctly implement useParams().
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
The docs only show examples based on functional components, not class based.
Version <= 5:
You can use withRouter to accomplish this. Simply wrap your exported classed component inside of withRouter and then you can use this.props.match.params.id to get the parameters instead of using useParams(). You can also get any location, match, or history info by using withRouter. They are all passed in under this.props
Using your example it would look like this:
import React from "react";
import { withRouter } from "react-router";
class TaskDetail extends React.Component {
componentDidMount() {
const id = this.props.match.params.id;
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default withRouter(TaskDetail);
Simple as that!
import React, { Component } from "react";
import { useParams } from "react-router-dom";
function withParams(Component) {
return props => <Component {...props} params={useParams()} />;
}
class TaskDetail extends React.Component {
componentDidMount() {
let { id } = this.props.params;
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default withParams(TaskDetail);
Since hooks wont work with class based components you can wrap it in a function and pass the properties along:
class TaskDetail extends React.Component {
componentDidMount() {
const { id } = this.props.params;
// ...
}
}
export default (props) => (
<TaskDetail
{...props}
params={useParams()}
/>
);
But, like #michael-mayo said, I expect this is what withRouter is already performing.
Params get passed down through props on the match object.
props.match.params.yourParams
source: https://redux.js.org/advanced/usage-with-react-router
Here is an example from the docs destructing the props in the arguments.
const App = ({ match: { params } }) => {
return (
<div>
<AddTodo />
<VisibleTodoList filter={params.filter || 'SHOW_ALL'} />
<Footer />
</div>
)
}
You can not call a hook such as "useParams()" from a React.Component.
Easiest way if you want to use hooks and have an existing react.component is to create a function then call the React.Component from that function and pass the parameter.
import React from 'react';
import useParams from "react-router-dom";
import TaskDetail from './TaskDetail';
function GetId() {
const { id } = useParams();
console.log(id);
return (
<div>
<TaskDetail taskId={id} />
</div>
);
}
export default GetId;
Your switch route will still be something like
<Switch>
<Route path="/task/:id" component={GetId} />
</Switch>
then you will be able to get the id from from the props in your react component
this.props.taskId
In react-router-dom-v6 you can easily use useParams() in functional components but when it gets to the class component you have to create HOC (higher-order component) because hooks don't support class components:
import { useNavigate, useParams } from "react-router-dom";
export const withRouter = (WrappedComponent) => (props) => {
const params = useParams();
const navigate = useNavigate();
return <WrappedComponent {...props} params={params} navigate={navigate} />;
};
Then export your component from your HOC and give your component as a parameter. like below:
export default withRouter(YourComponentName);
After that you can easily access the url id with this.props.params.id and you can navigate to other components with this.props.navigate("/YourPath")
React Route v5
Query params can be read and processed as JSON using withRouter and queryString as follow:
import React from "react";
import { withRouter } from "react-router";
import queryString from 'query-string';
class MyComponent extends React.Component {
componentDidMount() {
const params = queryString.parse(this.props.location.search);
console.log('Do something with it', params);
}
render() {
return <div>Hi!</div>;
}
}
export default withRouter(MyComponent);
SmujMaiku is rigth!!! His answer works perfectly. This is how work today with react-router v6
enter code here
import React ,{Component} from 'react'
import { useParams } from "react-router-dom";
import PokeDescription from '../components/PokeDescription'
class PokeInfoConteiner extends Component{
render(){
let urlPokemon= "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/"
const {idPokemon} = this.props.params
console.log(idPokemon)
return(
<div>
<PokeDescription pokeImage={`${urlPokemon}${idPokemon}.png?raw=true`}/>
<p>{}</p>
</div>
)
}
}
export default (props) => (
<PokeInfoConteiner
{...props}
params={useParams()}
/>)
in React Router V6 :
import React, {Component} from 'react';
import {useParams} from 'react-router-dom';
/* This is a higher order component that
* inject a special prop to our component.
*/
function withRouter(Component) {
function ComponentWithRouter(props) {
let params = useParams()
return <Component {...props} params={params} />
}
return ComponentWithRouter
}
class TaskDetail extends React.Component {
state={
id : ""
}
componentDidMount() {
this.setState({
id : this.props.params.id
})
}
static getDerivedStateFromProps(nextProps) {
return {
id : nextProps.params.id
}
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
const HOCTaskDetail = withRouter(TaskDetail);
export default HOCTaskDetail;
React Route v6
My friends, I tried to use in class but I failed to find any doc about it. So after many hours of searching and trying hard this is (in function). Now (i.e when I'm writing this post) there is only limited resource about v6. But there are many for <v6.
Here I'm using useState,useEffect,useParams,axios.
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
const Post = () => {
let { post_id } = useParams();
const [posts, setPosts] = useState({ post: null, countSecrets: 0, ui: '' });
useEffect(() => {
if (posts.countSecrets === 0) {
const doAxe = (a) => {
axios.get('https://jsonplaceholder.typicode.com/posts/' + post_id)
.then((res) => {
setPosts(s => ({ ...s, value: res.data }));
doUI(res.data)
// console.log(res.data)
});
}
setPosts(s => ({ ...s, countSecrets: s.countSecrets + 1 }));
doAxe()
}
}, [posts, post_id]);
let doUI = (x) => {
// console.log('x' + x.title)
const finalPost = (x !== null) ? (
<div className="post">
<h4 className="center">{x.title}</h4>
<p>{x.body}</p>
</div>
) : (
<div className="center">Loading posts...</div>
);
setPosts(s => ({ ...s, ui: finalPost }));
}
return (
<div className="container">
{posts.ui}
</div>
);
}
export default Post;
NOTE:
I faced useEffect looping. I prevented it with a key.
HOPE: This may help someone!
Reference:
using useParams
state inside function
preventing loop from useEffect
In react-router-dom v6, there is no hook such as withRouter therefore my advice to you is to convert your class-based component to a functional component to use useParams hook in your component otherwise you can create a higher-order component to pass your class-based component.
as you know the useParams() is a hook for react-router-dom.
you can not use this inside the componentDidMount() or useEffect() because both of them are method that called during the Mounting phase of the React Life-cycle i.e after the component is rendered.
you have a solution:
create or define another function outside the componentDidMount() to define useParams then call it inside the componentDidMount.
know every thing will be ok.
This is my working example. :)
import React, { Component } from "react";
import { useParams } from "react-router-dom";
function withParams(Component) {
return (props) => <Component {...props} params={useParams()} />;
}
class ProductDetails extends Component {
handleSave = () => {
// Navigate to /products
};
render() {
return (
<div>
<h1>Product Details - {this.props.params.id}</h1>
<button onClick={this.handleSave}>Save</button>
</div>
);
}
}
export default withParams(ProductDetails);
Hooks only work on functional components,
you have to make that ocmponent a functional component
Fixed by creating a wrapping function
I needed to pass params to my SaxjaxApp.js from index.js using react-router-dom v6.
In v6 Switch has been changed to Routes
I got the useParams working with a class component by following Mohamed MAZEK's idea in post 20 using a wrapping function.
I needed to access the sessionId part of the url when it was available.
ie in localhost:3000/shared/123XYZId
I needed the 123XYZId part.
make note of this line : <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> in the index.js below.
:sessionId denotes that useParams has a property called sessionId, that can be accessed by:
const {sessionId} = useParams() from a functional component.
In my index.js file I did this:
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import "./styles/style.scss";
import SaxjaxAppWrapper from "SaxjaxAppWrapper";
import SaxjaxApp from "./SaxjaxApp";
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
//INFO: to learn about react-roue-dom v6 https://reactrouter.com/en/v6.3.0/upgrading/v5
root.render(
// <React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} />
<Route path="/" element={<SaxjaxApp />} />
</Routes>
</BrowserRouter>
// </React.StrictMode>
);
This line <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> calls my wrapping function, whereas the default path / just calls the class component.
I had to create a separate file to hold the wrapping function I don't know why:
import React from "react";
import { useParams } from "react-router-dom";
import SaxjaxApp from "SaxjaxApp";
function SaxjaxAppWrapper() {
//I use the params here and store them to pass as props
let { sessionId } = useParams();
return (
//I pass the sessionId from the url params as a prop to my SaxjaxApp class component here
<SaxjaxApp sessionId={sessionId} />
);
}
export default SaxjaxAppWrapper;
My class component:
import React, { Component } from "react";
import "./styles/style.scss";
class SaxjaxApp extends Component {
state = {
octave: 4,
};
constructor(props) {
super(props);
//... initialise stuff
}
//... a lot of methods
render() {
//Access the param here
const { sessionId } = this.props;
<>
<div>
keybordId={sessionId ? sessionId : "no id was passed"}
</div>
</>
);
}
}
export default SaxjaxApp;

ReactSSR: Expected server HTML to contain a matching <div> in <body>

Sorry to interrupt you guys to check my question, I have searched abot my question in stackoverflow and read this before: same question as mine
It mentioned this way to solve this warning, but I don't think this is a really good way to handle the question: suppressHydrationWarning={true} prop can also be used on the rendered element. However, as documentation points, this prop must be used scarcely. The better solution is using hydrate() or render() appropriately.
So, here's my trouble when I use React SSR:
I start up nodejs server, and then I request a route in browser.
When server received my request, it should be returned server render
template to browser.
I can see elements are rendered in first screen which means dom are successfully mounted in .
When I click a element which will trigger route in configuration can also render.For now, everything is all right.
Here comes a thing: when I refresh page in browser which route calls '/text1' or '/text2' will also comes a warning like my question's title: Expected server HTML to contain a matching in .
I suspect whether between my route in nodejs and some particular code in client didn't handle well which caused my question.
Here's my particular code[fake]:
// app.js
const http = require('http')
const fs = require('fs')
const path = require('path')
const demo = require('./demo')
const clientScripts = demo('Client')
let scriptsTag = ''
clientScripts.map((script) => {
scriptsTag += `<script src=${script}></script>`
})
const server = http.createServer((req, res) => {
res.setHeader("Access-Control-Allow-Origin","*");
res.setHeader("Access-Control-Allow-Headers","*");
// ssr
const ssrObj = require('./static/entry/serverEntry')
const dom = ssrObj.inital('server').dom
const store = ssrObj.inital('server').store
// const title = ssrObj.inital('server').title
console.log('in: ', dom)
res.setHeader("Content-Type","text/html;charset=utf-8");
res.end(`
<html>
<head>
<title>React & React Router4 SSR</title>
</head>
<body>
<!-- server side -->
<div id="root">${dom}</div>
<script>window.__PRELOADED_STATE__ = ${JSON.stringify(store)}</script>
<!-- ok with client side -->
${scriptsTag}
</body>
</html>
`);
});
server.listen(1234, () => {
console.log('开始监听1234端口')
})
// demo.js
const path = require('path')
const fs = require('fs')
let targetFile = ''
// suppose webpack configuration are ok, its' server output set in '/dist'
const fileList = fs.readdirSync(path.resolve(__dirname, '../dist'))
const container = []
module.exports = (params) => {
fileList.map((file) => {
const ext = path.extname(file).slice(1)
if (ext === 'js') {
const reg = new RegExp(`${params}`,"gim");
if (reg.test(file)) {
container.push(file)
}
}
})
return container
}
// /entry/serverEntry.js
require('babel-polyfill')
require('babel-register')({
presets: [ 'env' ]
})
const App = require('../common/initalEntry')
module.exports = App
// /entry/client.js
require('babel-polyfill')
require('babel-register')({
presets: [ 'env' ]
})
const App = require('../common/initalEntry')
App.inital('client').dom
// /common/initalEntry.js
import React from 'react';
// dom
import {hydrate} from 'react-dom' // client side use hydrate to replace render in react16
import {renderToString} from 'react-dom/server'
// router
import {StaticRouter, BrowserRouter} from 'react-router-dom'
// store
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import * as reducers from '../store/reducers'
import { App } from './App'
export function inital (url = '') {
if (url === 'server') {
console.log(1, url)
const serverStore = createStore(reducers.counter)
return {
dom: renderToString(
<Provider store={serverStore}>
<StaticRouter location={url} context={{}}>
<App type={url} />
</StaticRouter>
</Provider>
),
store: serverStore
}
} else if (url === 'client') {
console.log(2, url, App)
const clientStore = createStore(reducers.counter, window.__PRELOADED_STATE__)
delete window.__PRELOADED_STATE__
return {
dom: hydrate(
<Provider store={clientStore}>
<BrowserRouter>
<App type={url} />
</BrowserRouter>
</Provider>
, document.getElementById('root')
),
store: clientStore
}
}
}
// common/App.js
import React from 'react';
import {Route, Link} from 'react-router-dom'
class Text1 extends React.Component {
constructor (props) {
super(props)
}
render () {
return (
<div>i am text1.</div>
)
}
}
class Text2 extends React.Component {
render () {
return (
<div>i am text2.</div>
)
}
}
export class App extends React.Component {
constructor (props) {
super(props)
}
componentDidMount () {
console.log(this.props, '<<<<<<<')
}
goTo () {
}
render () {
return (
<div>
<Link to="/text1">go text1</Link>
<Link to="/text2">go text2</Link>
<Route path="/text1" component={Text1}></Route>
<Route path="/text2" component={Text2}></Route>
</div>
)
}
}
Above all, these are my configuration about react ssr which causes this question.Thanks to reviewing my question and pls give me some idea to handle this question.I will very apprciated to your help.
Here's my whole code: just viewing and running the server/ directory is ok
Again, thanks for your help.
I deleted the code in App.js, then no longer show the warning, here's my modify:
import React from 'react';
import {Route, Link} from 'react-router-dom'
export class App extends React.Component {
constructor (props) {
super(props)
}
goTo () {
console.log('click me')
}
render () {
return (
<div>
<p onClick={this.goTo.bind(this)}>123</p>
</div>
)
}
}
// I finally solve this problem when I find something a litte bit strange in /common/initalEntry.js
import React from 'react';
// dom
import {hydrate, render} from 'react-dom'
import {renderToString} from 'react-dom/server'
// router
import {StaticRouter, BrowserRouter} from 'react-router-dom'
// store
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import * as reducers from '../store/reducers'
import { App } from './App'
export function inital (type, url = '') {
if (type === 'server') {
const serverStore = createStore(reducers.counter)
return {
dom: renderToString(
<Provider store={serverStore}>
{/* What I pass in location is a empty value which calls 'url' which causes me a lot of time to figure out what happened.The right way is pass the request url received by node.js server to 'location', then no more warning */}
<StaticRouter location={url} context={{}}>
<App />
</StaticRouter>
</Provider>
),
store: serverStore
}
} else if (type === 'client') {
// ...ignore
}
}

React router 4 nested routes not working

I am trying to create a nested route - When user logs in it opens dashboard and when dashboard open i want to create a nested route by making a side menu and change the content at the right but not able to do. When I am trying to access the post page in dashboard it is not opening.
import React from 'react';
import { Switch, Route, Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { userActions } from '../_actions';
import { PostPage } from './PostPage';
import { HomePage } from '../HomePage';
class DashboardPage extends React.Component {
render() {
const { url } = this.props;
return (
<div>
<h1>BasicRouting</h1>
<p>With the help of "Match" Component we can specify the Component we want to render for a particular pattern of the App location/window.pathname.</p>
<p>Select a level from Left Navigation to view the content, also notice the change in URL.</p>
<div className="rightContent">
<p>Second Level Content will appear here:</p>
<Route path={`${this.props.match.url}/post`} component={PostPage} />
</div>
</div>
);
}
}
function mapStateToProps(state) {
console.log(state)
return {
isLoggedIn: state
};
}
const connectedDashboardPage = connect(mapStateToProps)(DashboardPage);
export { connectedDashboardPage as DashboardPage };
There are several problems in your code.
You import { Switch ... } from 'react-router-dom', but never used it afterward.
If you want to call the route in upper/parent components, you need to import { withRouter } to wrap the redux connected class, something like,
const connectedDashboardPage = connect(mapStateToProps)(DashboardPage);
const withRouterConnectedDashboardPage =
withRouter(connectedDashboardPage);
export default withRouterConnectedDashboardPage;
Final suggestion, read through the tutorial here:
https://medium.com/#pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf
&
always refer to: https://reacttraining.com/react-router/

How to make Relay and React Routing work properly?

I am starting out with Relay and trying to make my routing work properly.
Unfortunately, I am not having much of luck.
Here is the error I am getting:
Uncaught TypeError: Component.getFragment is not a function
Here is the code I have for your reference:
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import Relay from 'react-relay';
import {Router, Route, IndexRoute, browserHistory} from 'react-router';
import {RelayRouter} from 'react-router-relay';
import App from './modules/app';
import Home from './modules/home';
const AppQueries = {
store: (Component) => Relay.QL `query {
store {
${Component.getFragment('store')}
}
}`
};
ReactDOM.render(
<RelayRouter history={browserHistory}>
<Route path='/' component={App} queries={AppQueries}>
<IndexRoute component={Home}/>
</Route>
</RelayRouter>,
document.getElementById('ch-root'));
app.jsx
import React, {Component} from 'react';
import Relay from 'react-relay';
import Header from './ui/header';
import Footer from './ui/footer';
class App extends Component {
render() {
return (
<div id="ch-container">
<Header/>
<section id="ch-body">
{this.props.children}
</section>
<Footer/>
</div>
);
}
}
App = Relay.createContainer(App, {
fragments: {
store: (Component) => Relay.QL `
fragment on Store {
${Component.getFragment('store')}
}
`
}
});
export default App;
home.jsx
import React, {Component} from 'react';
import Relay from 'react-relay';
import Loader from './ui/loader';
import AccountSelector from './account/account-selector';
const APP_HOST = window.CH_APP_HOST;
const CURR_HOST = `${window.location.protocol}//${window.location.host}`;
class Home extends Component {
state = {
activeAccount: null,
loading: true
}
render() {
const {activeAccount, loading} = this.state;
if (loading) {
return <Loader/>;
}
if (!activeAccount && !loading) {
return <AccountSelector/>;
}
return (
<h1>Hello!</h1>
);
}
}
Home = Relay.createContainer(Home, {
fragments: {
store: () => Relay.QL `
fragment on Store {
accounts {
unique_id,
subdomain
}
}
`
}
});
export default Home;
UPDATE
I made few changes suggested by freiksenet as below. But that raises the following two issues:
What would happen when I change the route and a component other than Home gets rendered by the App component?
I now get this error:
Warning: RelayContainer: Expected prop store to be supplied to
Home, but got undefined. Pass an explicit null if this is
intentional.
Here are the changes:
index.jsx
const AppQueries = {
store: () => Relay.QL `query {
store
}`
};
app.jsx
import Home from "./home";
...
App = Relay.createContainer(App, {
fragments: {
store: () => Relay.QL `
fragment on Store {
${Home.getFragment('store')}
}
`
}
});
Fragment definitions don't actually get Components as arguments, but a map of variables of the container, you only need to use them to have conditional fragments based on variable values.
Relay Route queries don't accept any arguments.
Here are the changes you need to make.
Route queries in Relay just need to specify the root query you are going to retrieve in this route, without the fragment.
index.jsx
const AppQueries = {
store: () => Relay.QL `query {
store
}`
};
Your App component actually doesn't use any relay data, so you can just export normal component instead of container.
export default class App extends Component {
If you'd need to pass some Relay data to it, you don't need to include child fragments, because fragments inclusion is only needed when you directly render other containers as direct children (not as this.props.children).
Then in Router you need to move the queries to Home.
ReactDOM.render(
<RelayRouter history={browserHistory}>
<Route path='/' component={App}>
<IndexRoute component={Home} queries={AppQueries} />
</Route>
</RelayRouter>,
document.getElementById('ch-root'));

Resources