Blacklist React components - javascript

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)

Related

How do I access app.state from a Cypress test in a Remix project

Cypress has a way to expose the app's state to the test runner -- in React it usually looks like this:
class MyComponent extends React.Component {
constructor (props) {
super(props)
// only expose the app during E2E tests
if (window.Cypress) {
window.app = this
}
}
...
}
Then you could access your state in a test with
cy.window()
.its('app.state')
.should('deep.equal', myStateObject)
However, the setup for Remix projects relies on functional components. I've tried this in my root.tsx component with a useEffect call:
export default function App() {
useEffect(() => {
window.app = App;
}, []}
}
as well as in the root route (routes/index.tsx) by importing the <App /> component and using the logic in the useEffect function above. Neither of these options are working and I'm not sure where else to go here. Remix's GitHub issues are devoid of questions about this issue, so maybe I'm going about this the wrong way. Any help is appreciated! Thanks!
I haven't done much work with Remix, but there is a question here that might be useful:
React - getting a component from a DOM element for debugging.
Note the last paragraph
Function components
Function components don't have "instances" in the same way classes do, so you can't just modify the FindReact function to return an object with forceUpdate, setState, etc. on it for function components.
That said, you can at least obtain the React-fiber node for that path, containing its props, state, and such. To do so, modify the last line of the FindReact function to just: return compFiber;
There's a lib cypress-react-app-actions that implements this for Cypress
export const getReactFiber = (el) => {
const key = Object.keys(el).find((key) => {
return (
key.startsWith('__reactFiber$') || // react 17+
key.startsWith('__reactInternalInstance$') // react <17
)
})
if (!key) {
return
}
return el[key]
}
// react 16+
export const getComponent = (fiber) => {
let parentFiber = fiber.return
while (typeof parentFiber.type == 'string') {
parentFiber = parentFiber.return
}
return parentFiber
}
One of the example tests is
/// <reference types="cypress" />
import { getReactFiber, getComponent } from '../support/utils'
it('calls Example double()', () => {
cy.visit('/')
cy.get('.Example').within(() => { // select via className of component
cy.contains('[data-cy=count]', '0')
cy.get('[data-cy=add]').click().click()
cy.contains('[data-cy=count]', '2')
cy.root().then((el$) => {
const fiber = getReactFiber(el$[0])
console.log(fiber)
const component = getComponent(fiber)
console.log(component.stateNode)
cy.log('calling **double()**')
component.stateNode.double() // work with component for functional
})
cy.contains('[data-cy=count]', '4')
})
})
This example is for class components, but given the info in Function components section above, you would use the component object rather than component.stateNode.

New to React - How do I go about fixing/improving this component?

I'm new to the React framework and recently wrote the below code but have been asked to understand what is wrong with it and how I could improve/fix this component.
Please could someone advise a better way of structuring this component and why this approach? Thanks!
class App extends React.Component {  
  constructor(props) {
    super(props);
    this.state = {
      name: this.props.name || 'Anonymous'
    }
  }    
  render() {
    return (
      <p>Hello {this.state.name}</p>
    );  
  }
}
Unless name is supposed to change at some point, your component does not need to be stateful. Simply use the name prop, and use defaultProps to provide the default value:
class App extends React.Component {
render() {
return (
<p>Hello {this.props.name}</p>
)
}
}
App.defaultProps = {name: 'Anonymous'}
You actually don't need to use a class for such a simple component:
function App() {
return (
<p>Hello {this.props.name}</p>
)
}
App.defaultProps = {name: 'Anonymous'}
See the Function and Class Components section for more information about functional vs classes component.
The construction of the state cannot be done that way. You must use componentDidMount() and setState(),
componentDidMount() {
this.setState({
name: this.props.name
});
}
Do you receive some content from an input, or you just want to display this state? If you just wanna display this state, you can just change constructor block with state = { name: 'Anonimus'} and call it in your jsx just as you did.

Best practices for using React refs to call child function

