How to force rendering if the global variable value changes? - javascript

#File 1:
let ticketEnable = false;
export default class SupportTicketMain extends Component {
constructor () {
super();
}
render () {
let expandIcon = <DownIcon/>;
if (this.state.ticketDetailExpanded) {
expandIcon = <UpIcon/>;
}
return (
<Section className="ticketMain" primary={true}>
<TicketHeader expanded={ticketEnable}/>
</Section>
);
}
};
export function setTicketEnablement (value) {
ticketEnable = value;
}
#file 2:
import { setTicketEnablement } from file1;
export default class SupportTicketTabs extends Component {
constructor () {
super();
this.state = {
ticketDetailExpanded: false
};
this._expandClick = this._expandClick.bind(this);
}
_expandClick() {
this.setState({ticketDetailExpanded: !this.state.ticketDetailExpanded});
setTicketEnablement(this.state.ticketDetailExpanded);
}
render () {
let expandIcon = <DownIcon/>;
if (this.state.ticketDetailExpanded) {
expandIcon = <UpIcon/>;
}
return (
<Button className="expander" type="icon" onClick={this._expandClick}>
{expandIcon}
</Button>
);
}
};
Here a button click in supportTicketTabs class of #file2 will update global variable in #File1 , but SupportTicketMain render doesn't update if the global variable value changes! please guide me on this.

ticketEnable should be a prop passed into SupportTicketMain. The component that wraps both SupportTicketTabs and SupportTicketMain should be handing down a callback as a prop that modifies the value of ticketEnable (toggleTicketEnable) and the value of ticketEnable
class Main extends Component {
constructor (props) {
super(props);
this.onToggleTicketEnable = this.onToggleTicketEnable.bind(this);
this.state = {
ticketEnabled: false;
};
}
onToggleTicketEnable() {
this.setState({ ticketEnabled: !this.state.ticketEnabled });
}
render () {
return (
<App centered={false}>
<SupportTicketMain ticketEnable={this.ticketEnabled} />
<SupportTicketTabs onToggleTicketEnable={this.onToggleTicketEnable}/>
</App>
);
}
}

Related

How to test logic in ComponenWillMount using Enzyme/Jest

I am beginner in react unit testing with enzyme/jest,
I want to test my logic inside componentWillMount method.
I want to test based on my context object whether redirect happens or not based on my business logic
class ActivateSF extends Component {
constructor(props) {
super(props);
this.className = 'ActivateSF.js'
this.state = {
messages: null,
}
}
render() {
return (
<SDPActivateInterstitialUI
context={this.props.context}
messages={this.state.messages}
/>
);
}
componentWillMount() {
let context = this.props.context
if(!context.userInfo){
return this.callIdentify(context)
}
let externalLP = ExternalLandingPageUtil.getExternalLandingPageUrl(context);
if (externalLP) {
window.location.replace(`${externalLP}`);
return;
}
if (context.userInfo)
{
console.log("user identified prior to activation flow")
if (UserInfoUtil.isSubsribedUser(context))
{
window.location = '/ac'
}
else
{
this.callPaymentProcess(context)
}
}
}
You can try beforeEach to mount and in your test you call .unmount and perform your test on it.
beforeEach(() => {
const myComponent= mount(<MyComponent myprop1={...} />);
});
describe('<MyComponent/>', () => {
it('actually unmounts', () => {
...
...
myComponent.unmount();
... Do unmount tests here
});
});
Example straight from the enzyme docs: https://airbnb.io/enzyme/docs/api/ShallowWrapper/unmount.html
import PropTypes from 'prop-types';
import sinon from 'sinon';
const spy = sinon.spy();
class Foo extends React.Component {
constructor(props) {
super(props);
this.componentWillUnmount = spy;
}
render() {
const { id } = this.props;
return (
<div className={id}>
{id}
</div>
);
}
}
Foo.propTypes = {
id: PropTypes.string.isRequired,
};
const wrapper = shallow(<Foo id="foo" />);
expect(spy).to.have.property('callCount', 0);
wrapper.unmount();
expect(spy).to.have.property('callCount', 1);

Getting the ref from a dynamic component when using Redux, React and react-router-dom 4.x

I have the following class
class MatchBox extends React.Component {
constructor(props) {
super(props);
this.countdownHandler = null;
this.showBlocker = true;
this.start = this.start.bind(this);
}
start() {
...
}
render() {
...
return (
<div style={ styles.mainContainer } className="fluid-container">
...
</div>
);
}
};
function mapStateToProps(state) {
...
}
function matchDispatchToProps(dispatch) {
...
}
export default withRouter(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(MatchBox));
which is used in this class
class GameBox extends React.Component {
constructor(props) {
super(props);
...
}
render() {
var mainElement = null;
switch(this.props.mainElement.element) {
case 'SEARCHING': mainElement = <SearchingBox gameType={ this.props.gameType }/>; break;
case 'MATCH': mainElement = <MatchBox ref='matchBox'/>; break;
default: mainElement = <SearchingBox/>;
}
return (
<div style={ styles.mainContainer } className="fluid-container">
{ mainElement }
</div>
);
}
};
function mapStateToProps(state) {
...
}
function matchDispatchToProps(dispatch) {
...
}
export default withRouter(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(GameBox));
And I can't get the ref of the object MatchBox. I tried with this.refs.matchBox and is null, also tried getting directly from ref(ref={(r) => { // r is null } }) and I don't know what to try anymore.
I'm using react-router-dom 4 and I don't know if function withRouter affect the outcome component.
It's not pretty, but I think this is the solution. withRouter exposes the child ref via a wrappedComponentRef callback, which gets us to the connect hoc. That exposes its child ref via getWrappedInstance if you pass the withRef attribute as you did. So you just have to combine both of those.
class GameBox extends React.Component {
matchboxRefCallback = (connectHOC) => {
this.matchboxRef = connectHOC ? connectHOC.getWrappedInstance() : null;
}
render() {
return <MatchBox wrappedComponentRef={this.matchboxRefCallback}/>;
}
}
Much more cleaner solution would be to create a HOC. which will forward the ref to actual component
const matchBoxHOC = (WrappedComponent) => {
class MatchBoxHOC extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return <WrappedComponent {...rest} ref={forwardRef} />;
}
}
const WithRouterMatchBoxHOC = withRouter(MatchBoxHOC, { withRef: true });
return React.forwardRef((props, ref) => {
return <WithRouterMatchBoxHOC {...props} forwardRef={ref} />;
});
}
Call is like
export default matchBoxHOC(connect(mapStateToProps, matchDispatchToProps, null, { withRef: true })(MatchBox));

