Update Passed Data from Parent to Child After Axios Request React - reactjs

From the title itself. I am fairly new to react and would like to know
how would you update the already "passed" data from parent to child. I have an alert component that will display the error message depending on the data acquired after an Axios request.
Parent
..
this.state = {
formContact: {
fullname: '',
contact:'',
email: '',
message: ''
},
formAlert: { alertMessage: 'default'}
};
handleClick() {
let rm = this;
axios({
method: 'post',
url: 'submit',
data: {
form: this.state.formContact
}
})
.then(function (response) {
let data = response.data.data;
rm.setState({
formAlert: { alertMessage: 'test' }
});
}).catch(function (response) {
//handle error
console.log(response);
});
}
render() {
return (
<div className="row">
<Alert data={this.state.formAlert} />
</div>
);
}
}
Child
class Alert extends Component {
constructor(props) {
super(props);
console.log(props);
// Holds the form state and input boxes
this.state = {
formError: {
icon: '',
header: '',
message: '',
errorType: 'errormsg'
}
};
}
render() {
return (
<div className={'style-msg ' + this.state.formError.errorType}>
<div className="sb-msg"><i className="icon-thumbs-up"></i>
<strong>Well done!</strong>
{this.state.formError.message}
</div>
<button type="button" className="close" data-dismiss="alert" aria-hidden="true">×</button>
</div>
);
}
}
It seems I cannot update the formAlert.alertMessage to "test" and pass the new data "test" to the child.
Any help would greatly be appreciated. Thanks

As you have passed data to Alert component,
<Alert data={this.state.formAlert} />
But in Alert component, you have never used that data.
I think instead of this,
{this.state.formError.message}
you should use this,
{this.props.data.alertMessage}
Update
In order to set props to state do this,
formError: {
icon: '',
header: '',
message: props.data.alertMessage,
errorType: 'errormsg'
}
Now you can use,
{this.state.formError.message}
When data changes after first render your Alert component's state will not get new data, for this you need componentDidUpdate method,
componentDidUpdate(prevProps, prevState) {
if (prevProps.data.alertMessage !== this.props.data.alertMessage) {
let formError = {...this.state.formError};
formError.message = this.props.data.alertMessage
this.setState({formError},()=>console.log(this.state.formError.message))
}
}

Your Child Component should use the data props to show the alert.
class Alert extends Component {
constructor(props) {
super(props);
console.log(props);
// Holds the form state and input boxes
this.state = {
formError: {
icon: '',
header: '',
message: '',
errorType: 'errormsg'
}
};
}
render() {
return (
<div className={'style-msg ' + this.state.formError.errorType}>
<div className="sb-msg"><i className="icon-thumbs-up"></i>
<strong>Well done!</strong>
{this.props.data.alertMessage}
</div>
<button type="button" className="close" data-dismiss="alert" aria-hidden="true">×</button>
</div>
);
}
}

Related

How can I pass my state to this class component in ReactJs/.Net?