I'm hoping for some clarity on the use of React refs for calling a child function. I have a Parent component that's a toolbar with a few buttons on it, and in the child component I have access to a library's export functionality. I'd like to call this export function on a button click in the parent component. Currently I'm using React refs to accomplish this:
Parent.js [ref]
class Parent extends React.Component {
onExportClick = () => {
this.childRef.export();
}
render() {
return (
<div>
<button onClick={this.onExportClick} />Export</button>
<Child ref={(node) => this.childRef = node;} />
</div>
)
}
}
Child.js [ref]
class Child extends React.Component {
export() {
this.api.exportData();
}
render() {
<ExternalLibComponent
api={(api) => this.api = api}
/>
}
}
This solution works fine, but I've seen a lot of disagreement on if this is the best practice. React's official doc on refs says that we should "avoid using refs for anything that can be done declaratively". In a discussion post for a similar question, Ben Alpert of the React Team says that "refs are designed for exactly this use case" but usually you should try to do it declaratively by passing a prop down.
Here's how I would do this declaratively without ref:
Parent.js [declarative]
class Parent extends React.Component {
onExportClick = () => {
// Set to trigger props change in child
this.setState({
shouldExport: true,
});
// Toggle back to false to ensure child doesn't keep
// calling export on subsequent props changes
// ?? this doesn't seem right
this.setState({
shouldExport: false,
});
}
render() {
return (
<div>
<button onClick={this.onExportClick} />Export</button>
<Child shouldExport={this.state.shouldExport}/>
</div>
)
}
}
Child.js [declarative]
class Child extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.shouldExport) {
this.export();
}
}
export() {
this.api.exportData();
}
render() {
<ExternalLibComponent
api={(api) => this.api = api}
/>
}
}
Although refs are seen as an "escape hatch" for this problem, this declarative solution seems a little hacky, and not any better than using refs. Should I continue to use refs to solve this problem? Or should I go with the somewhat hacky declarative approach?
You don't need to set the shouldExport back to false, you could instead detect the change:
componentWillReceiveProps(nextProps) {
if (nextProps.shouldExport !== this.props.shouldExport) {
this.export();
}
}
Then every toggle of the shouldExport would cause exactly one export. This however looks weird, I'd use a number that I'd increment:
componentWillReceiveProps(nextProps) {
if (nextProps.exportCount > this.props.exportCount) {
this.export();
}
}
I ran into the same problem in many occasions now, and since the React team doesn't encourage it i'll be using the props method for later development, but the problem is sometimes you want to return a value to the parent component, sometimes you need to check the child's state to decide whether to trigger an event or not, therefore refs method will always be my last haven, i suggest you do the same

React: componentDidMount + setState not re-rendering the component

I'm fairly new to react and struggle to update a custom component using componentDidMount and setState, which seems to be the recommended way of doing it. Below an example (includes an axios API call to get the data):
import React from 'react';
import {MyComponent} from 'my_component';
import axios from 'axios';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
GetData() {
return axios.get('http://localhost:5000/<route>');
}
componentDidMount() {
this.GetData().then(
(resp) => {
this.setState(
{data: resp.data}
)
}
)
}
render() {
return (
<MyComponent data={this.state.data} />
);
}
}
Doing console.log(this.state.data) just below render() shows that this.state.data does indeed get updated (from [] to whatever the API returns). However, the problem appears to be that MyComponent isn't rendered afresh by componentDidMount. From the Facebook react docs:
Setting state in this method will trigger a re-rendering.
This does not seem to be the case here: The constructor of MyComponent only gets called once (where this.props.data = []) and the component does not get rendered again. I'd be great if someone could explain why this is and whether there's a solution or a different way altogether to get the updating done.
UPDATE
I've added the code for MyComponent (minus some irrelevant features, as indicated by ...). console.log(data_array) prints an empty array.
import React from 'react';
class DataWrapper {
constructor(data) {
this._data = data;
}
getSize() {
return this._data.length;
}
...
}
export class MyComponent extends React.Component {
constructor(props) {
super(props);
this._dataWrapper = new DataWrapper(this.props.data);
this.state = {
data_array: this._dataWrapper,
};
}
render() {
var {data_array} = this.state;
console.log(data_array);
return (
...
);
}
}
You are falling victim to this antipattern.
In MyComponent constructor, which only gets called the first time it mounts, passed your empty array through new DataWrapper and now you have some local state which will never be updated no matter what your parent does.
It's always better to have one source of truth, just one state object anywhere (especially for things like ajax responses), and pass those around via props. In fact this way, you can even write MyComponent as a simple function, instead of a class.
class Example extends Component {
state = { data: [] }
GetData() { .. }
componentDidMount() {
this.GetData().then(res =>
this.setState({data: new DataWrapper(res.data)})
)
}
render() { return <MyComponent data={this.state.data} /> }
}
...
function MyComponent (props) {
// props.data will update when your parent calls setState
// you can also call DataWrapper here if you need MyComponent specific wrapper
return (
<div>..</div>
)
}
In other words what azium is saying, is that you need to turn your receiving component into a controlled one. Meaning, it shouldn't have state at all. Use the props directly.
Yes, even turn it into a functional component. This helps you maintain in your mind that functional components generally don't have state (it's possible to put state in them but ... seperation of concerns).
If you need to edit state from that controlled component, provide the functions through props and define the functions in the "master" component. So the master component simply lends control to the children. They want anything they talk to the parent.
I'm not posting code here since the ammendment you need to make is negligible. Where you have this.state in the controlled component, change to this.props.

