Modify react child components state for storybook - reactjs

I have a react Component that I am trying to add to a storybook. It has child components that have component state which changes the way the component displays. I would like to set that component state to show in my storybook. What is the best way to achieve this ?
class ParentComponent extends PureComponent<ParentComponentProps> {
render() {
return (
<ChildComponent />
)
}
}
class ChildComponent extends PureComponent<ChildComponentProps> {
constructor(props) {
super(props);
this.handleOnBlur = this.handleOnBlur.bind(this);
this.state = {
isValid: true
};
}
handleOnBlur() {
this.setState({
isValid: isInputValid()
});
}
render() {
return (
<TextField
placeholder="eg. 12345"
validationMessage={'not a valid input'}
isInvalid={this.state.isValid}
onBlur={this.handleOnBlur}
/>
)
}
}
And Storybook code looks like this at the moment
import React from 'react';
import { Provider } from 'react-redux';
import ParentComponent from './ParentComponent';
export default { title: 'UpdateChildComponent' };
export const FieldValidationShowing = (state) => {
const { store, updateState } = mockStore;
updateState(state);
return (
<Provider store={store}>
<ParentComponent />
</Provider>
);
};
The above code is a sample of what I am doing.

Related

Is there a way to test if a React component is within another component of a particular type?

Let's for a second assume we have 3 components.
export class ComponentA extends React.Component<IComponentAProps, IComponentAState>{
constructor(props: Readonly<IComponentAProps>) {
super(props)
}
render() {
return(
<ComponentB />
);
}
}
export class ComponentB extends React.Component<IComponentBProps, IComponentBState>{
constructor(props: Readonly<IComponentBProps>) {
super(props)
}
render() {
return(
<ComponentC />
);
}
}
export class ComponentC extends React.Component<IComponentBProps, IComponentBState>{
constructor(props: Readonly<IComponentBProps>) {
super(props)
}
render() {
return(
<ComponentA />
);
}
}
Now obviously this is going to cause an infinite loop. Is there a way for me to check in ComponentC to see whether or not it is ultimately contained within an instance of ComponentA?
I'm not sure this is what you need, but you can achieve this using a React context.
import React, { createContext, FunctionComponent, useContext, useEffect } from 'react';
export const TopDogContext = createContext<string>('');
// The top dog can never be nested!
export const TopDog: FunctionComponent = ({ children }) => {
const aboveMe = useContext(TopDogContext);
useEffect(() => {
if (aboveMe) {
setTimeout(() => alert('Yo, you can\'t nest me!'));
throw new Error('Yo, you can\'t nest me!');
}
}, [aboveMe]);
return (
<TopDogContext.Provider value={'I\'m the top dog!'}>
{ children }
</TopDogContext.Provider>
)
};
// -------
import { TopDog } from './top-dog';
function App() {
<TopDog>
<div className="App">
[... YOUR APP HERE ...]
{/* Will create an error if uncommented */}
{/* <TopDog /> */}
</div>
</TopDog>
}
Note that you can still have multiple <TopDog>, but they will never have one being the ancestor of another.

React JS - Pass Provider components methods to this.children

In React can methods be passed to {this.children} in a container consumer model. What I mean to ask is I have a provider component and I need to pass or refer the provider components methods in the child component.
export default class ContainerCompo extends React.Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
methodOne() {
//some code
}
methodTwo() {
//some code
}
render() {
return (
{this.props.children}
}
}
export default class InputComponent extends React.Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
validate() {
ContainerCompo.methodOne(param)
}
render() {
return <InputComponent />
}
// Rendering the components
<ContainerCompo>
<InputComponent containerMethods={methods of ContainerCompo}/>
</ContainerCompo>
I hope my question is clear here, please suggest
First create a react context.
import React, { Component, createContext } from 'react';
// Create's authentication context to be use anywhere in the app
const ContainerContext = createContext();
export default ContainerContext;
Then create a provider for it.
export default class ContainerProvider extends Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
methodOne() {
//some code
}
methodTwo() {
//some code
}
render() {
const { children } = this.props;
return (
<ContainerContext.Provider
value={{
container: {
methodOne: (...params) => this.methodOne(...params),
methodTwo: (...params) => this.methodTwo(...params)
}
}}
>
{children}
</ContainerContext.Provider>
)}}
Wrap your App with the provider.
import ContainerProvider from './ContainerProvider'
<ContainerProvider>
<App />
</ContainerProvider>
Then create a consumer for the context
export default function withContainer(InComponent) {
return function ContainerComponent(props) {
return (
<ContainerContext.Consumer>
{({ container }) => <InComponent {...props} container={container} />}
</ContainerContext.Consumer>
);
};
}
Then import the consumer and user in your components and you will get the methods as props
import withContainer from './ContainerConsumer'
render() {
const { container } = this.props;
return(<div />)
}
export default withContainer(YourComponent);

reactjs, how to pass props at the class level

This is an example of what im trying to do. I want to pass in props at the MyComponent class, without using <MyComponent hello={'Hello World'}/>, because I am passing MyComponent to some other libraries.
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
const { hello } = this.props;
return (
<div>{hello}</div>
);
}
}
// this line is wrong
MyComponent.props = {
hello: 'Hello World!'
}
<MyComponent />
How is react-redux connect to add this.props.state, and map any other functions to the component's props? I want to inject my custom objects into a component.
You can set default props. I think its a good practice.
import React, { Component } from 'react';
class MyComponent extends Component {
render() {
const { hello } = this.props;
return (
<div>{hello}</div>
);
}headerProp: "Header from props...",
contentProp:"Content from props..."
}
MyComponent.defaultProps = {
hello: 'Hello World!'
}
<MyComponent />
You need to create a Higher Order Component
function withMyObject(WrappedComponent,helloText) {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} hello={helloText} />;
}
};
}
Usage:
UpdatedComponent = withMyObject(ComponentToBeWrapped,"Hello");

