React - set state using button and pass it as props - javascript

I want to use the 'compare' button to toggle the compare state to true or false.
Next I want to pass this compare state to pivot as props.
I am literally using the same code as in the react documentation when looking at the Toggle class. https://facebook.github.io/react/docs/handling-events.html
The only thing I changed is the name isToggleOn to compare.
When looking at the console client side I get following error every time the component renders:
modules.js?hash=5bd264489058b9a37cb27e36f529f99e13f95b78:3941 Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.`
My code is following:
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = { compare: true };
this.handleClick = this.handleClick.bind(this);
}
handleClick(button) {
if (button === 'compare') {
this.setState(prevState => ({
compare: !prevState.compare,
}));
}
}
render() {
return (
<Grid>
<div className="starter-template">
<h1>This is the dashboard page.</h1>
<p className="lead">
Use this document as a way to quickly start any new project.<br />{' '}
All you get is this text and a mostly barebones HTML document.
</p>
</div>
<ButtonToolbar>
<button onClick={this.handleClick('compare')}>
{this.state.compare ? 'AGGREGATE' : 'COMPARE'}
</button>
</ButtonToolbar>
<PivotTable
ready={this.props.isReady}
data={this.props.gapData}
compare={this.state.compare}
/>
</Grid>
);
}
}
export default (DashboardContainer = createContainer(() => {
// Do all your reactive data access in this method.
// Note that this subscription will get cleaned up when your component is unmounted
const handle = Meteor.subscribe('weekly-dashboard');
return {
isReady: handle.ready(),
gapData: WeeklyDashboard.find({}).fetch(),
};
}, Dashboard));
Any advice on how to fix this?

The reason is this line
<button onClick={this.handleClick('compare')}>
This will call the handleClick function while executing render function. You can fix by:
<button onClick={() => this.handleClick('compare')}>
Or
const handleBtnClick = () => this.handleClick('compare');
...
<button onClick={this.handleBtnClick}>
...
I prefer the latter

Related

Enzyme - Testing onClick of child component

I very new to testing and I am currently testing React components with jest/enzyme.
I have a parent component
ParentComp.jsx
export class ParentComp extends React.Component {
super(props);
this.state = {
selectedTemplate: "",
disabled: true
};
return <Modal
header={<h2>Header</h2>}
visible={true}
footer={<span>
<Button
disabled={false}
onClick={ () => {
sessionStorage.setItem('id', this.state.id);
}}
>
Continue
</Button>
}>
<h1> My modal </h1>
</Modal>
}
How would I go about testing the onClick and making sure the sessionStorage is tested?
I've already tried:
ParentComp.spec.jsx
const wrapper = shallow(<ParentComp/>);
wrapper.find(Modal).first().props().footer.find(Button).simulate('click')
jest.spyOn(window.localStorage.__proto__, 'setItem');
window.localStorage.__proto__.setItem = jest.fn();
// assertions as usual:
expect(localStorage.setItem).toHaveBeenCalled();
expect(global.sessionStorage.getItem).toBecalledWith('id',1)
}
I thankfully don't get any actual errors, however, my sessionStorage line in ParentComp is apparently not being covered. How would I go about covering this line?
I have an idea to call directly prop onClick directly in this case instead of simulating of click. As long as you can find your <Button /> in your footer then call its prop of onClick. Let's try:
// You can console.log to see what is the correct to select the right one
// I'm not sure below route is correct :)
const yourButton = wrapper.find(Modal).first().props().footer.props.children;
yourButton.props.onClick();

Is this a valid way of updating state that depends on prevState?

I am following along with a video tutorial on using React. The presenter is currently detailing how to add a toggle button to a UI. They said to give it a go first before seeing how they do it, so I implemented it myself. My implementation was a little different to theirs, just the handler was different; but it does seem to work.
Can anyone with more experience using React tell me, is my toggleSideDrawerHandler wrong in some way? Or is it a valid shorter way of setting the state that depends on a previous state?
My implementation:
//Layout.js
class Layout extends Component {
state = {
showSideDrawer: false
};
toggleSideDrawerHandler = prevState => {
let newState = !prevState.showSideDrawer;
this.setState({ showSideDrawer: newState });
};
closeSideDrawerHandler = () => {
this.setState({ showSideDrawer: false });
};
render() {
return (
<Fragment>
<Toolbar drawerToggleClicked={this.toggleSideDrawerHandler} />
<SideDrawer
open={this.state.showSideDrawer}
close={this.closeSideDrawerHandler}
/>
<main className={styles.Content}>{this.props.children}</main>
</Fragment>
);
}
}
//Toolbar.js
const toolbar = props => (
<header className={styles.Toolbar}>
<DrawerToggle clicked={props.drawerToggleClicked} />
<div className={styles.Logo}>
<Logo />
</div>
<nav className={styles.DesktopOnly}>
<NavItems />
</nav>
</header>
);
Tutorial implementation:
toggleSideDrawerHandler = () => {
this.setState(prevState => {
return { showSideDrawer: !prevState.showSideDrawer };
});
};
Your solution works, but I guess in the part, where you call the toggleSideDrawerHandler you probably call it like
() => this.toggleSideDrawerHandler(this.state)
right?
If not, can you please paste the rest of your code (especially the calling part) to see where you get the prevState from?
This works, because you pass the old state to the method.
I would personally prefer the tutorials implementation, because it takes care of dependencies and the "user" (the dev using it) doesn't need to know anything about the expected data.
With the second implementation all you need to do is call the function and not think about getting and passing the old state to it.
Update after adding the rest of the code:
I think the reason, why it works is because the default value for your parameter is the one passed by the event by default, which is an event object.
If you use prevState.showSideDrawer you are calling an unknown element on this event object, that will be null.
Now if you use !prevState.showSideDrawer, you are actually defining it as !null (inverted null/false), which will be true.
This is why it probably works.
Maybe try to toggle your code twice, by showing and hiding it again.
Showing it will probably work, but hiding it again will not.
This is why the other code is correct.
You should stick to the tutorial implementation. There is no point in passing component state to the children and then from them back to the parents. Your state should be only in one place (in this case in Layout).
Child components should be only given access to the information they need which in this case is just showSideDrawer.
You are using this:
toggleSideDrawerHandler = prevState => {
let newState = !prevState.showSideDrawer;
this.setState({ showSideDrawer: newState });
};
This is a conventional way to update state in react, where we are defining the function and updating state inside. Though you are using term prevState but it doesn't holds any value of components states. When you call toggleSideDrawerHandler method you have to pass value and prevState will hold that value. The other case as tutorial is using:
toggleSideDrawerHandler = () => {
this.setState(prevState => {
return { showSideDrawer: !prevState.showSideDrawer };
});
};
This is called functional setStae way of updating state. In this function is used in setState methods first argument. So prevState will have a value equal to all the states in the component.Check the example below to understand the difference between two:
// Example stateless functional component
const SFC = props => (
<div>{props.label}</div>
);
// Example class component
class Thingy extends React.Component {
constructor() {
super();
this.state = {
temp: [],
};
}
componentDidMount(){
this.setState({temp: this.state.temp.concat('a')})
this.setState({temp: this.state.temp.concat('b')})
this.setState({temp: this.state.temp.concat('c')})
this.setState({temp: this.state.temp.concat('d')})
this.setState(prevState => ({temp: prevState.temp.concat('e')}))
this.setState(prevState => ({temp: prevState.temp.concat('f')}))
this.setState(prevState => ({temp: prevState.temp.concat('g')}))
}
render() {
const {title} = this.props;
const {temp} = this.state;
return (
<div>
<div>{title}</div>
<SFC label="I'm the SFC inside the Thingy" />
{ temp.map(value => ( <div>Concating {value}</div> )) }
</div>
);
}
}
// Render it
ReactDOM.render(
<Thingy title="I'm the thingy" />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
So depending on requirement you will use one of the two ways to update the state.

How to setState from within a mapped item within React component

I'm trying to change the state of a component that is part of a mapped array of objects from a json file. I want to ONLY change the item containing the clicked button and none of the others.
I've been attempting to set a property (projectID) with an onClick and while I can get it to toggle one element of state (expanded or not expanded), it does it to ALL the returned results. So I've been trying to get the projectId (set in the data) and use that to set a conditional. But I can't seem to get projectId to update with the click. I briefly played around with context but I think there's something simpler I'm missing. I've attempted it within the onClick (as shown) and from within onViewChange, but that didn't seem to work as I can't access item.id from outside the mapped item.
I'm using a conditional based on a prop handed down from a couple levels up to set the category, showing only the objects I want. That part seems to be working. So I removed my expanded state as it's not necessary to the issue here.
import React from 'react';
import projects from '../data/projects.json';
class ProjectCard extends React.Component {
constructor(props) {
super(props);
this.state={
projects,
expanded: false,
projectId: -1
}
}
onViewChange = (e) => {
this.setState({
expanded: !this.state.expanded
});
console.log(this.state.projectId)
};
render() {
return (
<React.Fragment>
{this.state.projects.map((item) => {
if (!this.state.expanded && item.category === this.props.category) {
return (
<div key={item.id} className="project-card">
<div className="previewImg" style={{backgroundImage: `url(${item.previewImg}` }} ></div>
<div className="copy">
<h2>{item.title}</h2>
<p>{item.quickDesc}</p>
</div>
<div className="footer">
{item.tools.map((tool) => {
return (
<div key={tool} className="tools" style={{backgroundImage: `url(${tool}` }}></div>
);
}
)}
<button onClick={() => this.onViewChange({projectId: item.id})} className="btn float-right"><i className="fas fa-play"></i></button>
</div>
</div>
);
}
}
)}
</React.Fragment>
);
};
};
export default ProjectCard;
I've set a console log to tell me if projectId changes and it always comes back as my default value (-1). The pasted version is the only one that doesn't throw errors with regards to undefined values/objects, but still no changes. If I can get projectId to change based on the item.id from the clicked button, I think I can figure out the conditional and take it from there.
You aren't actually setting the state with the new projectId in your click handler. First step is just simply passing the item's ID to the click handler, not an object:
onClick={() => this.onViewChange(item.id)}
And second part is to actually use that argument in your click handler:
onViewChange = (id) => {
this.setState({
expanded: !this.state.expanded,
projectId: id
});
};
Also, setState() is asynchronous so you can't do what you did and console.log your state on the next line and expect it to have changed. Log the state at the top of your render function to see when it changes (after state/props change, render is called). Or another option is to use the optional second argument of setState, which is a callback that's executed with the new state:
this.setState({id: 5}, () => console.log(this.state.id)) <-- id will be 5

React - a way to unify, if many components have the same piece of code in lifecycle methods

I have multiple component with similar piece code in lifecycle methods and some similarity in state variables. Is there a way to unify them, by inheriting from one parent or something like that?
constructor(props) {
super(props);
this.state = {
//state properties similar in all components, getting from redux
//state properties specific for this component
}
// same code in many components
}
componentWillMount() {
// same code in many components
// code specific for this component
}
Can I use children methods and props in parent "wrapper" ? Can I change component state from parent ?
You can create Higher Order Component (HOC) for that, basically, you just write component with your same lifecycle method which is repeating, and then in render() function, call this.props.children function with any HOC internal state arguments you want, you can pass the whole state and a setState function as well, so you can change the HOC's state inside the underlying component.
For example:
class HOC extends React.Component {
constructor(props) {
super(props);
state = {
someState: 'foo',
};
}
componentWillMount() {
console.log('i mounted!')
}
render() {
return (
<div>
{this.props.children({ state: this.state, setState: this.setState })}
</div>
)
}
}
const SomeComponent = () =>
<HOC>
{({ state, setState }) => (
<div>
<span>someState value: </span>
<input
value={state.someState}
onChange={e => setState({ someState: e.target.value})}
/>
</div>
)}
</HOC>
You can also do really cool and interesting things with it, like connecting a slice of your redux state whenever you need it:
import { connect } from 'react-redux';
const ProfileState = connect(
state => ({ profile: state.profile }),
null,
)(({
profile,
children
}) => (
<div>
{children({ profile })}
</div>
));
const ProfilePage = () => (
<div>
Your name is:
<ProfileState>
{({ profile }) => (
<span>{profile.name}</span>
)}
</ProfileState>
</div>
);
Here is the full documentation on this technique.
You could create HOCs (Higher Order Components) in that case. It can look like this:
/*
A Higher Order Component is a function,
that takes a Component as Input and returns another Component.
Every Component that gets wrapped by this HOC
will receive `exampleProp`,`handleEvent`,
plus all other props that get passed in.
*/
function WithCommonLogic(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
example: ''
}
}
componentWillMount() {
...
// Same code in many components.
}
callback = () => {
/* Enhanced components can access this callback
via a prop called `handleEvent`
and thereby alter the state of their wrapper. */
this.setState({example: 'some val'})
}
render() {
return <WrappedComponent
exampleProp={this.state.example}
handleEvent={this.callback}
{...this.props}
/>
}
}
// You use it like this:
const EnhancedComponent1 = WithCommonLogic(SomeComponent);
const EnhancedComponent2 = WithCommonLogic(SomeOtherComponent);
Now all the shared logic goes into that HOC, which then wrap all your different components you want to share it with.
See the React Docs for further reading.

Push state updates directly to conditionally rendered React component?

I'm building a React app and have a tab section, where clicking on a tab will render a specific component.
First, my parent component:
class Interface extends Component {
constructor(props) {
super(props);
this.chooseTab = this.chooseTab.bind(this);
this.state = {
current: 'inventory',
inventory: [],
skills: [],
friends: [],
notifications: {}
};
}
chooseTab(tabID) {
this.setState({ current: tabID });
chooseComponent(tabID) {
if (tabID === 'skills') return Skills;
else if (tabID === 'inventory') return Inventory;
else if (tabID === 'friends') return FriendsList;
}
render() {
const tabID = this.state.current;
const CustomComponent = this.chooseComponent(tabID);
return (
<div className='column' id='interface'>
<div className='row' id='tabs'>
<ActiveTab
current={this.state.current}
tabID='skills'
chooseTab={this.chooseTab}
/>
<ActiveTab
current={this.state.current}
tabID='inventory'
chooseTab={this.chooseTab}
/>
<ActiveTab
current={this.state.current}
tabID='friends'
chooseTab={this.chooseTab}
/>
</div>
<TabBody>
<CustomComponent
data={this.state[tabID]}
notifications={this.state.notifications}
/>
</TabBody>
</div>
);
}
}
Which renders three ActiveTab's and one TabBody:
const ActiveTab = (props) => {
const isActive = props.tabID === props.current ? 'active' : 'inactive';
return (
<button
className={`active-tab ${isActive}`}
onClick={() => props.chooseTab(props.tabID)}
>{props.tabID}
</button>
);
};
const TabBody = (props) => {
return (
<div className='tab-body'>
{props.children}
</div>
);
};
This works fine, and it's clearly an intended way of handling this issue. However, I'd like to be able to move the notifications state object into my FriendsList component (since it's unique to friends) and also trigger a setState in it from another component even if FriendsList is not the component currently rendered by the TabBody (i.e., unmounted).
I'm currently triggering remote state changes using a globally available actions closure where a specific action and setState is defined in the ComponentWillMount() lifecycle method of the target element, and it's executed from whatever component is activating the remote state change. I've left those out of Interface for brevity.
How would you handle this? Is my only option to leave notifications in Interface, define actions there, and let React handle passing props down? Or is there a way to build my tab components and conditional rendering so I can trigger state changes from a separate component to a non-displayed component in one of the tabs, i.e move notifications and its corresponding action to FriendsList?
I've passed through a problem similar than yours weeks ago, if you are not decided to adopts some state manager like Redux, MobX or even Flux I think you should pass props down to their child's.

Categories