How to test decorated React component with shallow rendering

I am following this tutorial: http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/
Trying to learn how "shallow rendering" works.
I have a higher order component:
import React from 'react';
function withMUI(ComposedComponent) {
return class withMUI {
render() {
return <ComposedComponent {...this.props}/>;
}
};
}
and a component:
#withMUI
class PlayerProfile extends React.Component {
render() {
const { name, avatar } = this.props;
return (
<div className="player-profile">
<div className='profile-name'>{name}</div>
<div>
<Avatar src={avatar}/>
</div>
</div>
);
}
}
and a test:
describe('PlayerProfile component - testing with shallow rendering', () => {
beforeEach(function() {
let {TestUtils} = React.addons;
this.TestUtils = TestUtils;
this.renderer = TestUtils.createRenderer();
this.renderer.render(<PlayerProfile name='user'
avatar='avatar'/>);
});
it('renders an Avatar', function() {
let result = this.renderer.getRenderOutput();
console.log(result);
expect(result.type).to.equal(PlayerProfile);
});
});
The result variable holds this.renderer.getRenderOutput()
In the tutorial the result.type is tested like:
expect(result.type).toEqual('div');
in my case, if I log the result it is:
LOG: Object{type: function PlayerProfile() {..}, .. }
so I changed my test like:
expect(result.type).toEqual(PlayerProfile)
now it gives me this error:
Assertion Error: expected [Function: PlayerProfile] to equal [Function: withMUI]
So PlayerProfile's type is the higher order function withMUI.
PlayerProfile decorated with withMUI, using shallow rendering, only the PlayerProfile component is rendered and not it's children. So shallow rendering wouldn't work with decorated components I assume.
My question is:
Why in the tutorial result.type is expected to be a div, but in my case isn't.
How can I test a React component decorated with higher order component using shallow rendering?
You can't. First let's slightly desugar the decorator:
let PlayerProfile = withMUI(
class PlayerProfile extends React.Component {
// ...
}
);
withMUI returns a different class, so the PlayerProfile class only exists in withMUI's closure.
This is here's a simplified version:
var withMUI = function(arg){ return null };
var PlayerProfile = withMUI({functionIWantToTest: ...});
You pass the value to the function, it doesn't give it back, you don't have the value.
The solution? Hold a reference to it.
// no decorator here
class PlayerProfile extends React.Component {
// ...
}
Then we can export both the wrapped and unwrapped versions of the component:
// this must be after the class is declared, unfortunately
export default withMUI(PlayerProfile);
export let undecorated = PlayerProfile;
The normal code using this component doesn't change, but your tests will use this:
import {undecorated as PlayerProfile} from '../src/PlayerProfile';
The alternative is to mock the withMUI function to be (x) => x (the identity function). This may cause weird side effects and needs to be done from the testing side, so your tests and source could fall out of sync as decorators are added.
Not using decorators seems like the safe option here.
Use Enzyme to test higher order / decorators with Shallow
with a method called dive()
Follow this link, to see how dive works
https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/dive.md
So you can shallow the component with higher order and then dive inside.
In the above example :
const wrapper=shallow(<PlayerProfile name={name} avatar={}/>)
expect(wrapper.find("PlayerProfile").dive().find(".player-profile").length).toBe(1)
Similarly you can access the properties and test it.
You can use 'babel-plugin-remove-decorators' plugin. This solution will let you write your components normally without exporting decorated and un-decorated components.
Install the plugin first, then create a file with the following content, let us call it 'babelTestingHook.js'
require('babel/register')({
'stage': 2,
'optional': [
'es7.classProperties',
'es7.decorators',
// or Whatever configs you have
.....
],
'plugins': ['babel-plugin-remove-decorators:before']
});
and running your tests like below will ignore the decorators and you will be able to test the components normally
mocha ./tests/**/*.spec.js --require ./babelTestingHook.js --recursive
I think the above example is confusing because the decorator concept is used interchangeably with idea of a "higher order component". I generally use them in combination which will make testing/rewire/mocking easier.
I would use decorator to:
Provide props to a child component, generally to bind/listen to a flux store
Where as I would use a higher order component
to bind context in a more declarative way
The problem with rewiring is I don't think you can rewire anything that is applied outside of the exported function/class, which is the case for a decorator.
If you wanted to use a combo of decorators and higher order components you could do something like the following:
//withMui-decorator.jsx
function withMUI(ComposedComponent) {
return class withMUI extends Component {
constructor(props) {
super(props);
this.state = {
store1: ///bind here based on some getter
};
}
render() {
return <ComposedComponent {...this.props} {...this.state} {...this.context} />;
}
};
}
//higher-order.jsx
export default function(ChildComp) {
#withMui //provide store bindings
return class HOC extends Component {
static childContextTypes = {
getAvatar: PropTypes.func
};
getChildContext() {
let {store1} = this.props;
return {
getAvatar: (id) => ({ avatar: store1[id] });
};
}
}
}
//child.js
export default Child extends Component {
static contextTypes = {
getAvatar: PropTypes.func.isRequired
};
handleClick(id, e) {
let {getAvatar} = this.context;
getAvatar(`user_${id}`);
}
render() {
let buttons = [1,2,3].map((id) => {
return <button type="text" onClick={this.handleClick.bind(this, id)}>Click Me</button>
});
return <div>{buttons}</div>;
}
}
//index.jsx
import HOC from './higher-order';
import Child from './child';
let MyComponent = HOC(Child);
React.render(<MyComponent {...anyProps} />, document.body);
Then when you want to test you can easily "rewire" your stores supplied from the decorator because the decorator is inside of the exported higher order component;
//spec.js
import HOC from 'higher-order-component';
import Child from 'child';
describe('rewire the state', () => {
let mockedMuiDecorator = function withMUI(ComposedComponent) {
return class withMUI extends Component {
constructor(props) {
super(props);
this.state = {
store1: ///mock that state here to be passed as props
};
}
render() {
//....
}
}
}
HOC.__Rewire__('withMui', mockedMuiDecorator);
let MyComponent = HOC(Child);
let child = TestUtils.renderIntoDocument(
<MyComponent {...mockedProps} />
);
let childElem = React.findDOMNode(child);
let buttons = childElem.querySelectorAll('button');
it('Should render 3 buttons', () => {
expect(buttons.length).to.equal(3);
});
});
I'm pretty sure this doesn't really answer your original question but I think you are having problems reconciling when to use decorators vs.higher order components.
some good resources are here:
http://jaysoo.ca/2015/06/09/react-contexts-and-dependency-injection/
https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750
https://github.com/badsyntax/react-seed/blob/master/app/components/Menu/tests/Menu-test.jsx
https://github.com/Yomguithereal/baobab-react/blob/master/test/suites/higher-order.jsx
In my case decorators are very useful and I dont want to get rid of them (or return wrapped and unwrapped versions) im my application.
The best way to do this in my opinion is to use the babel-plugin-remove-decorators (which can be used to remove them in tests) has Qusai says, but I wrote the pre-processor differently like below:
'use strict';
var babel = require('babel-core');
module.exports = {
process: function(src, filename) {
// Ignore files other than .js, .es, .jsx or .es6
if (!babel.canCompile(filename)) {
return '';
}
if (filename.indexOf('node_modules') === -1) {
return babel.transform(src, {
filename: filename,
plugins: ['babel-plugin-remove-decorators:before']
}).code;
}
return src;
}
};
Take notice of the babel.transform call that im passing the babel-plugin-remove-decorators:before element as an array value, see: https://babeljs.io/docs/usage/options/
To hook this up with Jest (which is what I used), you can do it with settings like below in your package.json:
"jest": {
"rootDir": "./src",
"scriptPreprocessor": "../preprocessor.js",
"unmockedModulePathPatterns": [
"fbjs",
"react"
]
},
Where preprocessor.js is the name of the preprocessor.

Categories

Resources