I followed a tutorial to make an Asp.Net Core MVC app with a ReactJs front end (https://reactjs.net/tutorials/aspnetcore.html). I've been adding additional functionality to the project after completing the tutorial to see what else I can do with it.
My <AddColourForm> component assembles a <Colour> object and posts it off via an XmlHttpRequest to my API controller which in turn persists it to local storage. The submitUrl for the controller is passed in through the props. This works.
I've since tried to add the <SoftDeleteColour> component to each colourNode rendered in the <ColourList> which I intend to behave in more-or-less the same manner as the <AddColourForm> component. Each colourNode rendered in the <ColourList> has it's own delete button and I want the <SoftDeleteColour> component to take the colour.id from the selected colour and pass it to the softDelete action on the API controller so that can be handled in turn (it'll find the colour by id and append a DateDeleted to it, the API will then ignore any colours where DateDeleted != null) and the <SoftDeleteColour> component can then call loadColoursFromServer() to bring back the refreshed list from the storage. I want <SoftDeleteColour> to receive the softDeleteUrl from props in the same way that the add form does.
When I run the project in debug the softDeleteUrl is coming in as undefined and when I inspect the props in the browser it doesn't contain the softDeleteUrl. Also the "colour" is undefined so I feel like my <SoftDeleteColour> component isn't receiving the props or state. I'm new to React and struggling conceptually with props/state binding a little bit so I suspect this is the source of my problem.
How can I pass the softDeleteUrl and the properties of the colour from the <ColourList> that I am selecting for deletion to the <SoftDeleteColour> component? Do I need to call something like <SoftDeleteColour HandleDeletion=this.HandleDeletion.bind(this) /> or something?
class ColourDisplay extends React.Component {
constructor(props) {
super(props);
this.state = { data: [] };
this.handleColourSubmit = this.handleColourSubmit.bind(this);
}
loadColoursFromServer() {
const xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = () => {
const data = JSON.parse(xhr.responseText);
this.setState({ data: data });
};
xhr.send();
}
handleColourSubmit(colour) {
const data = new FormData();
data.append('name', colour.name);
data.append('brand', colour.brand);
data.append('expiry', colour.expiry);
data.append('serialNumber', colour.serialNumber);
const xhr = new XMLHttpRequest();
xhr.open('post', this.props.submitUrl, true);
xhr.onload = () => this.loadColoursFromServer();
xhr.send(data);
}
componentDidMount() {
this.loadColoursFromServer();
}
render() {
return (
<div className="colourDisplay">
<h1>Colours</h1>
<ColourList data={this.state.data}/>
<AddColourForm onColourSubmit={this.handleColourSubmit}/>
</div>
);
}
}
class ColourList extends React.Component {
render() {
const colourNodes = this.props.data.map(colour => (
<Colour name={colour.name} key={colour.id}>
<div>Brand: {colour.brand}</div>
<div>Exp: {colour.expiry}</div>
<div>Serial #: {colour.serialNumber}</div>
<div>Date Added: {colour.dateAdded}</div>
<SoftDeleteColour />
</Colour>
));
return <div className="colourList">{colourNodes}</div>;
}
}
class SoftDeleteColour extends React.Component {
constructor(props) {
super(props)
this.state = {
colour: this.props.colour
};
}
HandleDeletion(colour) {
var xhr = new XMLHttpRequest();
var url = this.props.softDeleteUrl + colour.id;
xhr.open('DELETE', url, true);
xhr.onreadystatechange = () => {
if (xhr.status == 204) {
this.loadColoursFromServer();
}
}
xhr.send();
}
render() {
return (
<button onClick={() => { this.HandleDeletion(this.state.colour); }}>Delete</button>
)
}
}
class AddColourForm extends React.Component {
constructor(props) {
super(props);
this.state = { name: '', brand: '', expiry: '', serialNumber: '' };
this.handleNameChange = this.handleNameChange.bind(this);
this.handleBrandChange = this.handleBrandChange.bind(this);
this.handleExpiryChange = this.handleExpiryChange.bind(this);
this.handleSerialNumberChange = this.handleSerialNumberChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleNameChange(e) {
this.setState({ name: e.target.value });
}
handleBrandChange(e) {
this.setState({ brand: e.target.value });
}
handleExpiryChange(e) {
this.setState({ expiry: e.target.value });
}
handleSerialNumberChange(e) {
this.setState({ serialNumber: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
const name = this.state.name.trim();
const brand = this.state.brand.trim();
const expiry = this.state.expiry.trim();
const serialNumber = this.state.serialNumber.trim();
if (!name || !brand || !expiry || !serialNumber) {
return;
}
this.props.onColourSubmit({
name: name,
brand: brand,
expiry: expiry,
serialNumber: serialNumber
})
this.setState({
name: '',
brand: '',
expiry: '',
serialNumber: ''
});
}
render() {
return (
<form className="addColourForm" onSubmit={this.handleSubmit}>
<h2>Add a colour to your list</h2>
<div>
<input
type="text"
placeholder="Colour"
value={this.state.name}
onChange={this.handleNameChange}
/>
</div>
<div>
<input
type="text"
placeholder="Brand"
value={this.state.brand}
onChange={this.handleBrandChange}
/>
</div>
<div>
<input
type="text"
placeholder="Expiry MM/YY"
value={this.state.expiry}
onChange={this.handleExpiryChange}
/>
</div>
<div>
<input
type="text"
placeholder="Serial #"
value={this.state.serialNumber}
onChange={this.handleSerialNumberChange}
/>
</div>
<input type="submit" value="Post" />
</form>
);
}
}
class Colour extends React.Component {
render() {
return (
<div className="colour">
<h2 className="colourName">{this.props.name}</h2>
{this.props.children}
</div>
);
}
}
ReactDOM.render(
<ColourDisplay
url="/colours"
submitUrl="/colours/new"
softDeleteUrl="/colours/softDelete"
/>,
document.getElementById('content')
);

Passing the state of a Child component to a Parent?

I think I know what I need to do to make my searchLocationChange function work, but I'm not sure quite how to do it. Please forgive the indentation, was grappling a fair bit with StackOverflow's WYSIWYG!
Here's my Parent component setup:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
forecasts: [],
location: {
city: '',
country: '',
},
selectedDate: 0,
searchText: '',
};
this.handleForecastSelect = this.handleForecastSelect.bind(this);
this.searchLocationChange = this.searchLocationChange.bind(this);
}
}
With this specific function I want to make work:
searchLocationChange() {
console.log(this.state.searchText);
Axios.get('https://mcr-codes-weather.herokuapp.com/forecast', {
params: {
city: this.state.searchText,
},
})
.then((response) => {
this.setState({
forecasts: response.data.forecasts,
location: {
city: response.data.location.city,
country: response.data.location.country,
}
});
});
}
And in my Child component, the logic is:
class SearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const enteredText = event.target.value;
this.setState({
searchText: enteredText
});
}
render() {
return (
<span className="search-form">
<div className="search-form__input"><input type="text" value={this.state.searchText} onChange={this.handleInputChange} /></div>
<div className="search-form__submit"><button onClick={this.props.searchLocationChange}>Search</button></div>
</span>
);
}
}
I realise I'm trying to update the Parent component with searchText from the state of the Child component, but I can't figure out what I need to change to make this work. I have a sneaking suspicion I'm 99% of the way there, and it's only a few more lines I need, but I could be way off?
You're already passing down searchLocationChange from your parent.
in parent component:
searchLocationChange(searchedText) {
console.log(searchText);
Axios.get('https://mcr-codes-weather.herokuapp.com/forecast', {
params: {
city: searchText,
},
})
.then((response) => {
this.setState({
forecasts: response.data.forecasts,
location: {
city: response.data.location.city,
country: response.data.location.country,
},
});
});
}
in child:
render() {
const { searchText } = this.state;
return (
<span className="search-form">
<div className="search-form__input"><input type="text" value={this.state.searchText} onChange={this.handleInputChange} /></div>
<div className="search-form__submit"><button onClick={()=>{this.props.searchLocationChange(searchText)}}>Search</button></div>
</span>
);
}
You should call the function like that this.props.searchLocationChange(this.state.searchText)
You can do something like below
<div className="search-form__submit"><button onClick={() => {this.props.searchLocationChange(this.state.searchText)}}>Search</button></div>
and function definition should be
searchLocationChange(searchText) {
You are mixing controlled and uncontrolled. Either do controlled or uncontrolled. So take the search Text from parent only. Above solutiion is one way of doing this . Another way is to pass searchTxt from parent to child.
<SearchForm
searchTxt={this.state.searchTxt}
handleInputChange={this.handleInputChange}
searchLocationChange={this. searchLocationChange}
/>
Move your handleInputChange in parent:
handleInputChange = (event) => {
const enteredText = event.target.value;
this.setState({ searchText: enteredText });
}
Then change your child component respective line to
<div className="search-form__input"><input type="text" value={this.props.searchText} onChange={this.props.handleInputChange} /></div>
Now when you try the above code it should work. Now you are keeping your searchTxt in the parent component. your SearchForm component is Completely controlled now.

react child component not updating after parent state changed

I am trying to make a react messaging app where the channel page is composed a channel bar Channel.js (Parent Component) with a general and random channel and a message-list ChannelMessage.js (Child Component).
Currently, I can click on the channel bar and it changes the url and this.props.channelName, but the child component displays the same text, regardless of Link clicked. I believe it is because ComponentDidMount does not get called in the child component. How would I go about updating/rerendering the child component to get ComponentDidMount to reload?
Channel.js
export default class Channel extends React.Component {
constructor(props) {
super(props);
this.state = {
channelName: 'general'
};
this.handleSignOut = this.handleSignOut.bind(this);
}
...
render() {
return (
<div className="container">
<div className="row">
<div className="channel-list col-lg-2">
<h2>Channels</h2>
<ul className="list-group">
<li><Link to="/channel/general"
onClick={() => this.setState({ channelName: 'general' })}>General</Link></li>
<li><Link to="/channel/random"
onClick={() => this.setState({ channelName: 'random' })}>Random</Link></li>
</ul>
<div className="footer-segment">
...
</div>
</div>
<ChannelMessages channelName={this.state.channelName} />
</div>
</div>
);
}
ChannelMessages.js
export default class ChannelMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
channelName: this.props.channelName,
message: '',
messages: [],
textValue: ''
}
...
}
componentWillReceiveProps(nextProps) {
this.setState({channelName: nextProps.channelName})
}
componentDidMount() {
this.messageRef = firebase.database().ref('messages/' + this.state.channelName);
this.messageRef.limitToLast(500).on('value', (snapshot) => {
let messages = snapshot.val();
if (messages !== null) {
let newMessages = [];
for (let message in messages) {
newMessages.push({
author: {
displayName: messages[message].author.displayName,
photoURL: messages[message].author.photoURL,
uid: messages[message].author.uid
},
body: messages[message].body,
createdAt: messages[message].createdAt,
id: message
});
}
this.setState({ messages: newMessages, textValue: newMessages.body });
}
console.log(this.state.textValue)
});
}
componentWillUnmount() {
this.messageRef.off('value');
}
handleSubmitMessage(event) {
event.preventDefault();
let user = firebase.auth().currentUser;
this.messageRef.push().set({
author: {
displayName: user.displayName,
photoURL: user.photoURL,
uid: user.uid
},
body: this.state.message,
createdAt: firebase.database.ServerValue.TIMESTAMP
});
this.setState({ message: '' });
}
...
render() {
return (...);
}
}
why are you not using the channelName directly from the props? you are using the componentDidMount event. It runs only once. If you want to run the firebase code whenever a new channel name is passed; extract the fetching logic function and run it in componentDidMount and componentDidUpate(check here if it's different from the previous one)
ComponentDidMount runs only once - more here
ComponentDidUpdate doesn't run on initial trigger - more here
export default class ChannelMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
message: '',
messages: [],
textValue: ''
}
...
}
componentDidMount() {
const {channelName} = this.props;
fetchFromDb(channelName);
}
componentDidUpdate(prevProps) {
if (prevProps.channelName !== this.props.channelName) {
fetchFromDb(this.props.channelName);
}
}
componentWillUnmount() {
this.messageRef.off('value');
}
handleSubmitMessage(event) {
event.preventDefault();
let user = firebase.auth().currentUser;
this.messageRef.push().set({
author: {
displayName: user.displayName,
photoURL: user.photoURL,
uid: user.uid
},
body: this.state.message,
createdAt: firebase.database.ServerValue.TIMESTAMP
});
this.setState({ message: '' });
}
...
render() {
return (...);
}
}

