Get Param from Url & create Edit Item Form – react-redux example

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.

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.

edit-item-form-react-redux-example-goal

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 (
            
// form elements
); } }

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 }) => (
    
// ...
); 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:

edit-item-form-react-redux-example-goal

– Change something and check result:

edit-item-form-react-redux-example-result

Source Code

ReactRedux-editItem-form

For running:
yarn install
yarn run dev-server or yarn run build, then yarn run serve.



By grokonez | April 19, 2018.

Last updated on May 2, 2021.



Related Posts


Got Something To Say:

Your email address will not be published. Required fields are marked *

*