A Storybook newbie here. I have an Angular component with a FormControl, which when set to touched, might show an error (depending on the user interaction with the component itself). I've read this: https://storybook.js.org/docs/angular/writing-stories/decorators and tried adding a wrapper for the component which only contained a button. The idea was that user can click a button and thus validate the control inside the component. Didn't happen. Here is my setup:
export default {
title: 'Some title',
component: Component,
decorators: [
moduleMetadata: [...],
componentWrapperDecorator((story)=>`
<div>${story}</div>
<button (click)="doValidate"></button>
`),
]
} as Meta<Component>;
const template: Story<Component> = ( args: Component ) => {
component: Component,
props: args,
methods: {
doValidate(): void {
args.formControl.markAsTouched();
}
}
});
export const validate = template.bind({});
I concur that the method definition is out of place here. So here is the question: how do I trigger/set/mutate something inside a component from that wrapper?
Related
Using the example Storybook code at the bottom of this post, I expect to see a Primary button rendered containing the text, "Primary Button", but instead the button renders following code, verbatim:
(...args) => {
// If a user calls a compiled slot inside a template expression (#1745), it
// can mess up block tracking, so by default we disable block tracking and
// force bail out when invoking a compiled slot (indicated by the ._d flag).
// This isn't necessary if rendering a compiled `<slot>`, so we flip the
// ._d flag off when invoking the wrapped fn inside `renderSlot`.
if (renderFnWithContext._d) {
setBlockTracking(-1);
}
const prevInstance = setCurrentRenderingInstance(ctx);
const res = fn(...args);
setCurrentRenderingInstance(prevInstance);
if (renderFnWithContext._d) {
setBlockTracking(1);
}
if (true) {
devtoolsComponentUpdated(ctx);
}
return res;
}
How do I render Vue component slot content in Storybook?
The Storybook code:
// ./components/button/Button.stories.js
import AtxButton from './Button';
import ButtonTypes from './Button.types';
export default {
/* π The title prop is optional.
* See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'Button',
component: AtxButton,
argTypes: {
default: {
description: 'The default Vue slot',
control: 'text'
}
}
};
//π We create a βtemplateβ of how args map to rendering
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { AtxButton },
setup() {
//π The args will now be passed down to the template
return { args };
},
template: `
<AtxButton v-bind="args">{{ args.default }}
</AtxButton>
`
});
//π Each story then reuses that template
export const Primary = Template.bind({});
Primary.args = {
intensity: ButtonTypes.Intensity.Primary,
default: 'Primary Button'
};
export const Secondary = Template.bind({});
Secondary.args = {
intensity: ButtonTypes.Intensity.Secondary,
default: 'Secondary Button'
};
I had syntax error in my component code. The Button component is created using a render function, which includes slot content by accessing this.slots.default(). I was getting a console error claiming "this.slots.default() is not a function", so I changed it to this.slots.default, assuming it might have been a getter.
That wasn't the right solution, of course. I changed it back to this.slots.default(), and then ensured that slot content was always included in the Storybook story. Now it works perfectly!
In order to provide a minimum example, suppose I have the following two routes specified in my RouterModule.forRoot, both linked to the same component SomeComponent.
const routes: Routes = [{ path: 'one-path', component: SomeComponent },
{ path: 'another-path', component: SomeComponent }];
And in the app html template I have the two router links along with the router-outlet
<a routerLink="/one-path">someLink</a>
<a routerLink="/another-path">anotherLink</a>
...
<router-outlet> </router-outlet>
What I want is have as part of the state of SomeComponent the last router link path due to which the component was rendered on the page. The purpose is to display a slightly different view depending on which link was used.
export class SomeComponent implements OnInit {
let route_path = ... //this should either be 'one-path' or 'another-path' depending on which link was clicked
}
How can this be achieved?
You need to subscribe to the actived route url observable. when component is initialize, route_path will either be 'one-path' or 'another-path' depending on which link was clicked or if you are on the home route.
export class SomeComponent implements OnInit {
route_path ='';
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.url.subscribe((url) => {
this.route_path = (url.length > 0 )? url[url.length-1].path: '';
});
}
}
I have created a simple datepicker component based on react-dates and implemented as per documentation at https://github.com/airbnb/react-dates
The datepicker seems to be working however the calendar styling is completely broken when clicking on the datepicker field
The datepicker code:
import React, { Component } from 'react';
import moment from 'moment';
import 'react-dates/initialize';
import { SingleDatePicker } from 'react-dates';
import 'react-dates/lib/css/_datepicker.css';
export default class DatePicker extends Component {
constructor(props) {
super(props);
this.state = {
selectedStartDate: moment(),
calendarSelectedStartDate: false
}
}
onDateChange = (selectedStartDate) => {
this.setState(() => ({ selectedStartDate }));
};
onFocusChange = ({ focused }) => {
this.setState(() => ({ calendarSelectedStartDate: focused }));
};
render() {
return (
<SingleDatePicker
date={this.state.selectedStartDate}
onDateChange={this.onDateChange}
focused={this.state.calendarSelectedStartDate}
onFocusChange={this.onFocusChange}
numberOfMonths={1}
isOutsideRange={() => false}
/>
)
}
}
Implementation is just call to :
<DatePicker />
outside of any parent html tags that could affect it.
The styling looks like this:
Ok i found an answer for this problem, so:
Are you trying to render the calendar inside another element that is not always visible, right?
Well, if you are doing that, in the parent component you must have an state like "isOpen" or something like that. Then, when you call the Picker component, must be like:
{isOpen && <DatePicker />}
I spent like two hours searching this solution. I hope that make sense for you.
When I am debugging using the browser console console or react dev tools, they always refer to my components as "Constructor" and I would like to change that. See the example below:
I would have hoped to set defined names for my components so they would show up as "MyComponent" for example. This would help on pages where there are many components and one of them is throwing a warning that I would like to solve.
Add the displayName property to your components:
var Component = React.createClass({
displayName: 'MyComponent',
...
});
You don't need to set the displayName property to your components actually. It is automatically set.
But there are certain cases you need to consider.
1.You put your component in a separate file and content of that file is -
export default React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
In this case displayName will be undefined.
2.You assigned the component in a variable.
var TestComponent = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
Now displayName is set to TestComponent.
See corresponding jsx conversion for more clarification.
var TestComponent = React.createClass({
displayName: "TestComponent",
render: function render() {
return React.createElement(
"h1",
null,
"Hello, ",
this.props.name
);
}
});
3.If you are using es6 e.g
class HelloMessage extends React.Component {
render() {
return <div > Hello component {
} < /div>;
}
}
In this case displayName will be name of class you have given i.e HelloMessage.
I've got a component that builds search/sort filters that can be selected. I want the selected state of those filters to be tracked in redux so that the search builder can subscribe and see when they change and update appropriately. the thing I'm trying to figure out how to do (in a way that doesn't feel weird) is populate the filter objects into the state. Eg, right now in the <Search /> component I have something like:
<OptionPicker
group={'searchFilters'}
options={{word: 'price', active: true},
{word: 'distance', active: false},
{word: 'clowns', active: false}}
/>
So how to get those props into state to be used without triggering multiple element renders. I'm also rendering the app on the server as well, so for the initial attachment render, the state already has the options.
In the OptionPicker component I've got:
class OptionPicker extends Component {
constructor(props) {
super(props)
if (!props.optionstate) {
this.props.addOptionState(props)
}
}
render() {
return {this.props.optionstate.word.map((word) => <Option ... />)}
}
}
function mapStateToProps(state, props) {
return {
optionstate: state.optionstate[props.group],
};
}
function mapDispatchToProps(dispatch) {
return {
addOptionState: (props) => {
dispatch(addOptionState(props));
},
optionToggled: (group, word) => {
dispatch(updateOptionState(group, word));
}
};
}
export default connect(mapStateToProps,mapDispatchToProps)(OptionGroup);
This kinda works, but there exists a time when render is called before the redux state has been populated, which throws an error. I could guard against that, but none of this feels "right". Shouldn't that prop always be there? Is there a pattern for this that I'm missing?
I agree with you in that the prop should always be there. The pattern I use for this is to set up an initial state and to pass it to the reducer function:
export const INITIAL_STATE = {
optionstate: { /* all filters are present but deactivated */ }
};
export default function (state = INITIAL_STATE, action) {
// reduce new actions into the state
};