React Router server rendering with server props - javascript

Here's an example from the docs.
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from './routes'
serve((req, res) => {
// Note that req.url here should be the full URL path from
// the original request, including the query string.
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
// You can also check renderProps.components or renderProps.routes for
// your "not found" component or route respectively, and send a 404 as
// below, if you're using a catch-all route.
res.status(200).send(renderToString(<RouterContext {...renderProps} />))
} else {
res.status(404).send('Not found')
}
})
})
I have an object like this:
let reactServerProps = {
'gaKey': process.env.GA_KEY,
'query': req.query,
}
I'm trying to pass this object into react router here
res.status(200).send(renderToString(<RouterContext {...renderProps} {...reactServerProps} />))
And I can't seem to provide access to the variables from within my components.

The issue is that <ReactContext /> only passes some react-router pre-defined props down to the component tree that it builds rather than the custom props you might expect in a normal component.
There are a few workarounds to this issue, though none of them are particularly pretty. The most widely used I think I've seen is to wrap <ReactContext /> with a component whose sole purpose is to make use of React's context feature & pass contextual data down to its children rather than props.
So:
import React from 'react';
export default class DataWrapper extends React.Component {
getChildContext () {
return {
data: this.props.data
};
}
render () {
return this.props.children;
}
}
Then in your express server:
// Grab the data from wherever you need then pass it to your <DataWrapper /> as a prop
// which in turn will pass it down to all it's children through context
res.status(200).send(renderToString(<DataWrapper data={data}><RouterContext {...renderProps} /></DataWrapper>))
And you should then be able to access that data in your child components:
export default class SomeChildComponent extends React.Component {
constructor (props, context) {
super(props, context);
this.state = {
gaKey: context.data.gaKey,
query: context.data.query
};
}
};
I know it used to be possible to make use of the createElement method to set some custom props and pass them through to your child routes in a similar fashion, but I'm not positive that's still valid in newer versions of react-router. See: https://github.com/reactjs/react-router/issues/1369
UPDATE: It is still possible to use middleware to pass additional values into route components. via: https://github.com/reactjs/react-router/issues/3183
And you should be able to make use of props over context:
function createElementFn(serverProps) {
return function(Component, props) {
return <Component {...serverProps} {...props} />
}
}
Then add createElement in your <RouterContext /> like so, passing it your serverProps:
res.status(200).send(renderToString(<RouterContext {...renderProps} createElement={createElementFn(serverProps)} />))
And access them in any of your child components with a simple this.props.gaKey & this.props.query

A much simpler solution is to put whatever data you want in props.routes and it will be passed to your component where you can access it directly through
props.routes.[whatever you passed in]
So in the serve function you can do
props.routes.reactServerProps = {
'gaKey': process.env.GA_KEY,
'query': req.query
}
and in your component you can access it with props.routes.reactServerProps.
See the very last comment by Ryan Florence. https://github.com/ReactTraining/react-router/issues/1017
He forgot the (s). Won't work with route.

Related

How to implement routing from and to root (index.js) in react with redux

