In the previous parts, we have covered many different React features. The way to create the input controls from the configuration object and validation of those controls are just some of the features we have covered. These features will be useful in this part of the series where we send the PUT request towards the server in order to update our entity object.
So, let’s dive right into it.
For the complete navigation and all the basic instructions of the React series, check out: Introduction to the React series.
The source code is available at GitHub: React series – react-series-part8-end branch
This post is divided into several sections:
- Preparation for the Update Owner Component and the Routing Settings
- Adding Input Elements, Validation, and Two-Way Binding
- Connecting the Reducer With the Component
- Fetching Data From The Server
- Displaying Data on the Screen
- Executing the Update Action
- Conclusion
Preparation for the Update Owner Component and the Routing Settings
Inside the containers
folder, we can find the Owner
folder. Inside that folder, we are going to create a new one and name it UpdateOwner
. Let’s create a new file UpdateOwner.js
inside:
In this file, we are going to handle a business logic to update the owner entity by reusing the features we have already used in the CreateOwner
component. Therefore, this business logic shouldn’t be too hard to understand because we already have all the necessary knowledge (from previous parts) about input fields, validation, and a state object.
Let’s add the basic logic to the UpdateOwner.js
file:
import React, { Component } from 'react'; import { Form, Well, Button, FormGroup, Col } from 'react-bootstrap'; import { returnInputConfiguration } from '../../../Utility/InputConfiguration'; class UpdateOwner extends Component { state = { ownerForm: {}, isFormValid: true } componentWillMount = () => { this.setState({ ownerForm: returnInputConfiguration() }); } render() { return ( <Well> </Well> ) } } export default UpdateOwner;
So, we have created the class component with its local state. This state has the same properties as the CreateOwner component: the
ownerForm
, and isFormValid
. For the UpdateOwner component, we need to set the isFormValid
property to true because all the fields on the form are going to be populated as soon as the component mounts. Therefore, our fields will not be invalid when form mounts as they were in the CreateOwner component.In the compnentWillMount
lifecycle hook, we fetch all the input configuration and update the state.
To enable navigation to this component we need to modify the App.js
file:
import UpdateOwner from './Owner/UpdateOwner/UpdateOwner';
<Route path="/updateOwner/:id" component={UpdateOwner} />
Now we are able to navigate to this component.
Adding Input Elements, Validation, and Two-Way Binding
Right below the render
function and above the return
block we are going to add this line of code to convert the ownerForm
object to an array of objects:
const formElementsArray = formUtilityActions.convertStateToArrayOfFormObjects({ ...this.state.ownerForm });
We shouldn’t forget to add the import statement for the
formUtilityActions
and the Input
component as well:import * as formUtilityActions from '../../../Utility/FormUtility'; import Input from '../../../UI/Inputs/Input';
Then, inside the
Well
tag, we are going to add this code to display the input elements and buttons:<Form horizontal onSubmit={this.updateOwner}> { formElementsArray.map(element => { return <Input key={element.id} elementType={element.config.element} id={element.id} label={element.config.label} type={element.config.type} value={element.config.value} changed={(event) => this.handleChangeEvent(event, element.id)} errorMessage={element.config.errorMessage} invalid={!element.config.valid} shouldValidate={element.config.validation} touched={element.config.touched} blur={(event) => this.handleChangeEvent(event, element.id)} /> }) } <br /> <FormGroup> <Col mdOffset={6} md={1}> <Button type='submit' bsStyle='info' disabled={!this.state.isFormValid}>Update</Button> </Col> <Col md={1}> <Button bsStyle='danger' onClick={this.redirectToOwnerList}>Cancel</Button> </Col> </FormGroup> </Form>
This is code which we have used in one of the previous posts. Moreover, let’s add the
handleChangeEvent
function (below the componentWillMount
lifecycle hook) to enable validation and two-way binding:handleChangeEvent = (event, id) => { const updatedOwnerForm = { ...this.state.ownerForm }; updatedOwnerForm[id] = formUtilityActions.executeValidationAndReturnFormElement(event, updatedOwnerForm, id); const counter = formUtilityActions.countInvalidElements(updatedOwnerForm); this.setState({ ownerForm: updatedOwnerForm, isFormValid: counter === 0 }) }
Again this is the section where we can reuse the code for validation and two-way binding.
At this moment when we click on the Update button in the OwnerList
component, we are going to see the UpdateOwner view:
This is fine, but we want to populate all the fields with correct owner’s data.
So, let’s do exactly that.
Connecting the Reducer With the Component
Let’s add the necessary import statements in the UpdateOwner.js
file:
import * as repositoryActions from '../../../store/actions/repositoryActions'; import * as errorHandlerActions from '../../../store/actions/errorHandlerActions'; import { connect } from 'react-redux'; import moment from 'moment';
Then we are going to add the
mapStateToProps
function below the component:const mapStateToProps = (state) => { return { data: state.repository.data, showSuccessModal: state.repository.showSuccessModal, showErrorModal: state.errorHandler.showErrorModal, errorMessage: state.errorHandler.errorMessage } }
Just below the
mapStateToProps
function, we need to add the mapDispatchToProps
function:const mapDispatchToProps = (dispatch) => { return { onGetOwnerById: (url, props) => dispatch(repositoryActions.getData(url, props)), onUpdateOwner: (url, owner, props) => dispatch(repositoryActions.putData(url, owner, props)), onCloseSuccessModal: (url, props) => dispatch(repositoryActions.closeSuccessModal(props, url)), onCloseErrorModal: () => dispatch(errorHandlerActions.closeErrorModal()) } }
Finally, let’s modify the export statement:
export default connect(mapStateToProps, mapDispatchToProps)(UpdateOwner);
There it is.
Now we have the connection between the reducers and our component and we can add our modal components to show success or error messages.
First, let’s add the import statements:
import SuccessModal from '../../../components/Modals/SuccessModal/SuccessModal'; import ErrorModal from '../../../components/Modals/ErrorModal/ErrorModal';
Then we need to add the components below the
Form
tag but inside the Well
tag:<SuccessModal show={this.props.showSuccessModal} modalHeaderText={'Success message'} modalBodyText={'Action completed successfully'} okButtonText={'OK'} successClick={() => this.props.onCloseSuccessModal('/owner-List', { ...this.props })} /> <ErrorModal show={this.props.showErrorModal} modalHeaderText={'Error message'} modalBodyText={this.props.errorMessage} okButtonText={'OK'} closeModal={() => this.props.onCloseErrorModal()} />
Fetching Data From The Server
Below the compnentWillMount
lifecycle hook, we are going to add another hook to fetch the data from the server:
componentDidMount = () => { const id = this.props.match.params.id; const url = '/api/owner/' + id; this.props.onGetOwnerById(url, { ...this.props }); }
In this function, we take the id of the owner we want to update and then we fetch that owner from the server. After that we need to update our local state (to be more precise the ownerForm object inside that state) to show the data from the owner object in the input fields.
The componentWillMount
and the componentDidMount
hooks are the creation lifecycle hooks. But React has the update lifecycle hooks which triggers as soon as a new property arrives at the component or we update the state.
So, if we take a look at the diagram where we explained the Redux flow, we can see that after the reducer updates the state, the central store is going to propagate that state as props
inside the component. We can catch that change at that moment and update our ownerForm
object inside our local state with the update lifecycle hook.
Displaying Data on the Screen
Therefore, let’s add a new update lifecycle hook below the componentDidMount
function:
componentWillReceiveProps = (nextProps) => { const updatedOwnerForm = { ...this.state.ownerForm }; let nameObject = { ...updatedOwnerForm.name }; let dateObject = { ...updatedOwnerForm.dateOfBirth }; let addressObject = { ...updatedOwnerForm.address }; nameObject.value = nextProps.data.name; nameObject.valid = true; dateObject.value = moment(nextProps.data.dateOfBirth); addressObject.value = nextProps.data.address; addressObject.valid = true; updatedOwnerForm['name'] = nameObject; updatedOwnerForm['dateOfBirth'] = dateObject; updatedOwnerForm['address'] = addressObject; this.setState({ ownerForm: updatedOwnerForm }); }
The
componentWillReceiveProps
hook is called only when a component receives a new props object and not when we update the state.Why is this important ?
Well, some of the update lifecycle hooks are going to be triggered when new props arrive and when we update the state as well, therefore if we update the state inside that hook and do not provide the exit condition (some type of if statement) we are going to end up with an infinite loop. But that’s not the case with the componentWillReceiveProps
hook.
Inside the componentWillReceiveProps
hook, we are not using this.props
statement but the nextProps
parameter because it contains our new props
object. So, all the actions in this function are dedicated to extracting the ownerForm from the state immutably and placing it in theupdatedOwnerform
. Then from the updatedOwnerForm
, we can extract all the other objects immutably as well (name, address, and dateOfBirth). After that action, we can change the property values inside those objects and return those objects inside the updatedOwnerForm
. Finally, we can update the state with the new updatedOwnerForm
object.
Now our form has a different look once we navigate to it:
Executing the Update Action
At the end let’s add two functions to redirect to the OwnerList component if we click the Cancel button, and to update the entity:
redirectToOwnerList = () => { this.props.history.push('/owner-List'); } updateOwner = (event) => { event.preventDefault(); const ownerToUpdate = { name: this.state.ownerForm.name.value, dateOfBirth: this.state.ownerForm.dateOfBirth.value, address: this.state.ownerForm.address.value } const url = "/api/owner/" + this.props.data.id; this.props.onUpdateOwner(url, ownerToUpdate, {...this.props}); }
There we go. Now we can test the update functionality.
We can modify the code on the Web API server to respond with the success and the error responses to test our modal components. Furthermore, we can test the form validation by emptying the input fields or typing more than 60 characters in the address input field.
Conclusion
By reading this post, you’ve learned:
- How to use different lifecycle hooks to fetch data from the server and to update a local state
- To handle PUT request in our project
Thank you for reading the article and I hope you found something useful in it.
In the next part of the series, where we finish our React series journey, we are going to learn how to handle Delete request in our application.