We have built a React Application that connects with Redux, then filter with input text and sort list of items. We also created Form for adding new item. In this tutorial, we’re gonna use that form to edit and update item with id param getting from Url using react-redux and React Router v4.
Contents
Example Overview
When clicking on any item, the app will bring us to a Form (like add item form).
Now we can edit item and click on Add Book button, the list of Book items will be updated immediately.
How to Get Param from Url & create Edit Item Form
Context
Remember that our App state is like this:
const demoState = { books: [ { id: '123abcdefghiklmn', title: 'Origin', description: 'Origin thrusts Robert Langdon into the dangerous intersection of humankind’s two most enduring questions.', author: 'Dan Brown', published: 2017 } ], filters: { text: 'ori', sortBy: 'published', // published or title startYear: undefined, endYear: undefined } }; |
We have had BookForm Component that has its own state for Book fields and onSubmit()
method:
// components/BookForm.js export default class BookForm extends React.Component { constructor(props) { super(props); this.onTitleChange = this.onTitleChange.bind(this); this.onAuthorChange = this.onAuthorChange.bind(this); this.onDescriptionChange = this.onDescriptionChange.bind(this); this.onPublishedChange = this.onPublishedChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.state = { title: '', author: '', description: '', published: 0, error: '' }; } // ... onSubmit(e) { // ... this.props.onSubmitBook( { title: this.state.title, author: this.state.author, description: this.state.description, published: this.state.published } ); } render() { return ( <div> <form onSubmit={this.onSubmit} className='add-book-form'> // form elements </form> </div> ); } } |
We also have Router to route edit Book item by its id:
// routers/AppRouter.js const AppRouter = () => ( <BrowserRouter> <div className='container'> <Header /> <Switch> <Route path="/" component={DashBoard} exact={true} /> <Route path="/add" component={AddBook} /> <Route path="/book/:id" component={EditBook} /> <Route path="/help" component={Help} /> <Route component={NotFound} /> </Switch> </div> </BrowserRouter> ); |
And each Book item has enough data that includes id
:
// components/Book.js const Book = ({ id, title, description, author, published, dispatch }) => ( <div> // ... </div> ); export default connect()(Book); |
Solution
We need:
– url for each Book item including id
=> inside Book item, add React Link
with attribute to={
./book/${id}
}
– ‘editBook’ action => create new Action in actions/books folder with type: 'EDIT_BOOK'
. This action need 2 parameters id
and updates
that contains Book fields.
– a form containing Book fields => re-use BookForm Component as child in EditBook Component. It connects with Redux to pass dispatch(editBook)
to BookForm Component.
– the form should have Book fields to be filled already => BookForm state get data from props that is passed from EditBook Component.
Practice
Add Link for each Item
Open components/Book.js, cover Book ‘title-published’ with Link
:
import React from 'react'; import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; import { removeBook } from '../actions/books'; const Book = ({ id, title, description, author, published, dispatch }) => ( <div> <Link to={`/book/${id}`}> <h4>{title} ({published})</h4> </Link> <p>Author: {author}</p> {description && <p>{description}</p>} <button onClick={() => { dispatch(removeBook({ id })); }}>Remove</button> </div> ); export default connect()(Book); |
Create new Action
Open actions/books.js, add action creator named editBook
with id
, updates
as input parameters:
// ... export const addBook = (...); export const removeBook = (...); export const editBook = (id, updates) => ({ type: 'EDIT_BOOK', id, updates }); |
Add case for Book Reducer
const booksReducerDefaultState = []; export default (state = booksReducerDefaultState, action) => { switch (action.type) { case 'ADD_BOOK': return [ ...state, action.book ]; case 'REMOVE_BOOK': return state.filter(({ id }) => id !== action.id); case 'EDIT_BOOK': return state.map((book) => { if (book.id === action.id) { return { ...book, ...action.updates }; } else { return book; } }); default: return state; } }; |
Add Form for editing item
Inside components/EditBook.js:
– use react-redux connect()
with mapStateToProps
to get Book item by id form url (props.match.params.id
)
– add BookForm Component with props including:
+ the Book above
+ onSubmitBook()
function that contains dispatch(editBook(id,book))
import React from 'react'; import BookForm from './BookForm'; import { connect } from 'react-redux'; import { editBook } from '../actions/books'; const EditBook = (props) => ( <div className='container__box'> <BookForm book={props.book} onSubmitBook={(book) => { props.dispatch(editBook(props.book.id, book)); props.history.push('/'); }} /> </div> ); const mapStateToProps = (state, props) => { return { book: state.books.find((book) => book.id === props.match.params.id) }; }; export default connect(mapStateToProps)(EditBook); |
Initialize Form elements with Book fields
BookForm state will be initialized by props that is received from EditBook Component.
components/BookForm.js:
export default class BookForm extends React.Component { constructor(props) { super(props); this.onTitleChange = this.onTitleChange.bind(this); this.onAuthorChange = this.onAuthorChange.bind(this); this.onDescriptionChange = this.onDescriptionChange.bind(this); this.onPublishedChange = this.onPublishedChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.state = { title: props.book ? props.book.title : '', author: props.book ? props.book.author : '', description: props.book ? props.book.description : '', published: props.book ? props.book.published : 0, error: '' }; } // ... onSubmit(e) { // ... this.props.onSubmitBook( { title: this.state.title, author: this.state.author, description: this.state.description, published: this.state.published } ); } render() { return ( <div> <form onSubmit={this.onSubmit} className='add-book-form'> // form elements </form> </div> ); } } |
Run and Check Result
app.js
import React from 'react'; import ReactDOM from 'react-dom'; import AppRouter from './routers/AppRouter'; import getAppStore from './store/store'; import { addBook } from './actions/books'; import { filterText, startYear, endYear, sortBy, clear } from './actions/filters'; import getVisibleBooks from './selectors/books'; import './styles/styles.scss'; import { Provider } from 'react-redux'; const store = getAppStore(); store.dispatch(addBook({ title: 'Origin', description: 'Origin thrusts Robert Langdon into the dangerous intersection of humankind’s two most enduring questions.', author: 'Dan Brown', published: 2017 })); store.dispatch(addBook({ title: 'Harry Potter and the Goblet of Fire', description: 'A young wizard finds himself competing in a hazardous tournament between rival schools of magic, but he is distracted by recurring nightmares.', author: 'J. K. Rowling', published: 2000 })); store.dispatch(addBook({ title: 'Harry Potter and the Deathly Hallows', description: 'The seventh and final novel of the Harry Potter series.', author: 'J. K. Rowling', published: 2007 })); store.dispatch(addBook({ title: 'The 100-Year-Old Man Who Climbed Out the Window and Disappeared', author: 'Jonas Jonasson', published: 2009 })); const template = ( <Provider store={store}> <AppRouter /> </Provider> ); ReactDOM.render(template, document.getElementById('app')); |
Check Result
– Click on a Book title:
– Change something and check result:
Source Code
For running:
– yarn install
– yarn run dev-server
or yarn run build
, then yarn run serve
.