I have setup storybook for my react project, and i have 2 stories. One that is a simple button and a larger component. I use knobs for both, it works fine of the simple button but it dose not update the larger component (The withInfo addon actaully shows the correct updated information from knobs, but the component dose not update.) This tells me there is something wrong with my component and not storybook knobs. But the component seems to work just fine in my application.
My Button story
.add('with text', () => {
const buttonText = text('Button Text', 'Hello Button');
return (
<Button onClick={action('clicked')}>{buttonText}</Button>)
})
My larger component story
import React from 'react';
import { storiesOf } from '@storybook/react';
import { text, number, file, boolean } from '@storybook/addon-knobs/react';
import blueSignImage from '../src/images/sign-3.jpg';
import SignType from '../src/components/SignType/index';
storiesOf('Sign Type', module)
.add('With current signs and requested signs', () => {
const typeId = text("Type id", "test-1234");
const availableSigns = number('Avalible Signs', 5);
const requestedSigns = number('Requested Signs', 3);
const typeName = text("Type name", "Blue Sign Image");
const isSaving = boolean("Is saving", false);
const onRequsetSigns = (id, value) => {
console.log("Got requset for " + value + " signs for id " + id);
};
return (
<SignType typeId={typeId}
availableSigns={availableSigns}
requestedSigns={requestedSigns}
typeName={typeName}
image={blueSignImage}
isSaving={isSaving}
onRequsetSigns={onRequsetSigns}
/>
)
});
The component
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Card, CardContent, CardMedia, Typography, Button, Slider, LinearProgress } from '@material-ui/core';
import SendIcon from '@material-ui/icons/Send';
import CloseIcon from '@material-ui/icons/Close';
import "./index.scss";
class SignType extends PureComponent {
constructor(props) {
super(props);
this.state = {
typeId: props.typeId,
typeName: props.typeName,
image: props.image,
availableSigns: props.availableSigns,
requestedSigns: props.requestedSigns,
isSaving: props.isSaving,
onRequsetSigns: props.onRequsetSigns,
// Local Vars
requestAmount: 5,
showOrderMore: false,
};
this.onOrderMoreClicked = this.onOrderMoreClicked.bind(this);
this.onHideOrderMore = this.onHideOrderMore.bind(this);
this.onRequestAmountChanged = this.onRequestAmountChanged.bind(this);
this.onSaveRequest = this.onSaveRequest.bind(this);
}
componentDidMount() {
}
onOrderMoreClicked() {
this.setState({ showOrderMore: true });
}
onHideOrderMore() {
this.setState({ showOrderMore: false });
}
onRequestAmountChanged(event, value) {
this.setState({ requestAmount: value });
}
onSaveRequest() {
this.setState({ showOrderMore: false });
this.state.onRequsetSigns(this.state.typeId, this.state.requestAmount);
}
render() {
const { typeId, availableSigns, requestedSigns, image, typeName, showOrderMore, isSaving, requestAmount } = this.state;
return (
<Card className="signType">
<CardMedia
className="cover"
image={image}
title="Live from space album cover"
/>
<div className="details">
<CardContent className="content">
<Typography component="h5" variant="h5">
{typeName}
</Typography>
<Typography variant="body1" color="textPrimary">
{availableSigns} signs available
{!showOrderMore && !isSaving && (
<Button color="primary" onClick={this.onOrderMoreClicked}>
Order more
</Button>)}
</Typography>
{requestedSigns > 0 && (
<Typography variant="subtitle2" color="textSecondary">
{requestedSigns} requested signs pending
</Typography>
)}
</CardContent>
{showOrderMore && (
<div className="requestSignsControl">
<Typography id={"request-slider-" + typeId} variant="h6" color="textPrimary">
Request additional signs
</Typography>
<Slider
defaultValue={5}
aria-labelledby={"request-slider-" + typeId}
valueLabelDisplay="auto"
step={1}
marks
min={1}
max={25}
onChangeCommitted={this.onRequestAmountChanged}
/>
<div>
<Button variant="contained" color="secondary" onClick={this.onHideOrderMore} className="icon">
<CloseIcon className="icon" />
</Button>
<Button variant="contained" color="primary" onClick={this.onSaveRequest} className="icon">
Send Request
<SendIcon className="icon" />
</Button>
</div>
</div>
)}
{isSaving && <LinearProgress />}
</div>
</Card>
);
}
}
SignType.propTypes = {
typeId: PropTypes.string.isRequired,
typeName: PropTypes.string.isRequired,
image: PropTypes.string.isRequired,
availableSigns: PropTypes.number.isRequired,
requestedSigns: PropTypes.number.isRequired,
isSaving: PropTypes.bool.isRequired,
onRequsetSigns: PropTypes.func.isRequired,
};
export default SignType;
My storybook config
import { configure, addDecorator } from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs';
import { withInfo } from '@storybook/addon-info';
import { withConsole } from '@storybook/addon-console';
import 'typeface-roboto';
// automatically import all files ending in *.stories.js
const req = require.context('../stories', true, /\.stories\.js$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
addDecorator(withInfo);
addDecorator(withKnobs);
addDecorator((storyFn, context) => withConsole()(storyFn)(context));
configure(loadStories, module);
my addons.js
import '@storybook/addon-actions/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-links/register';
import '@storybook/addon-actions/register';
Your component is only using props when you get the initial state, you aren't even using them during the render.
Sort of an anti-pattern - https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
Also not exactly what I would call a 'pure' component - https://reactjs.org/docs/react-api.html#reactpurecomponent
Unless you actually update your state (which you do in your handlers), you won't see an update based on those props.
That said if you wish to continue down this path you'll need a way to react to those prop changes. You could make use of the componentWillReceiveProps lifecycle and call setState here.