How to reduce the repeat object.chain in react-native?

I have react-native code with mobx like below, as you guys see, I need reference this.props.store.user.avatar to get deep object value from props, I don't wanna use the long syntax repeatedly, I know I can let it be a instance variable in constructor for example2, but I find that's a anti-pattern by the posts, it actually occurs some side-effect by my experiment cause the constructor execute only once when components initial, so I use the third way for example3, as you like, I create function in components and return the value by the long syntax, that's what can I do in my best, but I don't like this way, it looks not elegant, so anyone has better suggest or solution/way?
Example1 : My question
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
}
render() {
return(
<BasicInfo
avatar = { this.props.store.user.avatar }
displayName = { this.props.store.user.displayName }
location = { this.props.store.user.location }
/>
)
}
}
Example2 : Anti-Pattern
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
this.avatar = this.props.store.user.avatar
this.displayName = this.props.store.user.displayName
this.location = this.props.store.user.location
}
render() {
return(
<BasicInfo
avatar = { this.avatar }
displayName = { this.displayName }
location = { this.location }
/>
)
}
}
Example3 : Anti-Pattern
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
this.state = {
avatar: this.props.store.user.avatar,
displayName: his.props.store.user.displayName,
location: this.props.store.user.location,
}
}
render() {
return(
<BasicInfo
avatar = { this.state.avatar }
displayName = { this.state.displayName }
location = { this.state.location }
/>
)
}
}
Example 4 : It work, but exist better way?
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
}
avatar(){ return this.props.store.user.avatar}
displayName(){ return this.props.store.user.displayName}
location(){ return this.props.store.user.location}
render() {
return(
<BasicInfo
avatar = { this.avatar() }
displayName = { this.displayName() }
location = { this.location() }
/>
)
}
}
Example 5 : This is a good way, but it not work on callback
#observer
export default class Profile extends Component {
constructor(props) {
super(props);
}
callback = () => {
Actions.aboutMeEdit({ avatar: user.avatar })
// there are not work
}
render() {
const { user } = this.props.store;
return(
<BasicInfo
avatar = { user.avatar }
displayName = { user.displayName }
location = { user.location }
callback = { this.callback }
/>
)
}
}
You could do it like this to reduce the repetition:
render() {
const { user } = this.props.store;
return(
<ScrollView>
<BasicInfo
avatar = { user.avatar }
displayName = { user.displayName }
location = { user.location }
/>
)
}
Use spread:
render() {
const { user } = this.props.store;
return (
<ScrollView>
<BasicInfo {...user} callback={this.callback.bind(this)} />
</ScrollView>
)
}

React how to properly call a component function inside render()