React Higher Order Component that detects dom events that takes functional components as arg

I have a scenario where I want to create an HOC that detects mouse events (e.g. mouseenter, mouseleave) when they occur on the HOC's WrappedComponent, then pass the WrappedComponent a special prop (e.g. componentIsHovered). I got this working by using a ref callback to get the wrapped component instance, then adding event listeners to the wrapped instance in my HOC.
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
export default (WrappedComponent) => {
return class DetectHover extends Component {
constructor(props) {
super(props)
this.handleMouseEnter = this.handleMouseEnter.bind(this)
this.handleMouseLeave = this.handleMouseLeave.bind(this)
this.bindListeners = this.bindListeners.bind(this)
this.state = {componentIsHovered: false}
this.wrappedComponent = null
}
componentWillUnmount() {
if (this.wrappedComponent) {
this.wrappedComponent.removeEventListener('mouseenter', this.handleMouseEnter)
this.wrappedComponent.removeEventListener('mouseleave', this.handleMouseLeave)
}
}
handleMouseEnter() {
this.setState({componentIsHovered: true})
}
handleMouseLeave() {
this.setState({componentIsHovered: false})
}
bindListeners(wrappedComponentInstance) {
console.log('wrappedComponentInstance', wrappedComponentInstance)
if (!wrappedComponentInstance) {
return
}
this.wrappedComponent = ReactDOM.findDOMNode(wrappedComponentInstance)
this.wrappedComponent.addEventListener('mouseenter', this.handleMouseEnter)
this.wrappedComponent.addEventListener('mouseleave', this.handleMouseLeave)
}
render() {
const props = Object.assign({}, this.props, {ref: this.bindListeners})
return (
<WrappedComponent
componentIsHovered={this.state.componentIsHovered}
{...props}
/>
)
}
}
}
The problem is that this only seems to work when WrappedComponent is a class component — with functional components the ref is always null. I would just as soon place the WrappedComponent inside <div></div> tags in my HOC and carry out the event detection on that div wrapper, but the problem is that even plain div tags will style the WrappedComponent as a block element, which doesn’t work in my use case where the HOC should work on inline elements, too. Any suggestions are appreciated!
You can pass the css selector and the specific styles you need to the Higher Order Component like this:
import React, {Component} from 'react';
const Hoverable = (WrappedComponent, wrapperClass = '', hoveredStyle=
{}, unhoveredStyle={}) => {
class HoverableComponent extends Component {
constructor(props) {
super(props);
this.state = {
hovered: false,
}
}
onMouseEnter = () => {
this.setState({hovered: true});
};
onMouseLeave = () => {
this.setState({hovered: false});
};
render() {
return(
<div
className={wrapperClass}
onMouseEnter= { this.onMouseEnter }
onMouseLeave= { this.onMouseLeave }
>
<WrappedComponent
{...this.props}
hovered={this.state.hovered}
/>
</div>
);
}
}
return HoverableComponent;
};
export default Hoverable;
And use Fragment instead of div to wrap your component:
class SomeComponent extends React.Component {
render() {
return(
<Fragment>
<h1>My content</h1>
</Fragment>
)
}
And then wrap it like this
const HoverableSomeComponent = Hoverable(SomeComponent, 'css-selector');

forceUpdate is not re-rendering children

I'm using the react, redux react-router stack for my webapp. In the top level component's(the component that renders on the root path) componentDidMount I'm subscribing to the store as shown below
import NotificationsList from './components/notifier';
import React from 'react';
let Spinner = ({
isVisible,
showSpinner,
solidBackdrop
}) => (
<div style={{opacity: solidBackdrop ? 1 : 0.5}} className={"spinner " + (isVisible ? '' : 'hide')}></div>
);
export default class AppPage extends React.Component {
static contextTypes = {
store: React.PropTypes.object,
router: React.PropTypes.object
};
handleDismissNotification(notification) {
this.context.store.dispatch({
type: 'REMOVE_NOTIFICATION',
data: notification
});
}
componentDidMount() {
this.context.store.subscribe(() => this.forceUpdate());
}
render() {
let state = this.context.store.getState();
let props = {
notifications: state.notifications,
handleDismiss: this.handleDismissNotification.bind(this)
};
return (
<div className="'apppage-container">
{this.props.children}
<NotificationsList {...props} />
<Spinner isVisible={state.initialFetchInProgress || state.requestInProgress}
showSpinner={!state.initialFetchInProgress} solidBackdrop={state.initialFetchInProgress}/>
</div>
);
}
}
this.props.children here renders the component shown below
import Header from './components/header';
import React from 'react';
class ContentPage extends React.Component {
static contextTypes = {
store: React.PropTypes.object
};
render() {
let user = this.context.store.getState().user;
return <div className="content-container">
<Header user/>
</div>
}
}
export default ContentPage;
The problem is that when the first time a render happens, everything goes fine. Then when the render happens through forceUpdate, the child component is not getting re-rendered.
I think I got it. Every container component should be subscribed to the store separately. So accordingly, ContentPage should also have
componentDidMount() {
this.context.store.subscribe(() => this.forceUpdate());
}
As you replied to yourself, indeed the container component should subscribe to the store , but in addition to the subscription, it's good practice for the the container to also unsubscribe when unmounted :
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => this.forceUpdate());
}
componentWillUnmount() {
this.unsubscribe();
}

Resources