Is it correct to manipulate state via props? - javascript

Using React is it correct to manipulate state in a parent element by manipulating props in a child element (example)? Or is the correct way to return an object and explicitly setState?
Example below just to show where I'm manipulating variable:
const Parent = React.createClass({
getInitialState: function() {
person: {name: 'john', age: 47},
},
render: function() {
<div>
<Child person = {this.state.person} />
</div>
}
});
const Child = React.createClass({
render: function() {
let person = this.props.person;
person = {name: 'john doe', age:30};
return(<div> Person {person} </div>);
}
});

Yes, you should take the state of your 'Parent' class and pass it in as props to your 'Child' class.
Your example is correct, except I am not sure why you want to do
person = {...}
again in the Child's render class.
Maybe you meant
const Child = React.createClass({
render: function() {
let person = this.props.person;
return(<div> Person {person} </div>);
}
});
This is in docs: ...props are immutable: they are passed from the parent and are "owned" by the parent.

Related

Having trouble incrementing the age when the button is clicked

New to react and I am having a hard time trying to increment the age of the person when the button is clicked.
EventComp.js code:
import React, { Component } from 'react';
class EventComp extends Component{
constructor(props) {
super(props);
this.state = {
clicks: 0,
show: true,
};
}
IncrementItem = () => {
this.setState({clicks: this.state.clicks + 1});
}
render(){
return(
<div>
<div class = "person">
<h1 class="person_h1">
{this.props.LastName},
{this.props.FirstName}</h1>
<p>Age: {this.props.Age}</p>
</div>
<button onClick = {this.IncrementItem}>Celebrate</button>
</div>
)
}
}
export default EventComp;
App.js code:
import React from 'react';
import EventComp from './components/EventComp';
import './App.css';
function App() {
return (
<div class ="main">
<div>
<EventComp FirstName="Jane "
LastName=" Doe " Age = {45}/>
</div>
<div>
<EventComp FirstName = "Joe " LastName = " Doe " Age={88}/>
</div>
<div>
</div>
</div>
);
}
export default App;
I'm just confused on how it's supposed to work. There are no errors, but when the button is clicked nothing happens.
…having a hard time trying to increment the age…
A common way to do this sort of thing is to have a parent component and a child component (App and EventComp) with responsibilities separated such that the parent manages the state (keeps track of the age) and the child (EventComp) is a "dumb" component that just renders whatever information it's given.
You're already doing most of this by passing the "person" information as props:
<EventComp FirstName="Joe" LastName="Doe" Age={88} /> // 👍
If you keep track of the age as state in App, then changing "age" will re-render, passing the updated age to EventComp:
function App () {
// create a state variable and a setter function for updating its value
const [age, setAge] = React.useState(88);
// pass age as a prop:
return (
<EventComp FirstName="Joe" LastName="Doe" Age={age} />
);
}
How does age get updated? By calling setAge. Here's the same example from above with the addition of a button that increments age:
function App () {
const [age, setAge] = React.useState(88);
return (
<div>
<button onClick={() => setAge(age + 1)}>Increment Age</button>
<EventComp FirstName="Joe" LastName="Doe" Age={age} />
</div>
);
}
Each click will trigger a re-render showing the new age.
But you want the button in EventComp not in App. How do we connect the button in EventComp to the state in App? By passing the update function as a prop:
function App () {
const [age, setAge] = React.useState(88);
return (
<EventComp
FirstName="Joe"
LastName="Doe"
Age={age}
onIncrementAge={() => setAge(age + 1)} // <= pass the age incrementing function as a prop
/>
);
}
function EventComp (props) {
return (
<div>
<div class = "person">
<h1 class="person_h1">
{props.LastName},
{props.FirstName}</h1>
<p>Age: {props.Age}</p>
</div>
<button onClick={props.onIncrementAge}> // <= onClick calls the function provided via props
Celebrate
</button>
</div>
)
}
Using this pattern, there's a single source of truth about the value of age, and EventComp doesn't need to know or care where it comes from.
Extending this to handle more than one person:
The examples above demonstrate the fundamental pattern but don't address the need to handle more than one person. Suppose you have a list of people:
const people = [
{
firstName: 'Jane',
lastName: 'Doe',
age: 45
},
{
firstName: 'Joe',
lastName: 'Doe',
age: 88
},
]
You can iterate over this list, rendering a component for each person:
function App () {
// initial state with 2 people
const [people, setPeople] = React.useState([
{ firstName: 'Jane', lastName: 'Doe', age: 45 },
{ firstName: 'Joe', lastName: 'Doe', age: 88 },
]);
return (
people.map((person) => ( // render an EventComp for each person
<EventComp
FirstName={person.firstName}
LastName={person.lastName}
age={person.age}
key={person.firstName} // key is a react thing. ignore for now.
/>
));
)
}
Updating a person's age becomes slightly more complicated, but it's still fairly straightforward. We need a function that does the following:
Find the person by index (their position in the list)
Update their age
Update state with the new info
const incrementAge = index => {
const person = people[index];
person.age += 1;
setPeople([...people]);
}
Pass this new function to EventComp and you're done.
function App () {
// initial state with 2 people
const [people, setPeople] = React.useState([
{ firstName: 'Jane', lastName: 'Doe', age: 45 },
{ firstName: 'Joe', lastName: 'Doe', age: 88 },
]);
const incrementAge = index => {
const person = people[index];
person.age += 1;
setPeople([...people]); // spread to a new array so react recognizes it as new
}
return (
people.map((person, index) => ( // render an EventComp for each person
<EventComp
FirstName={person.firstName}
LastName={person.lastName}
age={person.age}
onIncrementAge={() => incrementAge(index)} // <= indicate which person via index
key={person.firstName} // key is a react thing. ignore for now.
/>
));
)
}
Notice that we've added the index argument to the map call:
people.map((person, index) => {
And we're using it when we invoke the click handler:
onIncrementAge={() => incrementAge(index)}
Voila.
You could also pass the person as a single prop:
<EventComp
person={person}
onIncrementAge={() => incrementAge(index)}
/>
// use props.person.FirstName, etc. in EventComp
Hope this helps.
You're not updating age in your IncrementItem function. Instead, you're updating clicks. The reason you're not seeing anything happening is because clicks is not rendered anywhere. If you add a log statement and log clicked, you will see it being updated.
But if you are interested in incrementing age, you first need to add state to age:
this.state = {
clicks: 0,
show: true,
age: this.props.Age
};
As a note, in the future, props are usually lowercase and I would suggest having that prop be passed in as "age" instead.
The second thing you should do in your increment function is to update the age rather than clicks. If you add a console statement, you will see that the function is actually being invoked. You will also notice that age is being incremented rather than clicks because we are interested in that piece of state. The function should also be defined where the state is defined to retain context.
Note: functions in react are lowercased, components are capitalized. Functions also tend to use a "handle" naming convention, in this case, the function should be named something like "handleIncrementAge"
IncrementItem = () => {
this.setState({clicks: this.state.clicks + 1,
age: this.state.age + 1
});
}
Lastly, in your render, because it seems like you are interested in updating state, you want to display that piece of state.
<p>Increment Age: {this.state.age}</p>
*class attributes should be changed to className because class is a reserved keyword in React. Not doing so will result in your styles not being rendered.
I have attached a CodeSandbox with your code and the recommended changes: Code Sandbox
Updating State with React Class Components

Trying to edit data using react

I am very confused about a problem I am trying to solve. I am able to render data on the page using React, but I want to be able to change the values when an edit button is clicked. I am prompting the user for new data when the button is clicked and I want the new data to replace the old data on the page. The editItem function is where I am attempting to do this. Any suggestions on how to solve this would be extremely helpful.
const NewProduct = React.createClass({
render: function() {
return (
<section>
<div>Name: {this.props.name}</div>
<div>Price: {this.props.price}</div>
<div>Category: {this.props.category}</div>
<button className="deleteButton" onClick={this.deleteItem}>Delete</button>
<button className="editButton" onClick={this.editItem}>Edit</button>
</section>
);
},
deleteItem: function() {
console.log(this.props.id);
this.props.product.destroy();
},
editItem: function() {
var name = prompt('What should the new name be?');
<div>Name: {this.name.value}</div>
}
});
export default NewProduct;
You can make use of the local state and life cycle method to achieve this.
const NewProduct = React.createClass({
constructor(props) {
super(props);
const entity = {
name: '',
price : '',
category: '',
};
this.state = {
entity : entity
};
}
componentWillReceiveProps(newProps){
const entity = newProps.props;
const entity = {
name: entity.name,
price : entity.price,
category: entity.category,
};
this.setState({
entity: entity
});
}
render: function() {
const entity = this.state.entity;
return (
<section>
<div>Name: {entity.name}</div>
<div>Price: {entity.price}</div>
<div>Category: {entity.category}</div>
<button className="deleteButton" onClick={this.deleteItem}>Delete</button>
<button className="editButton" onClick={this.editItem}>Edit</button>
</section>
);
},
deleteItem: function() {
console.log(this.props.id);
this.props.product.destroy();
},
editItem: function() {
var name = prompt('What should the new name be?');
// here you need to just update the state based on the promt values or use a callback function passing the values and update the state.
}
});
export default NewProduct;
There are two approaches you can take for this.
Update props
You are currently always rendering the name based on the this.props.name value. If you'd like to update this, you'd have to notify your parent component when the value should update, and then have the parent component pass the new prop value back down to the child.
Example
editItem: function() {
var name = prompt('What should the new name be?');
/*
handleNewName would be a function passed in as a prop from the parent component.
It will then be on the parent component to update the name, and pass in
the updated name as a prop, which will trigger a re-render and update the
value on the child NewProduct component.
*/
this.props.handleNewName(name);
}
Introduce state
The second way you can handle this is to introduce local state into this component.
Example
const NewProduct = React.createClass({
getInitialState: function () {
// Get initial value from props.
return {
name: this.props.name
}
},
render: function() {
return (
<section>
<div>Name: {this.state.name}</div>
<div>Price: {this.props.price}</div>
<div>Category: {this.props.category}</div>
<button className="deleteButton" onClick={this.deleteItem}>Delete</button>
<button className="editButton" onClick={this.editItem}>Edit</button>
</section>
);
},
deleteItem: function() {
this.props.product.destroy();
},
editItem: function() {
var name = prompt('What should the new name be?');
this.setState({ name })
}
});
export default NewProduct;

Communicate between parent and child component in React.js

Just meet a problem about communication between parent and child component in React.
Child
var Child = React.createClass({
getInitialState() {
return {
childState: this.props.state,
};
},
changeState(e) {
this.setState({e.target.id});
},
render: function () {
return (
<button id='1' onClick={this.changeState}>1</button>
<button id='2' onClick={this.changeState}>2</button>
);
},
});
Parent
var Parent = React.createClass({
getInitialState() {
return {
parentState: '1',
};
},
render: function () {
return (
<Child state=this.state.parentState />
);
},
});
So right now Parent will pass the initial state '1' to child, I want the child component can change both child and parent's state. For example, when click the second button, both child and parent state are set to '2'. How can I achieve this? Thank guys!
to achieve this behaviour you need to communicate with your parent component through props.
var Child = React.createClass({
render: function () {
return (
<button id='1' onClick={this.props.changeState}>1</button>
<button id='2' onClick={this.props.changeState}>2</button>
);
},
});
var Parent = React.createClass({
getInitialState() {
return {
parentState: '1',
};
},
changeState: function(){
this.setState({parentState: e.target.id});
},
render: function () {
return (
<Child changeState={this.changeState} state={this.state.parentState} />
);
},
});
The idea behind this is, that you are passing down the changeState function from your parent Component to your child Component as a function and make it accessible through props. That way when you call the prop function in your child Component - the function in the parent Component will get executed.
That way you also don't need to keep a second "separate" state in your child component because the state of the parent component will be available in both and have the same value in both.
Pardon me for any typing mistakes - I am at the office but since you had no answer yet I wanted to help.

Giving a state to parent component in React

I know you can pass down states and props in React from a parent component to a child component, but is there any way to do this the opposite way?
For example:
Given some child component:
var Child = React.createClass({
getInitialState: function(){
return {
data: ''
};
},
componentDidMount: function(){
this.setState({data: 'something'});
},
render: function() {
return (
<div>
...
</div>
);
}
});
and given some parent component:
var Parent = React.createClass({
render: function() {
return (
<div>
<Child />
...
</div>
);
}
});
Is there any way for me to give Parent the value of the state data from Child?
No.
But yes. But really no.
You cannot "pass" anything from a child to a parent in React. However, there are two solutions you can use to simulate such a passing.
1) pass a callback from the parent to the child
var Parent = React.createClass({
getInitialState: function() {
return {
names: []
};
},
addName: function(name) {
this.setState({
names: this.state.names.push(name)
});
},
render: function() {
return (
<Child
addName={this.addName}
/>
);
}
});
var Child = React.createClass({
props: {
addName: React.PropTypes.func.isRequired
},
handleAddName: function(event) {
// This is a mock
event.preventDefault();
var name = event.target.value;
this.props.addName(name);
},
render: function() {
return (
...
onClick={this.handleAddName}
...
);
}
});
The second option is to have a top-level state by using a Flux-style action/store system, such as Reflux or Redux. These basically do the same thing as the above, but are more abstract and make doing so on much larger applications very easy.
One way to do this is through a 'render props' pattern I was recently introduced to. Remember, this.props.children is really just a way for React to pass things down to a component.
For your example:
var Parent = React.createClass({
render: function() {
return (
<div>
<Child>
{(childState) => {
// render other 'grandchildren' here
}}
</Child>
</div>
);
}
});
And then in <Child> render method:
var Child = React.createClass({
propTypes: {
children: React.PropTypes.func.isRequired
},
// etc
render () {
return this.props.children(this.state);
}
});
This is probably best suited for cases where the <Child /> is responsible for doing something but doesn't really care much at all about the children that would be rendered in its place. The example the react training guys used was for a component that would fetch from Github APIs, but allow the parent to really control what / if anything was rendered with those results.

How to access component methods from “outside” in ReactJS?

Why can’t I access the component methods from “outside” in ReactJS? Why is it not possible and is there any way to solve it?
Consider the code:
var Parent = React.createClass({
render: function() {
var child = <Child />;
return (
<div>
{child.someMethod()} // expect "bar", got a "not a function" error.
</div>
);
}
});
var Child = React.createClass({
render: function() {
return (
<div>
foo
</div>
);
},
someMethod: function() {
return 'bar';
}
});
React.renderComponent(<Parent />, document.body);
React provides an interface for what you are trying to do via the ref attribute. Assign a component a ref, and its current attribute will be your custom component:
class Parent extends React.Class {
constructor(props) {
this._child = React.createRef();
}
componentDidMount() {
console.log(this._child.current.someMethod()); // Prints 'bar'
}
render() {
return (
<div>
<Child ref={this._child} />
</div>
);
}
}
Note: This will only work if the child component is declared as a class, as per documentation found here: https://facebook.github.io/react/docs/refs-and-the-dom.html#adding-a-ref-to-a-class-component
Update 2019-04-01: Changed example to use a class and createRef per latest React docs.
Update 2016-09-19: Changed example to use ref callback per guidance from the ref String attribute docs.
If you want to call functions on components from outside React, you can call them on the return value of renderComponent:
var Child = React.createClass({…});
var myChild = React.renderComponent(Child);
myChild.someMethod();
The only way to get a handle to a React Component instance outside of React is by storing the return value of React.renderComponent. Source.
Alternatively, if the method on Child is truly static (not a product of current props, state) you can define it on statics and then access it as you would a static class method. For example:
var Child = React.createClass({
statics: {
someMethod: function() {
return 'bar';
}
},
// ...
});
console.log(Child.someMethod()) // bar
As of React 16.3 React.createRef can be used, (use ref.current to access)
var ref = React.createRef()
var parent = (
<div>
<Child ref={ref} />
<button onClick={e=>console.log(ref.current)}
</div>
);
React.renderComponent(parent, document.body)
Since React 0.12 the API is slightly changed. The valid code to initialize myChild would be the following:
var Child = React.createClass({…});
var myChild = React.render(React.createElement(Child, {}), mountNode);
myChild.someMethod();
You could also do it like this, not sure if it's a good plan :D
class Parent extends Component {
handleClick() {
if (this._getAlert !== null) {
this._getAlert()
}
}
render() {
return (
<div>
<Child>
{(getAlert, childScope) => (
<span> {!this._getAlert ? this._getAlert = getAlert.bind(childScope) : null}</span>
)}
</Child>
<button onClick={() => this.handleClick()}> Click me</button>
</div>
);
}
}
class Child extends Component {
constructor() {
super();
this.state = { count: 0 }
}
getAlert() {
alert(`Child function called state: ${this.state.count}`);
this.setState({ count: this.state.count + 1 });
}
render() {
return this.props.children(this.getAlert, this)
}
}
As mentioned in some of the comments, ReactDOM.render no longer returns the component instance. You can pass a ref callback in when rendering the root of the component to get the instance, like so:
// React code (jsx)
function MyWidget(el, refCb) {
ReactDOM.render(<MyComponent ref={refCb} />, el);
}
export default MyWidget;
and:
// vanilla javascript code
var global_widget_instance;
MyApp.MyWidget(document.getElementById('my_container'), function(widget) {
global_widget_instance = widget;
});
global_widget_instance.myCoolMethod();
Another way so easy:
function outside:
function funx(functionEvents, params) {
console.log("events of funx function: ", functionEvents);
console.log("this of component: ", this);
console.log("params: ", params);
thisFunction.persist();
}
Bind it:
constructor(props) {
super(props);
this.state = {};
this.funxBinded = funx.bind(this);
}
}
Please see complete tutorial here: How to use "this" of a React Component from outside?

Categories

Resources