I'm programming a react server webpage, trying to redirect from index.js (i.e: localhost:3000) to Login page: (localhost:3000/login), and from login to index (in case of failed login). What do I need to write in index.js and login.js?
This is for a react based app, using also redux framework. I've tried a few ways including setting up a BrowserRouter etc. All won't really do the redirecting.
My current code is this:
in index.js:
class Index extends Component {
static getInitialProps({store, isServer, pathname, query}) {
}
render() {
console.log(this.props);
//const hist = createMemoryHistory();
if (!this.props.isLoggedIn){
return(<Switch><Route exact path = "/login"/></Switch>)
}
else{...}
in login.js:
render() {
console.log(this.props);
if (fire.auth().currentUser != null) {
var db = fire.firestore();
db.collection("users").doc(fire.auth().currentUser.uid).get().then((doc) => {
this.props.dispatch({ type: 'LOGIN', user: doc.data() });
})
}
const { isLoggedIn } = this.props
console.log(isLoggedIn)
if (isLoggedIn) return <Redirect to='/' />
I except the root to redirect to login if no session is on, and login to redirect to root once there is a successful login.
I am currently getting "You should not use <Switch> outside a <Router>" at index (I have tried to wrap with BrowserRouter, ServerRouter, Router. the first says it needs DOM history. adding history does not change error. two others do not error but are blank display on browser.)
and "ReferenceError: Redirect is not defined" at login.
Any help will be appreciated.
you can use a HOC (Higher-Order Components)
something like this
export default ChildComponent => {
class ComposedComponent extends Component {
componentWillMount() {
this.shouldNavigateAway();
}
componentWillUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.authenticated) {
this.props.history.push('/')
}
}
render() {
return <ChildComponent {...this.props} />
}
}
}
As of now you're trying to return a route declaration wrapped in a Switch component. If you want to redirect the user to the /login page if hes not logged in, you need the route to be declared higher up in the component hierarchy, and then you would be able to return the <Redirect /> component. Either way, I would suggest you check out the react router documentation to see how they do authentication.
https://reacttraining.com/react-router/web/example/auth-workflow

Why won't my child component render props?

I have set up a React Frontend with a Node backend for an app I am trying to make. I have successfully created a server which is hosting my data, which I can then access and receive into my React Frontend. I am able to console.log the data I want and successfully saved it to the state (I think?). My issue is that I can't seem to actually pass the information contained in State into the child component.
Units.js
import UnitsCard from "./InfoCardUnits";
import React, { Component } from "react";
const axios = require("axios");
class Units extends Component {
constructor(props) {
super(props);
this.state = {
units: []
};
}
fetchData() {
axios
.get("http://localhost:3001/allData/units")
.then(response => {
// handle success
// console.log("Success");
this.setState({ units: response.data });
})
.catch(error => {
// handle error
console.error(error);
});
}
componentDidMount() {
this.fetchData();
}
render() {
// this console.log will show the data I want to send as props into my child component.
console.log(this.state.units[0]);
return <UnitsCard props={this.state.units[0]} />;
}
}
export default Units;
InfoUnitCard.js
import "../index.css";
function UnitsCard(props) {
// this console.log will show the "props" information that I want to use in my unit card. But the information itself won't actually show in the browser.
console.log(props);
return (
<div className="card">
<h2>{props.name}</h2>
<h2>{props.category}</h2>
<h2>{props.inf_melee}</h2>
</div>
);
}
export default UnitsCard;
When I console.log the state in either of the components it successfully shows the information I am trying to send. But I can't actually get that information to render. Any help or insights would be much appreciated.
EDIT: This has been resolved, thanks very much to everyone who chipped in an answer.
Avoid passing props in via the props keyword. Instead, consider making the following changes to your code:
render() {
// Get unit or empty object (makes code more readable in next step)
const unit = this.state.units[0] || {};
// Pass each piece of unit data in as a separate prop
return <UnitsCard
name={unit.name}
category={unit.category}
inf_melee={unit.inf_melee} />;
}
Alternatively, you could use the "spread" syntax available with ES6 to make this a little more concise:
render() {
// Get unit or empty object (makes code more readable in next step)
const unit = this.state.units[0] || {};
// Use spread operator to simplify passing of props to UnitsCard
return <UnitsCard {...unit} />;
}
Every thing you pass in the child component will be available in props object in the child component. In your case you are passing a 'props' to props object. This should be available as this.props.props.keyname. try changing your child component as follow.
function UnitsCard(props) {
// this console.log will show the "props" information that I want to use in my unit card. But the information itself won't actually show in the browser.
console.log(props);
return (
<div className="card">
<h2>{props.props.name}</h2>
<h2>{props.props.category}</h2>
<h2>{props.props.inf_melee}</h2>
</div>
);
}
You named your props props, so you can access to it with below code:
console.log(props.props);
you can pass like with a different name:
<UnitsCard child={this.state.units[0]} />
Then access to props with props.child, so your code will change to:
<h2>{props.child.name}</h2>

Blacklist React components

Is there a way to define a function to hook before each component in my app is mounted?
The idea is that if a component is blacklisted it doesn't mount at all.
The solution must leave the components unmodified for backward compatibility and should run in production (so rewire and other testing tools are probably off the table but open to suggestions :) )
Example
//something like this...
ReactDOM.beforeEachComponentMount( (component, action) => {
if(isBlacklisted(component)){
action.cancelMountComponent();
}
}
Could you write a simple Babel plugin that transforms blacklisted components to a noop functional component () => {} at compile time?
You could wrap the required components inside a higher order component that checks whether the component is blacklisted or not.
for example :
class YourComponent extends Component {
constructor(props){
super(props);
}
render(){
return(
// your component goes here ..
);
}
}
export default WithPermission(YourComponent);
check if the component needs to be rendered or not inside the HOC WithPermission.
function withPermission(YourComponent) {
class WithPermission extends React.Component {
constructor(props) {
super(props);
}
// you can check the props inside ComponentDidMount and set a flag if
// the component satisfies the criteria for rendering.
render() {
const {blacklistedComponents,...rest} = this.props;
if(!blackListedComponents){
return <YourComponent {...rest} />
}
else{
return null;
}
}
}
}
There is no such functionality out of box.
You may shim React rendering cycle, I mean shim React.createElement method and validate component before it is added to VDOM
All JSX is processed through React.createElement
e.g. at the start of app add
let React = require('react');
let originalCreateElement = React.createElement;
React.createElement = function() {
let componentConstructorOrStringTagName = arguments[0];
if (isBlacklisted(componentConstructorOrStringTagName)) {
return null;
}
return originalCreateElement.apply(this, arguments);
}
The best idea I can think of is to "shim" react and Component
if you are using webpack you can use this:
https://webpack.js.org/guides/shimming/
in the bottom line that means instead of importing react you will import your own class of react.
In your new class you could extend React Component and place a check on the render function or something similar.
You could implement a custom ESLint rule and catch this as soon as a dev tries to use a blacklisted components. The id-blacklist rule is similar to what you want, but at the identifier level. The source code looks simple. Maybe you can adapt it to disallow more then just identifiers.
Consider the following solution:
Let there be a file where you declare which components are blacklisted:
let blacklist = [{
name: 'secretComponent',
invoke: (props)=> {
return <SecretComponent ...props />
},
isBlacklisted: true
},{
name: 'home',
invoke: (props)=> {
return <HomeComponent ...props />
},
isBlacklisted: false
},{
name: 'login',
invoke: (props)=> {
return <LoginComponent ...props />
},
isBlacklisted: false
}];
Define a Higher Order Component like below:
function renderIfNotBlacklisted(name) {
let component = blacklist.map(x=> x.name == name); //blacklist from above
if (!component.isBlacklisted){
return component.invoke();
} //else can be handled as you will
//You can keep a default component to render or send empty values
}
Call this component in the render function wherever you want this feature to work. This way you have a centralized location to managed blacklisted components (blacklist.json can be in the root of react project or fetched from API on first run)

React - Apollo Client, How to add query results to the state

I've created a react app driven by Apollo client and graphQL.
My schema is defined so the expected result is an array of objects ([{name:"metric 1", type:"type A"},{name:"metric 2", type:"type B"}])
On my jsx file I have the following query defined:
query metrics($id: String!) {
metrics(id: $id) {
type
name
}
}`;
I've wrapped the component with Apollo HOC like so:
export default graphql(metricsQuery, {
options: (ownProps) => {
return {
variables: {id: ownProps.id}
}
}
})(MetricsComp);
The Apollo client works fine and returns the expected list on the props in the render method.
I want to let the user manipulate the results on the client (edit / remove a metric from the list, no mutation to the actual data on the server is needed). However since the results are on the component props, I have to move them to the state in order to be able to mutate. How can I move the results to the state without causing an infinite loop?
If apollo works anything like relay in this matter, you could try using componentWillReceiveProps:
class ... extends Component {
componentWillReceiveProps({ metrics }) {
if(metrics) {
this.setState({
metrics,
})
}
}
}
something like this.
componentWillReceiveProps will be deprecated soon (reference link)
If you are using React 16 then you can do this:
class DemoClass extends Component {
state = {
demoState: null // This is the state value which is dependent on props
}
render() {
...
}
}
DemoClass.propTypes = {
demoProp: PropTypes.any.isRequired, // This prop will be set as state of the component (demoState)
}
DemoClass.getDerivedStateFromProps = (props, state) => {
if (state.demoState === null && props.demoProp) {
return {
demoState: props.demoProp,
}
}
return null;
}
You can learn more about this by reading these: link1, link2
you can use this:
import {useState} from 'react';
import {useQuery} from '#apollo/client';
const [metrics,setMetrics]=useState();
useQuery(metricsQuery,{
variables:{id: ownProps.id},
onCompleted({metrics}){
setMetrics(metrics);
}
});

Converting ES5 Mixins to Higher Order Components

In my project I'm trying to get rid of all the mixins and replace them with HOCs. I am stuck using ES5 at the moment.
export default React.createClass({
mixins: [SomeAsyncMixin],
data: {
item1: {
params: ({params, query}) => {
params: ({params, query}) => {
if (!query.p) {
return null;
}
const status = someTernaryResult
return {
groups: query.groups,
status,
subject: params.subject,
};
},
promise: query => query && query.subject && api(makeUrl(`/some/endpoint`, query))
},
item2: {
params: ({params, query}) => {
//same as before
},
promise: ({subject, query}) =>
// same as before
}
render() {
// some stuff
return(
// some jsx
);
}
}
Inside of the mixin, it has a componentWillMount and a componentWillUpdate that runs an update function that will loop through each key on data and update the props/state.
In React's docs about removing mixins, their mixins hold the data, not the component.
There are MANY components in my project that have a data object and use this mixin to update their props/state. How do I make a reusable component to handle this data object?
Also, how do I even access this data object from within the component? In the component this.data is null. Inside of the mixin this.data is the data object from inside the component.. why?
Your higher order component and mixin will look very similar. The main difference will be how data, props, and state are shared/passed. In the mixin case, you are altering your component's definition with the mixin's behavior, so the state and props are all in the one resulting component.
In the higher order component case, you are creating a new component that wraps around your other component. Thus, the shared state/behavior is entirely contained within wrapping component, and any data that needs to be used within the wrapped component can be passed via props.
So from what you have in your example, your higher order component would be something like
const someAsync = (data) => (WrappedComponent) => {
class SomeAsyncComponent extends React.Component {
constructor(...args) {
super(...args)
this.state = {
...
}
}
componentWillMount() {
// Make use of data, props, state, etc
...
}
componentWillUpdate() {
...
}
render() {
// May filter out some props/state, depending on what is needed
// Can also pass data through if the WrappedComponent needs it.
return (
<WrappedComponent
{ ...this.props }
{ ...this.state }
/>
)
}
}
return SomeAsyncComponent
}
And then your usage of it
export default someAsync(dataConfig)(WrappedComponent)

Categories

Resources