Trying to edit data using react - javascript

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;

Related

ReactJS Classes How to pass State from Child Component to its Parent?

I will call the LI tags like this (li) so they are not made into bullet points for his question
Hi I am trying to send a Child component to its Parent in ReactJS.
I tried many things I managed to send the child component state back up to its Parent props but when the page renders I have a couple of (li) tags which I want the update to update with it for example like:
(li) hard coded text (/li)
(li) old text (/li)
(li) update prop (/li)
(li) update prop etc (/li)
but instead the update deletes all previous code so it looks like:
(li) update prop deleted all previous li's (/li)
Hope that made sense here is my code
Parent Component
import React from 'react';
import { generateId, getNewExpirationTime } from '../../util/utilities';
import Thought from '../Thought/Thought.js'
import AddThoughtForm from '../AddThoughtForm/AddThoughtForm.js'
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
thoughts: [{
id: generateId(),
text: 'This is a place for your passing thoughts.',
expiresAt: getNewExpirationTime()
},
{
id: generateId(),
text: "They'll be removed after 15 seconds.",
expiresAt: getNewExpirationTime()
}]
};
this.addThought = this.addThought.bind(this);
this.removeThought = this.removeThought.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.componentDidUpdate = this.componentDidUpdate.bind(this);
}
addThought(thought) {
console.log("LOOK")
console.log(thought)
console.log("DONE")
console.log(this.state.thoughts);
this.state.thoughts.push(thought);
this.setState(prevState => ({
...prevState,
thoughts: [thought]
}))
console.log("passed over")
console.log(this.state.thoughts);
}
removeThought(selected) {
//alert('selected');
let updatedThoughts = this.state.thoughts.filter((thought) => selected.id !== thought.id);
return this.setState({ thoughts: updatedThoughts })
}
render() {
return (
<div className="App">
<header>
<h1>Passing Thoughts</h1>
</header>
<main>
<AddThoughtForm
addThought={this.addThought}
thoughts={this.state.thoughts} />
<ul className="thoughts">
{(this.state.thoughts) && this.state.thoughts.map((thought, index) => (
<Thought
key={thought.id}
thought={thought}
removeThought={this.removeThought} />
))}
</ul>
</main>
</div>
);
}
}
Still on the Parent Component my .addThought(arg) is where the action is. This is where I'm sending the Child AddThoughtForm state object into it. By inside of .addThought() I am doing this:
addThought(thought) {
console.log("LOOK")
console.log(thought)
console.log("DONE")
console.log(this.state.thoughts);
this.state.thoughts.push(thought);
this.setState(prevState => ({
...prevState,
thoughts: [thought]
}))
console.log("passed over")
console.log(this.state.thoughts);
}
What happens is when I pass it over my previous State of my parent is deleted and replaced by this new information from my child component. How do I stop that? I want to only add this new Information to the previous info that the Parent state already have. here is the state from my parent:
constructor(props) {
super(props);
this.state = {
thoughts: [{
id: generateId(),
text: 'This is a place for your passing thoughts.',
expiresAt: getNewExpirationTime()
},
{
id: generateId(),
text: "They'll be removed after 15 seconds.",
expiresAt: getNewExpirationTime()
}]
};
this.addThought = this.addThought.bind(this);
this.removeThought = this.removeThought.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.componentDidUpdate = this.componentDidUpdate.bind(this);
}
Now over to the Child Component
AddThoughtForm.js
import React from 'react';
import { generateId, getNewExpirationTime } from '../../util/utilities';
class AddThoughtForm extends React.Component {
constructor(props) {
super(props);
this.state = {
ideas: [this.props.thoughts] // I can take off []
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleTextChange(event) {
const { value } = event.target
this.setState(prevState => {
let thoughts = Object.assign({}, prevState.ideas); // creating copy of state variable
thoughts.id = generateId();
thoughts.text = value; // update the name property, assign a new value
thoughts.expiresAt = getNewExpirationTime();
return { thoughts }; // return new object
})
console.log(this.state.ideas)
}
handleSubmit(event) {
event.preventDefault();
this.props.addThought(this.state.thoughts)
alert(this.state.ideas);
}
render() {
return (
<form className="AddThoughtForm" onSubmit={this.handleSubmit}>
<input
type="text"
aria-label="What's on your mind?"
placeholder="What's on your mind?"
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Add" />
</form>
)
}
}
export default AddThoughtForm;
On my .handleTextChange(event) is where I am linking it with my input tag in render so what I am doing what ever Is typed into it I want entered I want it be passed to my Parent Component. well It is passed over but it over-writes the old info every time the old (li) and it is then all just a new li being rendered. Any ideas on how I can fix all of this?
handleTextChange(event) {
const { value } = event.target
console.log(value)
this.setState(prevState => {
let thoughts = Object.assign({}, prevState.ideas); // creating copy of state variable
thoughts.id = generateId();
thoughts.text = value; // update the name property, assign a new value
thoughts.expiresAt = getNewExpirationTime();
return { thoughts }; // return new object
})
console.log(this.state.ideas)
}
I managed to fix it
just had to add on the Parent Component a thoughts: [...prevState, thought] as I was overwritting the old thoughts with the new incoming thought
Like this:
In the method, .addThought()
this.setState(prevState => ({
...prevState,
thoughts: [...prevState.thoughts, thought]
}))

React: Binding array to dynamically added fields

Lets say I have a class with a state level array
ElementsClass = React.createClass({
getInitialState: function() {
return {
elements: []
}
},
addElement: function() {
var element = {
name: ""
};
},
render() {
return (
{this.state.elements.map(function (element, i) {
return <input value={element.name} />
}
)}
)
}
The idea being that I can dynamically add to the elements array and have a new input field appearing.
How do I bind the data so that I am able to change the value in the input field and have that reflect automatically in the correct element in the elements array?
To dynamically sync your inputs with your state array you can use someting called linkState from the react-catalyst package. Once you've installed it with npm you can use it in the following way:
//need to import
import Catalyst from 'react-catalyst';
ElementsClass = React.createClass({
// mixin the linkedstate component
mixins : [Catalyst.LinkedStateMixin],
getInitialState: function() {
return {
elements: []
}
},
addElement: function() {
var element = {
name: ""
};
//add to elements array
this.state.elements.push(element);
//let react know to rerender necessary parts
this.setState({
elements : this.state.elements
});
},
render() {
return (
{this.state.elements.map(function (element, i) {
//use the linkState method
return <input valueLink={this.linkState('elements.'+i+'.name')} />
}
)}
)
}
The reason we need the react-catalyst package is that natively React's valueLink will only link top level state items, in your case elements. Obviously this isn't particularily useful but thankfully it's a fairly easy problem to solve.
Note: for iterated items like your element inputs, you need to provide a unique key. Something like the following (might need modifying to be more specific):
{this.state.elements.map(function (element, i) {
//use the linkState method
return <input valueLink={this.linkState('elements.'+i+'.name')} key={'elinput' + i} />
}
)}
This doesn't have any outward effect on your app, it's mostly to help react target the element internally.
If you want to do this with just ES5 and React, one solution would be this:
var ElementsClass = React.createClass({
getInitialState: function() {
return {
elements: []
}
},
createElement: function(){
var element = {
name: ''
};
this.setState({elements: this.state.elements.concat(element)});
},
updateElement: function(pos, event) {
var value = event.target.value;
var updatedElements = this.state.elements.map(function(element, i){
if (i === pos){
return {name: value};
}
return element;
});
this.setState({elements: updatedElements});
},
render: function() {
console.log(this.state.elements);
return (
<div>
{this.state.elements.map(function (element, i) {
var boundClick = this.updateElement.bind(this, i);
return <input key={i} onKeyUp={boundClick}/>
}.bind(this))}
<button onClick={this.createElement}>Add Element</button>
</div>
)
}
});
React.render(<ElementsClass />, document.getElementById('app'));
You want to treat component state as immutable, so you don't want to call a mutating method like push on elements.
These situations are handled easily with custom links packages.
State and Forms in React, Part 3: Handling the Complex State
import Link from 'valuelink';
// linked inputs will be deprecated, thus we need to use custom wrappers
import { Input } from 'valueLink/tags.jsx'
const ElementsClass = React.createClass({
getInitialState: function() {
return {
elements: []
}
},
render() {
// Take link to the element
const elementsLink = Link.state( this, 'elements' );
return (
<div>
{ elementsLink.map( ( elementLink, i ) => (
<Input key={ i } valueLink={ elementLink.at( 'name' ) } />
))}
<button onClick={ elementsLink.push({ name : '' })}>
Add Elements
</button>
</div>
);
}
});

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.

Is it correct to manipulate state via props?

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.

React refs do not update between render

So I have this component
var LineItemRowsWrapper = React.createClass({
current_lineitem_count: 0,
getAjaxData: function(){
var lineitem_data = [];
for(var i = 0; i < this.current_lineitem_count; i++){
var data = this.refs['lineitem_'+i].getAjaxData();
lineitem_data.push(data)
}
return lineitem_data;
},
getLineitems: function(){
var self = this;
var lineitem_components = [];
this.current_lineitem_count = 0;
if(this.props.shoot){
var preview = this.props.preview;
var lineitems = this.props.shoot.get_lineitems();
lineitem_components = lineitems.map(function (item, index) {
var ref_str = 'lineitem_'+self.current_lineitem_count;
self.current_lineitem_count++;
return (
<LineItemRow item={item} key={index} ref={ref_str} preview={preview} onChange={self.props.onChange} />
)
});
}
return lineitem_components;
},
render: function() {
var lineitems = this.getLineitems();
return (
<div>
{lineitems}
</div>
)
}
})
the first time lineitems are rendered the refs work like expected. But if I add a lineitem to this.props.shoot the refs object of this component does not change.
So for example say I had an array of 3 lineitems
[i1,i2,i3]
this.refs would be
{lineitem_0:{}, lineitem_1:{}, lineitem_2:{}}
and when I update my lineitem array to be
[i1,i2,i3,i4]
this.refs does not change, it will still be
{lineitem_0:{}, lineitem_1:{}, lineitem_2:{}}
why doesn't the refs object update between renders?
The LineItemRow components update properly so I know its not something wrong on that front. Any insights would be much appreciated!
____Edit____ (requested to add more code for context)
var DocumentContent = React.createClass({
contextTypes: {
router: React.PropTypes.func.isRequired
},
getParams: function(){
return this.context.router.getCurrentParams()
},
getInitialState: function() {
return {
shoot: ShootStore.get_shoot(this.getParams().shoot_id),
}
},
componentWillMount: function() {
ShootStore.bind( 'change', this.onStoreUpdate );
},
componentWillUnmount: function() {
ShootStore.unbind( 'change', this.onStoreUpdate );
},
onStoreUpdate: function(){
this.setState(this.getInitialState());
},
addLineItem: function() {
ShootActions.create_lineitem(this.state.shoot.id);
},
update_shoot_timeout: null,
update_shoot:function(){
var self = this;
window.clearTimeout(this.update_shoot_timeout)
this.update_shoot_timeout = window.setTimeout(function(){
var lineitem_data = self.refs.lineitems.getAjaxData()
if(self.props.shoot){
ShootActions.update_shoot(self.state.shoot.id, lineitem_data )
}
}, 500)
},
render: function() {
var shoot = this.state.shoot;
return (
<div className='document__content'>
<div className='row'>
<div className='document__expenses'>
<h3 className='lineitem__title'> Expenses </h3>
<LineItemRowsWrapper shoot={shoot} onChange={this.update_shoot} ref='lineitems'/>
</div>
<button onClick={this.addLineItem} className="btn-small btn-positive">
+ Add Expense
</button>
</div>
);
}
})
Under the section "Caution" in the react documentation about refs https://facebook.github.io/react/docs/more-about-refs.html
"Never access refs inside of any component's render method - or while
any component's render method is even running anywhere in the call
stack."
Which is exactly what you're doing.
Instead you should store state about the component in this.state or properties of the component in this.props
Remove all your refs and iteration to read the data.
The onchange handler you pass all the way down to the LineItem component should be called and passed only the data that changes. (The single LineItem data)
This is then handled back at the component with the state handling (DocumentContent).
Create an action ShootActions.updateLineItem() that updates the relevant line item in the store, which then emits the change and everything renders again.

Categories

Resources