sockpuppet sockpuppet - 1 month ago 16
React JSX Question

How to correctly use compose to combine HOCs that encapsulate protected routes in React-Redux

I'm apparently not grasping the concept of

compose
through
Redux
as I can't get it working as expected.

I currently have two nested HOCs around one of my protected routes: one makes sure the user is verified before navigating to the protected route and the other is just a timer that kicks them out of the protected route if they are idle.

I want to leverage
compose
so that I can clean up my code and not nest my protected routes in HOCs.

This is what I currently have:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { routerMiddleware, ConnectedRouter } from 'react-router-redux';

import createHistory from 'history/createBrowserHistory';

// Render on every route
import App from './components/app';

// Route specific
import Signin from './containers/authentication/signin';
import Signout from './containers/authentication/signout';
import Home from './components/home';

// HoC to wrap protected routes in
import RequireAuth from './helpers/require_auth';
import Timer from './helpers/timer';

// Reducers
import rootReducer from './reducers';

// SCSS for the project
import styles from '../assets/scss/main.scss';

const history = createHistory();

const initialState = {};
const enhancers = [];
const middleware = [thunk, routerMiddleware(history)];

if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.devToolsExtension

if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}

const composedEnhancers = compose(applyMiddleware(...middleware), ...enhancers);
const store = createStore(rootReducer, initialState, composedEnhancers);

ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<App />
<Switch>
<Route exact path='/home' component={LeftSite(RequireAuth(Home))} />
<Route exact path='/auth/signout' component={Signout} />
<Route exact path='/auth/signin' component={Signin} />
<Route exact path='/' component={Signin} />
</Switch>
</div>
</ConnectedRouter>
</Provider>
, document.querySelector('.container'));


What I have tried is modifying like so:

const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
);

const protectedRoutes = compose(
applyMiddleware(RequireAuth, LeftSite)
);

const store = createStore(rootReducer, initialState, composedEnhancers, protectedRoutes);

ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<App />
<Switch>
<Route exact path='/home' component={protectedRoutes(Home)} />
<Route exact path='/auth/signout' component={Signout} />
<Route exact path='/auth/signin' component={Signin} />
<Route exact path='/' component={Signin} />
</Switch>
</div>
</ConnectedRouter>
</Provider>
, document.querySelector('.container'));


This just results in
Cannot call a class as a function
. Here is one of the HOCs as they both have similar structure:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { authRequired } from '../actions/authentication';

export default function(ComposedComponent) {
class AuthenticationRequired extends Component {

// Check if the user has a token when protected route component is about to mount
// Route them to / if they do not
componentWillMount() {
if (!sessionStorage.getItem('token')) {
this.props.authRequired();
}
}

// If the component will be updated then also check if user is authorized
componentWillUpdate() {
if (!sessionStorage.getItem('token')) {
this.props.authRequired();
}
}

// Render the component if passing these checks
render() {
return <ComposedComponent {...this.props} />
}
}

// Conenct to the authRequired action
return connect( null, { authRequired })(AuthenticationRequired);
}


I have tried several variations with varying errors. I wasn't going to post them all. What I have here seems like it might be closer to the correct answer... maybe?

I did read through the documentation which I didn't find helpful in my case:

https://paulkogel.gitbooks.io/redux-docs/content/docs/api/compose.html

So my questions are:

How should I structure the
compose
?

How should do I call it on just my protected routes?

Answer Source

Like the docs say, compose actually does something very simple, conceptually:

All compose does is let you write deeply nested function transformations without the rightward drift of the code. Don’t give it too much credit!

So let's say you have three very simple functions like this:

const capitalize = str => str.toUpperCase();
const takeFirstChar = str => str[0];
const prependHello = str => 'hello' + str;

If you wanted to use all three in a row, without using compose, it would become nested calls like this:

const originalValue = 'test';
console.log(  capitalize(takeFirstChar(prependHello(originalValue))))
// prints  'H' 
// (the first letter from the appended 'hello', capitalized)

See how the first function to be called (prependHello) is all the way to the right? That's what was meant with "the rightward drift of the code".

But with compose we can, instead, write it like this:

// NOTE the order of the arguments!
// The bottom one is applied first, then middle one, then the top one
const prependHelloAndTakeFirstCharToUpper = compose(
  capitalize,
  takeFirstChar,
  prependHello
);

console.log( prependHelloAndTakeFirstCharToUpper(originalValue)   )
// still prints 'H'

It's not clear how you where using your HOC's before when they worked, but if you didn't involve things like applyMiddleWare before then that probably shouldn't be there now either.

My guess, without knowing more about your code, is that it should work to do something like this:

const protectedRoutes = compose(RequireAuth, LeftSite);

// further down
<Route exact path='/home' component={protectedRoutes(Home)} />

And I think you can remove your protectedRoutes from your arguments to createStore.