I'm building a sidebar menu skeleton using ReactJs and need to understand the way to call a function inside ReactJs render() function.
The code is below:
import React from 'react';
var menuData = require("./data/admin.menu.json");
class SidebarMenu extends React.Component {
constructor(props) {
super(props);
this.state = { expanded: true };
this.buildItem = this.buildItem.bind(this);
};
buildItem(title, ref, icon) {
return (
<div className={"item" + this.props.key}>
<a href={ref}>{title}<i className={"fa " + icon} /></a>
</div>
);
};
render() {
return (
<div>
{
menuData.forEach(function (item) {
this.buildItem(item.title, item.ref, item.icon);
if (item.hasOwnProperty("submenu")) {
item.submenu.forEach(function (subitem) {
this.buildItem(subitem.title, subitem.ref, subitem.icon);
});
}
})
}
</div>
);
};
}
export default SidebarMenu;
The given code shows the following error:
Uncaught TypeError: Cannot read property 'buildItem' of undefined
How to properly call a function that will render data inside the ReactJs function ?
The this referenced when you try to call this.buildItem() refers to the anonymous function's context, not your React component.
By using Arrow Functions instead of functions defined using the function keyword inside the render() method, you can use this to reference the React component and its methods as desired.
Alternatively, you can use (function () { ... }).bind(this) to achieve the same result. But this is more tedious and the use of arrow functions is preferred.
Below is one solution, using fat arrow, AKA arrow functions:
import React from 'react';
var menuData = require("./data/admin.menu.json");
class SidebarMenu extends React.Component {
constructor(props)
{
super(props);
this.state = { expanded: true };
this.buildItem = this.buildItem.bind(this);
};
buildItem(title, ref, icon) {
return (
<div className={"item" + this.props.key}>
<a href={ref}>{title}<i className={"fa " + item.icon}/></a>
</div>
);
};
render() {
return (
<div>
{
menuData.forEach(item => {
this.buildItem(item.title, item.ref, item.icon);
if (item.hasOwnProperty("submenu"))
{
item.submenu.forEach(subitem => {
this.buildItem(subitem.title, subitem.ref, subitem.icon);
});
}
})
}
</div>
);
};
}
export default SidebarMenu;
Another solution would be:
render() {
return (
<div>
{
menuData.forEach(function (item) {
this.buildItem(item.title, item.ref, item.icon);
if (item.hasOwnProperty("submenu"))
{
item.submenu.forEach(function (subitem) {
this.buildItem(subitem.title, subitem.ref, subitem.icon);
}.bind(this));
}
}.bind(this))
}
</div>
);
};
}
But, IMO, the best solution would be to refactor the code using a component:
import React, {PropTypes, Component} from 'react';
const menuData = require('./data/admin.menu.json');
function MenuItem({key, ref, title, icon, submenu}) {
return (
<div className={`item${key}`}>
<a href={ref}>{title}<i className={`fa ${icon}`}/></a>
if (submenu) {
submenu.map((subitem) => <MenuItem {...subitem} />)
}
</div>
);
}
MenuItem.propTypes = {
key: PropTypes.string,
title: PropTypes.string,
ref: PropTypes.string,
icon: PropTypes.string,
submenu: PropTypes.array,
};
class SidebarMenu extends Component {
constructor(props) {
super(props);
this.state = {
expanded: true,
};
}
render() {
return (
<div>
{
menuData.map((subitem) => <MenuItem {...subitem} />)
}
</div>
);
}
}
export default SidebarMenu;
You can add this line:
render() {
let that = this
return (
and then instead of this.buildItem use that.buildItem or you may need that.buildItem.bind(that)

React setState between components ES6

I have a very simple application where I am trying to update the state of a parent component from a child component as follows:
import React from '../../../../../../../dependencies/node_modules/react';
import ReactDOM from '../../../../../../../dependencies/node_modules/react-dom';
class CalendarMain extends React.Component {
constructor() {
super();
}
handleClick() {
this.props.handleStateClick("State Changed");
}
render() {
return (
<div>
<div className="calendar">
{this.props.checkIn}
<button onClick={ this.handleClick.bind(this) }>Click Me</button>
</div>
</div>
)
}
}
class CalendarApp extends React.Component {
constructor() {
super();
this.state = {
checkIn: "Check-in",
checkOut: "Check-out",
dateSelected: false
};
}
handleStateClick( newState ) {
this.setState({
checkIn: newState
});
}
render() {
return (
<div>
<CalendarMain
checkIn = { this.state.checkIn }
handleStateClick = { this.handleStateClick.bind(this) }
/>
</div>
);
}
}
The error I am receiving is this.setState is not a function and I can't work out why. Any help would be much appreciated!
this is not auto-bound in ES6 style syntax.
Either:
Bind in constructor like so: this.func = this.func.bind(this)
Use arrow function syntax for the function in question like so: func = () => {};
More here: https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding
Use () => lambda to provide lexical scoping and bind correct value of this within the method handleStateClick():
handleStateClick = () => {
this.setState({
checkIn: newState
});
}

Categories