How do I successfully change the state in one component from another? - reactjs

I am calling setState in one component in an onClick function in another component but it says this.setState is not a function and that a component is repeatedly calling setState inside componentWill update. How do i fix this?
This is the relevant of the app.js file:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activeScreen: 'dashboard'
}
}
setActiveScreen(key) {
this.setState({
activeScreen: key
})
}
render() {
return (
<NavBar activeScreen={this.state.activeScreen} setActiveScreen={this.setActiveScreen} />
)
}
This is the relevant part of the Navbar component:
class NavBar extends Component {
render() {
return (
<li className="nav-item" key={key}>
<Link className={`nav-link ${this.props.activeScreen == i.key ? 'active' : ''}`} to={i.to} onClick={() => {this.props.setActiveScreen(i.key)}}>
{i.label}
</Link>
</li>
)
}

You have to bind the setActiveScreen function to this in the constructor of your App component.
Like this:
constructor(props) {
super(props);
this.state = {
activeScreen: 'dashboard'
}
this.setActiveScreen = this.setActiveScreen.bind(this);
}
Or, as #Nico said in the comments, use an arrow function.
Like this:
setActiveScreen = key => {
// logic
}

If you have this function in a Class based component then you can update your current function to an arrow function like below.
setActiveScreen = (key) => {
this.setState({
activeScreen: key
})
}
The thing is you are accessing this without a context being given.
Another way can be you bind this to the function. Below is the example to do it the other way inside your constructor.
constructor(props) {
super(props);
this.setActiveScreen = this.setActiveScreen.bind(this);
// Other code ....
}

First every methods how use setState has to be bind in the constructor like this :
this.setActiveScreen = this.setActiveScreen.bind(this);
Secondly, just for the good practice define your props in NavBar like this :
static propTypes = {
setActiveScreen: PropTypes.func.isRequired,
}
Thirdly, is not a good practice to use arrow function in your render. So you have to possibility :
Using partial from lodash
so it looks like this :
<Link onClick={ _.partial(this.props.setActiveScreen, i.key)}}>
Or you can define a specific method in your NavBar component called by onClick and you retrieve your key in the event object provide by default by onClick

Related

reactjs -- adding more to state when compositing a component

if I have a class
class Role extends Component {
constructor(props) {
super(props);
this.state = {
role: 'something',
value: 1
}
}
render() {
let roleStatus = [];
for (let key in this.state) {
roleStatus.push(<p key={key}>{key}: {this.state[key]}</p>)
}
return (
<div>
<div>
{roleStatus}
</div>
</div>
);
}
and then another class that uses composition (asrecommended by React doc)
class specialRole extends Component {
constructor(props) {
super(props);
// I want to add some other attributes to this.state
}
render() {
return <role />
}
}
}
I want to be able to add another attribute, let's say name, to this.state, but when I use setState to add it, the render doesn't reflect to it. I'm wondering if it's because setState is not synchronized function. If that's the case, how should I achieve what I want to do?
What you'll want to do is think of it like a parent/child composition.
The parent has the logic and passes it to the child, which then displays it.
In your case, the parent should be Role, and the child component be something that renders Role's states, for example: RoleStatus. In addition, you can have another component called SpecialRoleStatus. Note the capitalized component names, component names should be capitalized.
The composition would look something like this:
class Role extends React.Component {
constructor(props) {
super(props)
//lots of state, including special ones
}
render() {
return(
<div>
<RoleStatus normalState={this.state.normalState} />
<SpecialRoleStatus specialState={this.state.specialState} />
</div>
)
}
}
Also, setState() won't behave like you want it to because it does not set the state of any other component other than its own component.

Should I make all class variables part of state?

Say I have a class variable which I know won't change but is still needed to render the component. Something like this perhaps:
class MyComponent extends React.Component {
constructor(props) {
super(props);
let { arrayOfImages } = this.props;
this.arrayOfImages = arrayOfImages;
this.state = {
colorOfBackground: 'blue'
};
}
render() {
let images = this.arrayOfImages.map(image => {
return <img src={image.src} />
});
return (
<div style={{color: this.state.colorOfBackground}}>
{images}
</div>
);
}
}
Is it better to just make the arrayOfImages part of the state or just keep it as a class variable?
No, It is not necessary to put arrayOfImages in state if this will not change. It is better to put those value in state which can be change and want to re-execute code every time or conditionally when it change.

React.js - setState after calculation based on props

I have a component that receives images as props, performs some calculation on them, and as a result I need to update its class. But if I use setState after the calculation, I get the warning that I shouldn't update state yet... How should I restructure this?
class MyImageGallery extends React.Component {
//[Other React Code]
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
//NEED TO UPDATE STATE HERE?
}
//componentWillUpdate()? componentDidUpdate()? componentWillMount()? componentDidMount()? {
//I CAN ADD CLASS HERE USING REF, BUT THEN THE COMPONENT'S
// FIRST RENDERED WITHOUT THE CLASS AND IT'S ONLY ADDED LATER
}
render() {
return (
<div ref="galleryWrapper" className={GET FROM STATE????}
<ImageGallery
items={this.getImages()}
/>
</div>
);
} }
You should put your logic into componentWillReceiveProps (https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops) so as to do a prop transition before render occurs.
In the end what we did was run the logic in the constructor and then put the class into the initial state:
class MyImageGallery extends React.Component {
constructor(props) {
super(props);
this.getImages = this.getImages.bind(this);
this.images = this.getImages();
this.state = {smallImgsWidthClass: this.smallImgsWidthClass};
}
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
this.smallImgsWidthClass = '[calculation result]';
return this.props.images;
}
render() {
return (
<div className={this.state.smallImgsWidthClass }
<ImageGallery
items={this.images}
/>
</div>
);
}
}

