What is the best approach to render multiple React componenes - reactjs

I wanna to create a following React/Redux app, and I'm looking for the best approach to handle this.
Let's start from main App container:
class App extends React.Component {
render() {
const {open} = this.props;
return open ? <Maximized/> : <Minimized/>;
}
}
const mapStateToProps = (state) => ({
open: getIsOpen(state)
});
export default connect(
mapStateToProps,
{}
)(App);
Now it's easy - depends on the state I render only two containers. Minimized container is easy so let's skip it.
In Maximized container I want to display a different views(?).
class Maximized extends React.Component {
render() {
const {view} = this.props;
let content = null;
if (view === 'DEFAULT') {
content = <Component1/>
} else if (view === 'COMPONENT2') {
content = <Component2/>
}
return <div>
{content}
<Close/>
</div>;
};
}
const mapStateToProps = (state) => ({
view: getView(state)
});
export default connect(
mapStateToProps,
{}
)(Maximized);
view is from state selector getView and is handled by the simple if, but now I have only two components to display, in the future I think this components can be n.
I think about router with the MemoryRouter (I can't use URL), but I don't know is a good approach because of I must store current state in the local storage.
Maybe there is any pattern, good practice or tool for archive this.

I have used a similar pattern a lot and I think it works well. One change is I would just clean up your conditional logic a bit. I would use a switch instead so that it's much easier and cleaner to handle n amount of components:
class Maximized extends React.Component {
getView = () => {
const {view} = this.props;
switch(view) {
case 'DEFAULT': {
return <Component1/>
}
case 'COMPONENT2': {
return <Component2/>
}
case 'COMPONENT3': {
return <Component3/>
}
default: {
return <Component1 />
}
}
};
render() {
const content = this.getView();
return <div>
{content}
<Close/>
</div>;
};
}
const mapStateToProps = (state) => ({
view: getView(state)
});
export default connect(
mapStateToProps,
{}
)(Maximized);

Well, it is kinda strange that you would have so many components using the same URL. With that being said, if you absolutely can't use URL, I guess you can create a file that import all your components, and export an object acting as a map between your view value and your component class. Something like:
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
import ComponentC from './ComponentC';
export default {
'DEFAULT': ComponentA,
'PROFILE': ComponentB,
'CONTACT': ComponentC,
}
Then at your App container, you can probably do something like this:
import ComponentMap from './ComponentMap';
...
...
...
render() {
const Component = ComponentMap[this.state.view];
return <Component />;
}
codesandbox for demo
EDIT:
The reason for putting all the mapping to components at separate file is to have a separation of concern. Using a traditional switch or if logic would require you to add at the very least 2 lines of code to your App container. In time, your App container could be very lengthy. If your App container also handle other things (not just mapping to components), then it would be not very pleasant to work with it.
By using separate file, your App container will not be filled with all the mappings concern. The separate file is also less verbose than using switch or if. Compare adding
'KEY': ComponentX,
to
case 'KEY': ComponentX; break;
Last but not least, using switch or if would result in O(n) time to get the component you are looking for, compared to O(1) time if you are using javascript object.

Related

Hide some React component children depending on user role

I am writing a single page application in React and Redux (with a Node.js backend).
I want to implement role-based access control and want to control the display of certain parts (or sub parts) of the app.
I'm going to get permissions list from Node.js, which is just an object with such structure:
{
users: 'read',
models: 'write',
...
dictionaries: 'none',
}
key is protected resource,
value is user permission for this resource (one of: none, read, write).
I'm storing it into redux state. Seems easy enough.
none permission will be checked by react-router routes onEnter/onChange hooks or redux-auth-wrapper. It seems easy too.
But what is the best way to apply read/write permissions to any component view (e.g. hide edit button in Models component if the user has { models: 'read' } permission).
I've found this solution and change it a bit for my task:
class Check extends React.Component {
static propTypes = {
resource: React.PropTypes.string.isRequired,
permission: React.PropTypes.oneOf(['read', 'write']),
userPermissions: React.PropTypes.object,
};
// Checks that user permission for resource is the same or greater than required
allowed() {
const permissions = ['read', 'write'];
const { permission, userPermissions } = this.props;
const userPermission = userPermissions[resource] || 'none';
return permissions.indexOf(userPermission) >= permissions.indexOf(permission)
}
render() {
if (this.allowed()) return { this.props.children };
}
}
export default connect(userPermissionsSelector)(Check)
where userPermissionsSelector would be something like this: (store) => store.userPermisisons and returns user permission object.
Then wrap protected element with Check:
<Check resource="models" permission="write">
<Button>Edit model</Button>
</Check>
so if user doesn't have write permission for models the button will not be displayed.
Has anyone done anything like this? Is there more "elegant" solution than this?
thanks!
P.S. Of course user permission will also be checked on the server side too.
Well I think I understood what you want. I have done something that works for me and I like the way I have it but I understand that other viable solutions are out there.
What I wrote was an HOC react-router style.
Basically I have my PermissionsProvider where I init the users permissions. I have another withPermissions HOC that injects the permissions I provided earlier into my component.
So if I ever need to check permissions in that specific component I can access them easily.
// PermissionsProvider.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";
class PermissionsProvider extends React.Component {
static propTypes = {
permissions: PropTypes.array.isRequired,
};
static contextTypes = {
permissions: PropTypes.array,
};
static childContextTypes = {
permissions: PropTypes.array.isRequired,
};
getChildContext() {
// maybe you want to transform the permissions somehow
// maybe run them through some helpers. situational stuff
// otherwise just return the object with the props.permissions
// const permissions = doSomething(this.props.permissions);
// maybe add some validation methods
return { permissions: this.props.permissions };
}
render() {
return React.Children.only(this.props.children);
}
}
const withPermissions = Component => {
const C = (props, context) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<Component permissions={context.permissions} {...remainingProps} ref={wrappedComponentRef} />
);
};
C.displayName = `withPermissions(${Component.displayName || Component.name})`;
C.WrappedComponent = Component;
C.propTypes = {
wrappedComponentRef: PropTypes.func
};
C.contextTypes = {
permissions: PropTypes.array.isRequired
};
return hoistStatics(C, Component);
};
export { PermissionsProvider as default, withPermissions };
Ok I know this is a lot of code. But these are HOC (you can learn more here).
A higher-order component (HOC) is an advanced technique in React for
reusing component logic. HOCs are not part of the React API, per se.
They are a pattern that emerges from React’s compositional nature.
Concretely, a higher-order component is a function that takes a
component and returns a new component.
Basically I did this because I was inspired by what react-router did.
Whenever you want to know some routing stuff you can just add the decorator #withRouter and they inject props into your component. So why not do the same thing?
First you must setup the permissions with the provider
Then you use the withPermissions decorator on the components that check permissions
//App render
return (
<PermissionsProvider permissions={permissions}>
<SomeStuff />
</PermissionsProvider>
);
Somewhere inside SomeStuff you have a widely spread Toolbar that checks permissions?
#withPermissions
export default class Toolbar extends React.Component {
render() {
const { permissions } = this.props;
return permissions.canDoStuff ? <RenderStuff /> : <HeCantDoStuff />;
}
}
If you can't use decorators you export the Toolbar like this
export default withPermissions(Toolbar);
Here is a codesandbox where I showed it in practice:
https://codesandbox.io/s/lxor8v3pkz
NOTES:
I really really simplified the permissions because that logic comes from your end and for demo purposes I simplified them.
I assumed the permissions were an array so that is why I check the PropTypes.array in the HOCs
It's a really long and complicated answer and I tried to articulate at my best ability. Please don't grill me for some mistakes here and there :)
The approach that suggested by #lokuzt is great.
And you can go even further in order to simplify your code.
First of all, every protected component has some requirement to be satisfied for render. You need to define a function that takes requirement to render and credentials of the current user as parameters. It must return true or false.
function isSatisfied(requirement, credentials) {
if (...) {
return false;
}
return true;
}
Further, we have to define a HOC (Higher-Order Component) using the new context API from ReactJS.
const { Provider, Consumer } = React.createContext();
function protect(requirement, WrappedComponent) {
return class extends Component {
render() {
return (
<Consumer>
{ credentials => isSatisfied(requirement, credentials)
? <WrappedComponent {...this.props}>
{this.props.children}
</WrappedComponent>
: null
}
</Consumer>
);
}
};
}
Now you can decorate your components:
const requireAdmin = {...}; // <- this is your requirement
class AdminPanel extends Component {
...
}
export default protect(requireAdmin, AdminPanel);
or even third-party components:
import {Button} from 'react-bootstrap';
const AdminButton = protect(requireAdmin, Button);
Credentials have to be passed by ReactJS context API:
class MyApp extends Component {
render() {
const {credentials} = this.props;
<Provider value={credentials}>
...
<AdminPanel/>
<AdminButton>
Drop Database
</AdminButton>
...
</Provider>
}
}
Here is my extended implementation on github.
The demo is also available too.