React force componentDidMount

I have the following:
import React from 'react';
import axios from 'axios';
class FirstName extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
};
}
getName () {
var name = this.refs.firstName.value;
this.setState(function() {
this.props.action(name);
});
}
handleSubmit (e) {
e.preventDefault();
this.setState({ submitted: true }, function() {
this.props.actionID(2);
this.props.activeNav('color');
});
}
render () {
return (
<div>
<h2>tell us your first name</h2>
<form>
<input
type="text"
ref="firstName"
onChange={this.getName.bind(this)}
/>
<div className="buttons-wrapper">
<button href="#">back</button>
<button onClick={this.handleSubmit.bind(this)}>continue</button>
</div>
</form>
</div>
);
}
};
class PickColor extends React.Component {
backToPrevious (e) {
e.preventDefault();
this.props.actionID(1);
this.props.activeNav('name');
}
goToNext (e) {
e.preventDefault();
this.props.actionID(3);
this.props.activeNav('design');
this.props.displayIconsHolder(true);
}
getColorValue(event) {
this.props.color(event.target.getAttribute("data-color"));
}
render () {
var colors = ['red', 'purple', 'yellow', 'green', 'blue'],
colorsLink = [];
colors.forEach(el => {
colorsLink.push(<li
data-color={el}
key={el}
onClick={this.getColorValue.bind(this)}
ref={el}>
{el}
</li>
);
});
return (
<section>
<ul>
{colorsLink}
</ul>
<button onClick={this.backToPrevious.bind(this)}>back</button>
<button onClick={this.goToNext.bind(this)}>continue</button>
</section>
);
}
}
class ConfirmSingleIcon extends React.Component {
goBack () {
this.props.goBack();
}
confirmCaptionandIcon (event) {
var optionID = event.target.getAttribute("data-option-id"),
name = event.target.getAttribute("data-option-name");
this.props.setOptionID(optionID);
this.props.setIcon(1, name, optionID, false);
}
goNext () {
this.props.goNext();
}
render () {
console.log(this.props.currentState);
var options = [],
that = this;
this.props.iconOptionsList.forEach(function(el){
options.push(<li onClick={that.confirmCaptionandIcon.bind(that)} key={el.option} data-option-name={el.option} data-option-id={el.id}>{el.option}</li>);
});
return (
<div>
<h2>Choose your caption</h2>
<h3>
{this.props.selectedIcon}
</h3>
<ul>
{options}
</ul>
<button onClick={this.goBack.bind(this)} >back</button>
<button onClick={this.goNext.bind(this)} >confirm</button>
</div>
);
}
}
class ConfirmCaption extends React.Component {
handleClick () {
var currentState = this.props.currentState;
this.props.setIcon(currentState.icon_ID, currentState.selectedIcon, currentState.option_ID, true);
this.props.setIconVisiblity(true);
this.props.setIconListVisiblity(false);
}
render () {
console.log(this.props.currentState);
return (
<div>
<p onClick={this.handleClick.bind(this)}>confirm icon and caption</p>
</div>
);
}
}
class ChooseIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
icons: [],
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
setOptionID (id) {
this.setState({ option_ID: id })
}
setIconVisiblity (onOff) {
this.setState({ confirmIcon: onOff })
}
setIconListVisiblity (onOff) {
this.setState({ iconList: onOff })
}
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
handleClick (event) {
var iconId = event.target.getAttribute("data-icon-id"),
that = this;
this.state.icons.forEach(function(el){
if(el.id == iconId){
that.setState(
{
confirmIcon: true,
iconList: false,
selectedIcon: el.name,
icon_ID: iconId,
selectedIconOptions: el.option
}
);
}
});
}
goBack () {
this.setState(
{
confirmIcon: false,
iconList: true
}
);
}
goNext () {
this.setState(
{
confirmIcon: false,
iconList: false,
confirmCaption: true
}
);
}
render () {
var icons = [];
this.state.icons.forEach(el => {
icons.push(<li data-icon-id={el.id} onClick={this.handleClick.bind(this)} key={el.name}>{el.name}</li>);
});
return (
<div>
{this.state.iconList ? <IconList icons={icons} /> : ''}
{this.state.confirmIcon ? <ConfirmSingleIcon goBack={this.goBack.bind(this)}
goNext={this.goNext.bind(this)}
setIcon={this.props.setIcon}
selectedIcon={this.state.selectedIcon}
iconOptionsList ={this.state.selectedIconOptions}
setOptionID={this.setOptionID}
currentState={this.state} /> : ''}
{this.state.confirmCaption ? <ConfirmCaption currentState={this.state}
setIcon={this.props.setIcon}
setIconVisiblity={this.setIconVisiblity}
setIconListVisiblity={this.setIconListVisiblity} /> : ''}
</div>
);
}
}
class IconList extends React.Component {
render () {
return (
<div>
<h2>Pick your icon</h2>
<ul>
{this.props.icons}
</ul>
</div>
);
}
}
class Forms extends React.Component {
render () {
var form;
switch(this.props.formID) {
case 1:
form = <FirstName action={this.props.action} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 2:
form = <PickColor displayIconsHolder={this.props.seticonsHolder} color={this.props.colorVal} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 3:
form = <ChooseIcon setIcon={this.props.setOptionA} />
break;
}
return (
<section>
{form}
</section>
);
}
}
export default Forms;
"ChooseIcon" is a component that will get used 3 times therefore everytime I get to it I need to bring its state back as if it was the first time.
Ideally I would need to make this ajax call everytime:
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
is there a way to manually call componentDidMount perhaps from a parent component?
React handles component lifecycle through key attribute. For example:
<ChooseIcon key={this.props.formID} setIcon={this.props.setOptionA} />
So every time your key (it can be anything you like, but unique) is changed component will unmount and mount again, with this you can easily control componentDidMount callback.
If you are using the ChooseIcon component 3 times inside the same parent component, I would suggest you to do the ajax in componentDidMount of the parent component like this (exaclty how you have in your example, in terms of code)
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
and then pass this data down to the ChooseIcon component
render() {
return (
//do your stuff
<ChooseIcon icons={this.state.icons}/>
)
}
after this you will only need to set the received props in your ChooseIconcomponent, for that you only need to change one line in it's constructor:
constructor(props) {
super(props);
this.state = {
icons: props.icons, // Changed here!
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
The parent component can use a ref to call the function directly.
However, trying to force this function feels like a smell. Perhaps lifting the state higher up the component tree would solve this problem. This way, the parent component will tell ChooseIcon what to show, and there will not be a need to call componentDidMount again. Also, I assume the Ajax call can also occur once.

Can't use checkbox correctly to update state

I am using a checkbox that should update a todo item's completed status via the component's state. When I click the todo box it doesn't change state, but if I click it two times, it does update the state. Since I have to click it two times, that means when the box is checked this.state.done === false and when it is uncheck this.state.done === true. I don't know why it doesn't flip the state on the first click. The checkbox is controlled by handleDoneChange(). Could someone tell me why the checkbox doesn't change the state on the first click?
class ShowTodo extends Component {
static contextTypes = {
router: PropTypes.object
};
constructor(props) {
super(props);
this.state = {
descriptionChanged: false,
newDescription: '',
newTitle: '',
done: false
};
this.handleDescriptionChange = this.handleDescriptionChange.bind(this);
this.handleDeleteClick = this.handleDeleteClick.bind(this);
this.changeButtons = this.changeButtons.bind(this);
this.handleSaveClick = this.handleSaveClick.bind(this);
this.handleUndoClick = this.handleUndoClick.bind(this);
this.handleTitleChange = this.handleTitleChange.bind(this);
this.handleDoneChange = this.handleDoneChange.bind(this);
}
componentWillMount() {
this.props.fetchTodo(this.props.params.id).then(() => {
this.setState({
newDescription: this.props.todo.description,
newTitle: this.props.todo.title,
done: this.props.todo.completed
});
console.log("This is the todo's starting completed status: ", this.state.done);
});
}
render() {
const { todo } = this.props;
if (!todo) {
return (
<h3>Loading...</h3>
);
}
return (
<div className="input-group">
<Link to="/todos_index">Back</Link>
<h3>Title</h3>
<input
type="text"
className="form-control"
value={this.state.newTitle}
onChange={this.handleTitleChange} />
<textarea
className="form-control"
value={this.state.newDescription}
onChange={this.handleDescriptionChange}>
</textarea>
<span className="input-group-addon">
<input type="checkbox"
onClick={this.handleDoneChange} />
</span>
<span className="input-group-btn">
{this.changeButtons()}
<button onClick={this.handleDeleteClick} className="btn btn-danger pull-xs-right">Delete Post</button>
</span>
</div>
);
}
changeButtons() {
if (!this.state.descriptionChanged) {
return null;
} else {
return [
<button
className="btn btn-default"
onClick={this.handleSaveClick}
>Save</button>,
<button
className="btn btn-default"
onClick={this.handleUndoClick}
>Undo</button>
];
}
}
handleDescriptionChange(event) {
this.setState({
descriptionChanged: true,
newDescription: event.target.value
});
console.log('New description in state: ', this.state.newDescription);
}
handleTitleChange(event) {
this.setState({
descriptionChanged: true,
newTitle: event.target.value
});
}
handleDoneChange(event) { //This isn't updating the done status
this.setState({
done: !this.state.done
});
var id = this.props.params.id;
var props = {
completed: this.state.done
};
console.log(props);
this.props.updateTodo(id, JSON.stringify(props));
}
handleDeleteClick(event) {
this.props.deleteTodo(this.props.params.id).then(() => {
this.context.router.push('/todos_index');
});
}
handleSaveClick(event) {
var id = this.props.params.id;
var props = {
title: this.state.newTitle,
description: this.state.newDescription
};
this.props.updateTodo(id, JSON.stringify(props)).then(() => {
this.context.router.push('/todos_index');
});
}
handleUndoClick() {
this.setState({
descriptionChanged: false,
newTitle: this.props.todo.title,
newDescription: this.props.todo.description
});
}
}
function mapStateToProps(state) {
return { todo: state.todos.todo };
}
export default connect(mapStateToProps, { fetchTodo, updateTodo, deleteTodo })(ShowTodo);
Checkboxes should use onChange rather than onClick.
EDIT: There's another issue.
Your component is working correctly. Your debug console.log is not.
this.setState is asynchronous, so you won't see the changes in the following lines of code. If you want to do something after the state has finished changing, you should pass it as a callback to the setState function:
this.setState({
descriptionChanged: true,
newDescription: event.target.value
}, function(){
console.log('New description in state: ', this.state.newDescription);
}
});

Resources