Get the object clicked from the child component in React

I'd like to trigger the function handleDisplayProduct on click and pass to it the object clicked. So far it calls the function handleDisplayProduct when the list is generated for all the objects but the function is not triggered on the click event.
So how do i bind the event onclick with the Container and passing to it the element clicked?
Container
// Method to retrieve state from Stores
function getAllProductState(){
return {
products: ProductStore.getProducts(),
};
}
export default class ProductAllContainer extends Component {
constructor(props){
super(props);
this.state = getAllProductState();
}
handleDisplayProduct(data){
console.log(data);
// ProductActions.selectProduct(data)
}
render(){
const products = this.state.products;
return(
<div>
{ products.map(function(product,i){
return (
<ProductThumbnail
product = { product }
key = { i }
**onDisplayProduct = { this.handleDisplayProduct(product) }**
/>
)
},this)}
</div>
)
}
}
View
const ProductThumbnail = (props)=>{
return(
<div>
<LinksTo to="/" **onClick={props.onDisplayProduct}**>
<h1>{props.product.headline}</h1>
<img src={props.product.images[0].imagesUrls.entry[1].url} alt="Thumbnail small pic"/>
</LinksTo>
</div>
)
}
You need to bind the event listener to the react class. You can do it this way.
constructor(props){
super(props);
this.state = getAllProductState();
this.handleDisplayProduct = this.handleDisplayProduct.bind(this);
}
or alternatively using es6, you can use an arrow function instead.
handleDisplayProduct = (data) => {
console.log(data);
// ProductActions.selectProduct(data)
}
Note: Class properties are not yet part of current JavaScript standard. So the second example wouldn't work unless you add a babel-plugin-transform-class-properties babel plugin
Edit: Also #ryanjduffy pointed out a crucial mistake in your code. Refer his comment.

Why the scoping of this keyword is invalid in JSX but works in ES6? (Lexical this vs arrow functions)

I have the react component below. Why this is not defined when changeNameTwo is called?
See jsbin: http://jsbin.com/nuhedateru/edit?js,output
Then why it works in a typical ES6 Class? See jsbin: http://jsbin.com/kiyijuqiha/edit?js,output
class HelloWorldComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
name : 'yolo'
}
}
changeName = () => {
this.setState({name: 'another yolo'});
}
changeNameTwo() {
this.setState({name: 'another yolo'});
}
render() {
return (
<div>
<h1>Hello {this.props.name}</h1>
<p>Name: {this.state.name}</p>
<button onClick={this.changeName}>Change Name</button>
<button onClick={this.changeNameTwo}>Change Name 2</button>
</div>
);
}
}
React.render(
<HelloWorldComponent name="ES2015/ES6"/>,
document.getElementById('react_example')
);
One is a DOM event, the other you are directly calling. By default the context of a click event is the element that was clicked.
It is often desirable to reference the element on which the event
handler was fired, such as when using a generic handler for a set of
similar elements.
When attaching a handler function to an element using
addEventListener(), the value of this inside the handler is a
reference to the element. It is the same as the value of the
currentTarget property of the event argument that is passed to the
handler. (MDN - https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
Your methods have a default context of the instance, but only if the context is not specified (a normal function call as opposed to a .call or .apply. Thus animal.speak() will by default have the correct context.
When a function is called as a method of an object, its this is set to
the object the method is called on. (MDN - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)
To summarise, the click event sets a specific context which overrides that. As you probably know you can solve it with .call/.bind/.apply or onClick={(e) => this.changeName(e)}. Probably an implementation today wouldn't do that, but I imagine they have to keep it for compatibilities' sake.
When you use extend Component instead of React.createClass you lose autobinding feature. Binding to this is not a class itself, it’s undefined. It’s default JavaScript behavior and is quite expected.
What you can do about it
Method 1. Use Function.prototype.bind().
export default class CartItem extends React.Component {
render() {
<button onClick={this.increaseQty.bind(this)} className="button success">+</button>
}
}
Method 2. Use a function defined in the constructor.
export default class CartItem extends React.Component {
constructor(props) {
super(props);
this.increaseQty = this.increaseQty.bind(this);
}
render() {
<button onClick={this.increaseQty} className="button success">+</button>
}
}
Consider using this method over Method 1. Every time you use bind it actually creates a copy of a function. So it's much better to use it once in constructor then every time when your component calls render method
Method 3. Use an arrow function in the constructor.
export default class CartItem extends React.Component {
constructor(props) {
super(props);
this.increaseQty = () => this.increaseQty();
}
render() {
<button onClick={this.increaseQty} className="button success">+</button>
}
}
Method 4. Use an arrow function and the class properties proposal.
export default class CartItem extends React.Component {
increaseQty = () => this.increaseQty();
render() {
<button onClick={this.increaseQty} className="button success">+</button>
}
}
Class properties are not yet part of current JavaScript standard. But your are free to use them in Babel using corresponding experimental flag (stage 0 in our case).
You can see original article here

Resources