use react-redux with connect() and {...this.props}

I cannot figure out, how to make right solution, when I want to call action in my container from other component, by the way I want to use spread operator because I need to pass too many parametrs in my component and don't want describe all of them.
I know I can pass all props from redux store via props, like this example in Menu, but my component too nested, and I have to send props in eighter component in nest
render() {
return (
<div className="wrapper">
<Menu {...this.props} />
</div>
);
}
}
const mapStateToProps = reduxStore => (
{
app: reduxStore.app
}),
mapDispatchToProps = dispatch => ({appActions: bindActionCreators(appActions, dispatch)});
export default connect(mapStateToProps, mapDispatchToProps)(App);
So, I decided to connect my nested component with redux store, because I need to work from my nested component with store and actions in main container component. But this solution doesn't work, because i use spread operator to my nested component.
render() {
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} {...this.props} />;
}
And using spread operator is really important because component get too much different parameters from its parent component, and if i don't use {...this.props}, I have to write like this:
render() {
const { to, onlyActiveOnIndex, className, specialIcons } = this.props;
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} to={to} specialIcons={specialIcons} onlyActiveOnIndex={onlyActiveOnIndex} className={className} >{this.props.children}</Link>;
}
But also, I have to connect to common redux store, and when I connected, occurs an Error, because of my component use {...this.props} and it get all props, including actions from container and component doesn't know what do with them. I find one solution of this proplem, but I'm not sure that it is right variant. I clone props with spread operators, but delete property that contain new functions (actions) from common store.
render() {
let oldProps = {...this.props};
delete oldProps.appActions;
delete oldProps.app;
return <Link activeClassName='active' onClick={this.props.appActions.closeMenu} {...oldProps} >{this.props.children}</Link>;
}
}
const mapState = reduxStore => ({app: reduxStore.app}),
mapDispatchToProps = dispatch => ({appActions: bindActionCreators(appActions, dispatch)});
export default connect(mapState, mapDispatchToProps)(NavLink);
I'm guessing that I don't understand something basic and global in react-redux or I use bad practice. May be I should use higher order components in React? but now I don't know how to make it better.
Here is a functional example. I made it for a personal project. I removed the useless code for the purpose of the example.
Something you might want to get is eslint, it will show you basic mistake people are making while coding.
For example, it will say that you having declared your PropTypes. In your code, where does it say what app is? Sure it's coming from reduxStore.app but what kind of PropTypes is it?
Also, you shouldn't link all the reduxStore state to your component. You should just import what you really need. In my example, I import only users from state.app.users. If I had more, or want all elements of the reducer state, I would import all of them individually and then declare the props like this:
Home.propTypes = {
users: PropTypes.object.isRequired,
actions: {
load: PropTypes.func.isRequired,
},
};
Because JavaScript isn't a typed language, the PropTypes like above help you make typed validation. You can also see the props actions which contains all the functions you import in AppActions in your case.
To see how to use the function from the action afterward, look at my componentWillMount()
import React, { Component, PropTypes } from 'react';
import { ListView} from 'react-native';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as app from '../../actions/appActions';
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: ds.cloneWithRows(props.users.toJS()),
};
}
componentWillMount() {
this.props.actions.load();
}
componentWillReceiveProps(nextProps) {
if (this.props.users !== nextProps.users) {
this.setState({
dataSource: ds.cloneWithRows(nextProps.users),
});
}
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
enableEmptySections
renderRow={
(rowData) => <User haveLunch={rowData.haveLunch} name={rowData.name} />
}
/>
);
}
}
Home.propTypes = {
users: PropTypes.object.isRequired,
actions: {
load: PropTypes.func.isRequired,
},
};
function mapStateToProps(state) {
return {
users: state.app.users,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(app, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Hope this will help ya ;)

Reusing container and container extending other containers

import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { setLocation } from 'redux/modules/filters'
import SearchForm from 'components/SearchForm/SearchForm'
type Props = {
};
export class HomeSearchContainer extends React.Component {
props: Props;
static contextTypes = {
router: React.PropTypes.object.isRequired
};
constructor(props) {
super(props)
this.onSearch = this.onSearch.bind(this)
}
onSearch(address, event) {
event.preventDefault()
if (address) {
this.props.actions.setLocation(address)
}
this.context.router.push('/browse_items')
}
render() {
return (
<SearchForm
onSearch={this.onSearch}
currentLocation={this.props.currentLocation}
/>
)
}
}
const mapStateToProps = (state) => {
return {
currentLocation: state.filters.location
}
}
const mapDispatchToProps = (dispatch) => {
var actions = {
setLocation
}
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(HomeSearchContainer)
I have a few questions to validate my understanding.
Do we ever re-use containers? Am I correct if I say we intend to re-use components but not containers?
In the above code, I want to create another container that doesn't redirect to /browse_items. In other words, I just want to override onSearch function of this container. Is it okay to extend this container?
First of all, in my mind a container is a certain kind of component, so following this article: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.jqwffnfup I'll rather talk about container components and presentational components.
Ad 1) I would say we can re-use container components as well as presentational components - this always depends on how our code is structured. E.g. a container component might have child components, which can be different in different parts of the screen, but the container component is being re-used.
Ad 2) If you just want to have a different onSearch functionality, I might consider passing the onSearch callback to the HomeSearchContainer via the props. That way, you can re-use the same container, but it behaves differently.
Looking closely at your code, there is then not much left to the HomeSearchContainer, so you might as well use the SearchForm directly. If you need the SearchForm in multiple places, it might be worth pulling out the onSearch function into its own file and re-using that. But this is hard to judge without seeing the rest of the application. So I'm trying to throw some ideas at you here, which may or may not apply to your codebase.

React redux generated stateless components perfomance

I am learning redux and react. And i decided to run a simple "stress test" with lets say 15k rows of generated component (i hope i did it right).
So i have stateless component which receive common prop for example 'year'. And i want to clone this stateless component over 9000 times and update them. For example change it prop(year) from 2016 to 2015.
I built this component in my testing project and it's working, but with slow response especially in IE 11. I am new to react+redux and maybe i did something wrong in my code.
As suggested in discord chat room i have added into my Page component:
shouldComponentUpdate(nProps, nState) {
return nProps.year != this.props.year;
}
This did help a bit. But it is still slow.
Also as related question - Is it ok to use lodash.assign() to update my state?
Also i am using typescript and it seems to have not built-in polyfill for Object.assign(); That's why i decided to try lodash.
So here is my top base component app.tsx:
import * as React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as pageActions from '../actions/page';
import User from '../components/user/User';
import Page from '../components/page/Page';
class App extends React.Component<any, any> {
render() {
const { user, page } = this.props;
const { setYear } = this.props.pageActions;
return (
<div>
<User name={user.name} />
<Page photos={page.photos} year={page.year} setYear={setYear} />
</div>
);
};
}
function mapStateToProps (state) {
return {
user: state.user, // (1)
page: state.page // (2)
};
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
And this is my page reducer:
import {assign} from 'lodash';
const INITIAL_STATE = {
year: 2016,
photos: []
};
function pageReducer(state = INITIAL_STATE,
action = {type: '', payload: null}) {
switch (action.type) {
case 'SET_YEAR':
return assign({}, state, {year: action.payload});
default:
return state;
}
}
export default pageReducer;
And Page component:
import * as React from 'react';
import {range} from 'lodash';
let StatelessSpan: React.StatelessComponent<any> = (props) => (
<span>{props.year} </span>
);
class Page extends React.Component<any, any> {
constructor(props) {
super(props);
}
private onYearBtnClick = (e) => {
this.props.setYear(+e.target.innerText);
};
shouldComponentUpdate(nProps, nState) {
return nProps.year != this.props.year;
}
render() {
const {year, photos} = this.props;
let years = range(15000).map((value, index) => {
if(index % 4===0){
return <StatelessSpan key={index} year={year} />;
}
return <span key={index}>i am empty</span>
});
return <div>
<p>
<button onClick={this.onYearBtnClick}>2016</button>
<button onClick={this.onYearBtnClick}>2015</button>
<button onClick={this.onYearBtnClick}>2014</button>
</p>
{years}
</div>;
};
}
export default Page;
One told me that innerText is experimental and non-stable, so i've changed it to textContent. Still got delay in IE.
React/Redux may be THE best way to write apps, but it's important to understand that elegancy can sometimes come at the cost of performance issues. Luckily, it's much easier to take an elegant solution and make it performant than the other way around.
I could throw a bunch of performance optimization tips at you for React and Redux, but you might be optimizing the wrong things. You need profile your app and find out performance issues you are running into.
You might find this talk extremely helpful: https://www.youtube.com/watch?v=5sETJs2_jwo. Netflix has been able to start with very slow React and really make things go super fast without making a mess of things.
I've found this discussion here: https://twitter.com/mweststrate/status/720177443521343488
So this partially answers my question about performance and gives good vision on how this both libraries behave with my case.

React-Redux - Reuseable Container/Connector

I am completely lost on the react-redux container (ie connector) concept as it is not doing what I anticipated. My issue is straight forward, and to me reasonable, yet I cannot find a well written example of how to accomplish it.
Let us say we have a react component that connects to a store that has product context, and we will call this component ProductContext.
Furthermore, let's say we want to reuse ProductContext liberally throughout the app so as to avoid the boilerplate code of dispatching actions on every other component that may need products.
Illustratively this is what I mean:
from DiscountuedProducts:
<ProductContext >
// do something with props from container
</ProductContext >
from SeasonalProducts:
<ProductContext >
// do something with props from container
</ProductContext >
From the examples I see at react-redux, it appears to me that their containers lump both seasonal and discontinued products in the container itself. How is that reusable?
from the ProductContextComponent:
<section >
<DiscontinuedProducts />
<SeasonalProducts />
</section >
Complicating matters, while trying to keep a cool head about this most frustrating matter, "nebulous tersity" seems to be the only responses I receive.
So here is my ProductContext:
#connect(state => ({
products: state.productsReducer.products
}))
export default class ProductContext extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { dispatch } = this.props;
const clientId = this.context.clientInfo._id;
dispatch(fetchProductsIfNeeded(clientId));
}
// get from parent
static contextTypes = {
clientInfo: PropTypes.object.isRequired
};
static propTypes = {
children: PropTypes.node.isRequired,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
render() {
if (!this.props.products) return <Plugins.Loading />;
.....
return (
// I want to put the products array here
);
}
};
Then my thinking is if I do this:
from DiscountuedProducts:
<ProductContext >
// do something with props from container
</ProductContext >
DiscontinuedProducts should have knowledge of those products and it can simply filter for what is discontinued.
Am I completely wrong on this? Is this not reasonable to desire?
If anyone knows of a exhaustive example on the Net demonstrating how this can be achieved, I would much appreciate it being pointed out to me. I have spent over a week on this issue and am about ready to give up on react-redux.
UPDATE: A very slick solution below with use of a HOC.
If anyone knows of a exhaustive example on the Net demonstrating how this can be achieved, I would much appreciate it being pointed out to me.
Look at the Shopping Cart example.
Examples code from: shopping-cart/containers/CartContainer.js
Let us say we have a react component that connects to a store that has product context,
There isn't a store for products and a store for users, etc. There is one store for everything. You should use reducers to take the full store and reduce to what your component needs.
From the example:
import { getTotal, getCartProducts } from '../reducers'
const mapStateToProps = (state) => {
return {
products: getCartProducts(state),
total: getTotal(state)
}
}
Here state is the entire store.
let's say we want to reuse ProductContext liberally throughout the app so as to avoid the boilerplate code of dispatching actions
Components don't dispatch actions, they call functions passed to them. The functions live in an actions module, which you import and pass to container as props. In turn, the container passes those functions to component props.
From the example:
import { checkout } from '../actions'
CartContainer.propTypes = {
checkout: PropTypes.func.isRequired
}
connect(mapStateToProps,
{ checkout }
)(CartContainer)
What connect does, it subscribes to store changes, calls the map function, merges with constant props (here the action function), and assigns new props to the container.
DiscontinuedProducts should have knowledge of those products and it can simply filter for what is discontinued
This is actually spot on. The knowledge you mention is the one store, and it should absolutely filter in a reducer.
Hopefully this clears things up.
I have found a more practical way of utilizing repetitive data from redux than the docs. It makes no sense to me to repeat mapToProps and dispatch instantiation on every blessed component when it was already done once at a higher level, and there inlies the solution. Make sure your app is Babel 6 compliant as I used decorators.
1. I created a higher order component for the context ....
product-context.js:
import React, { Component, PropTypes } from 'react';
// redux
import { connect } from 'react-redux';
import { fetchProductsIfNeeded } from '../../redux/actions/products-actions';
// productsReducer is already in state from index.js w/configureStore
#connect(state => ({
products: state.productsReducer.products
}))
export default function ProductContext(Comp) {
return (
class extends Component {
static propTypes = {
children: PropTypes.node,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
static contextTypes = {
clientInfo: PropTypes.object.isRequired;
};
componentDidMount() {
const { dispatch } = this.props;
const clientId = this.context.clientInfo._id;
dispatch(fetchProductsIfNeeded(clientId));
}
render() {
if (!this.props.products) return (<div>Loading products ..</div>);
return (
<Comp products={ this.props.products }>
{ this.props.children }
</Comp>
)
}
}
)
}
2. component utilizing product-context.js
carousel-slider.js:
import React, { Component, PropTypes } from 'react';
......
import ProductContext from '../../../context/product-context';
#Radium
#ProductContext
export default class CarouselSlider extends Component {
constructor(props) {
super(props); }
......
static showSlideShow(carouselSlides) {
carouselSlides.map((slide, index) => {
......
results.push (
......
)
});
return results;
}
render() {
const carouselSlides = this.props.products;
const results = CarouselSlider.showSlideShow(carouselSlides);
return (
<div id="Carousel" className="animation" ref="Carousel">
{ results }
</div>
);
}
}
So there you go. All I needed was a decorator reference to product-context, and as a HOC, it returns the carousel component back with the products prop.
I saved myself at least 10 lines of repetitive code and I removed all related contextType from lower components as it is no longer needed with use of the decorator.
Hope this real world example helps as I detest todo examples.

Resources