React CRUD tutorial

De Wikijoan
Dreceres ràpides: navegació, cerca

Contingut

Introducció

Ara que ja he estat treballant amb ReactJS per fer aplicacions front-end, és el moment d'interaccionar amb el back-end per accedir a bases de dades, etc.

Utilitzarem MongoDB, Express, React i Node.js, i ho podem anomenar MERN Stack application. Recordem que CRUD significa create, read, edit, delete, les operacions bàsiques que podem fer en una base de dades, en aquest cas MongoDB.

Why use React?

  1. Fast Learning Curve.
  2. Reusable Components.
  3. Fast render with Virtual DOM.
  4. Clean Abstraction.
  5. Redux: A State Management Library For Complex UIs.
  6. Great Developer Tools.
  7. React Native: You can build a cross-platform native mobile application for Android or iOS.

Tutorial

React crud.png

Seguim aquest tutorial:

#1: Install React Application

npx create-react-app reactcrud
cd reactcrud
npm start

Now, install the Bootstrap 4 Framework using the following command.

npm install bootstrap --save

Import the Bootstrap CSS Framework inside our project.

// App.js

import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';

class App extends Component {
  render() {
    return (
      <div className="container">
        <h2>React CRUD Tutorial</h2>
      </div>
    );
  }
}

export default App;

#2: Configure React routing

L'enrutament és molt important per poder fer enllaços entre diferents pàgines web. Passem d'una aplicació SPA (single page application) a una aplicació MPA (multiple page application).

npm install react-router-dom --save

A index.js importem BrowserRouter:

// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';

import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <BrowserRouter>
  <App />
  </BrowserRouter>, document.getElementById('root'));

serviceWorker.unregister();

i creem tres components, que representaran les nostres 3 pàgines que tindrem. Inside the src folder, create one directory called components and inside that folder, make three components:

// create.component.js

import React, { Component } from 'react';

export default class Create extends Component {
    render() {
        return (
            <div>
                <p>Welcome to Create Component!!</p>
            </div>
        )
    }
}
// edit.component.js

import React, { Component } from 'react';

export default class Edit extends Component {
    render() {
        return (
            <div>
                <p>Welcome to Edit Component!!</p>
            </div>
        )
    }
}
// index.component.js

import React, { Component } from 'react';

export default class Index extends Component {
    render() {
        return (
            <div>
                <p>Welcome to Index Component!!</p>
            </div>
        )
    }
}

i ara a App.js ja podem importar aquests components, i afegir la barra de navegació. Si anem al navegador, ja podem veure que tenim la barra de navegació, i que podem enllaçar les tres pàgines:

#3: Create the bootstrap form

A create.component.js podem crear el formulari, que és del tipus bootstrap:

// create.component.js

import React, { Component } from 'react';

export default class Create extends Component {
    render() {
        return (
            <div style={{marginTop: 10}}>
                <h3>Add New Business</h3>
                <form>
                    <div className="form-group">
                        <label>Add Person Name:  </label>
                        <input type="text" className="form-control"/>
                    </div>
                    <div className="form-group">
                        <label>Add Business Name: </label>
                        <input type="text" className="form-control"/>
                    </div>
                    <div className="form-group">
                        <label>Add GST Number: </label>
                        <input type="text" className="form-control"/>
                    </div>
                    <div className="form-group">
                        <input type="submit" value="Register Business" className="btn btn-primary"/>
                    </div>
                </form>
            </div>
        )
    }
}

#4: Submit the Form

Treballem la classe Create, que és el formulari, de manera que quan modifiquem els valors de la caixa de text s'actualitzen les variables state, i quan cliquem submit podem visualitzar el valor dels estats, abans de fer el POST al servidor en les seccions següents.

compte! que en el tutorial hi ha un petit error. El codi s'ha de posar a create.component.js i no pas a App.js:

// App.js (NO!!)
// create.component.js 

import React, { Component } from 'react';

export default class Create extends Component {
...

#5: Create a backend on Node.js

Inside our reactcrud project root, create one folder called api/ and go inside that folder and initialize the package.json file.

npm init -y

Now, install the following node.js dependencies:

npm install express body-parser cors mongoose --save

Now, inside the api folder, create one file called the server.js and add the following code inside it.

// server.js

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const PORT = 4000;
const cors = require('cors');

app.use(cors());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.listen(PORT, function(){
  console.log('Server is running on Port:',PORT);
});

És un servidor que corre pel port 4000 i l'arrenco de la següent manera:

$ node server.js

Cada canvi que faci a server.js l'hauré de tornar a arrencar. Per fer-ho més fàcil i refrescar-lo en calent puc fer servir nodemon (node monitor):

Also, install the nodemon as a development dependency. So that we do not need to restart every time we change our server code.

$ npm install nodemon --save-dev

$ nodemon server

nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.

Però a mi no m'acaba de funcionar?

$ nodemon server.js
command not found

If you want to run it locally instead of globally, you can run it from your node_modules.

I ho fem de la següent manera:


$ npx nodemon server.js 
[nodemon] 2.0.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
Server is running on Port: 4000

#6: Setup a MongoDB database

Si no tenim instal·lat el MongoDB:

$ sudo apt-cache search mongodb

mongodb - object/document-oriented database (metapackage)
mongodb-clients - object/document-oriented database (client apps)
mongodb-dev - MongoDB C++ Driver (transitional package)
mongodb-server - object/document-oriented database (managed server package)
mongodb-server-core - object/document-oriented database (server binaries package)

$ sudo apt-get install mongodb

El client:

$ mongo
>

Inside the api folder, create one file called the DB.js: </pre> // DB.js

module.exports = {

   DB: 'mongodb://localhost:27017/reactcrud'

} </pre> I ara el nostre servidor ja es pot connectar a la base de dades:

...
const mongoose = require('mongoose');
const config = require('./DB.js');

mongoose.Promise = global.Promise;
mongoose.connect(config.DB, { useNewUrlParser: true }).then(
  () => {console.log('Database is connected') },
  err => { console.log('Can not connect to the database'+ err)}
);
...
$ node server.js 
Server is running on Port: 4000
Database is connected

#7: Create a Mongoose Schema

The next step is that we need to create a schema for the MongoDB database. For that, create a file inside the api project root called business.model.js and add the following code.

// business.model.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Define collection and schema for Business
let Business = new Schema({
  person_name: {
    type: String
  },
  business_name: {
    type: String
  },
  business_gst_number: {
    type: Number
  }
},{
    collection: 'business'
});

module.exports = mongoose.model('Business', Business);

We have taken three fields called person_name, business_name, and business_gst_number, with its datatypes.

NOTA: business_gst_number és un número, i si des del formulari (quan funcioni) no hi posem un número, l'aplicació petarà quan faci el insert. (És un problema que m'he trobat).

#8: Define the route for Node.js Express application

Write the CRUD code inside the business.route.js file.

// business.route.js

const express = require('express');
const businessRoutes = express.Router();

// Require Business model in our routes module
let Business = require('./business.model');

// Defined store route
businessRoutes.route('/add').post(function (req, res) {
  let business = new Business(req.body);
  business.save()
    .then(business => {
      res.status(200).json({'business': 'business in added successfully'});
    })
    .catch(err => {
    res.status(400).send("unable to save to database");
    });
});

...

Quan faci un add, o edit, update, delete o / (list), vull una secció del servidor que faci les operacions adequades a la base de dades. Per exemple, en el tros de codi anterior és el add. En el server.js hauré d'incloure aquest codi:

...
const businessRoute = require('./business.route');
...

#9: Install the Axios library and send a POST request

Ara he de saber enviar les dades del formulari al servidor, per POST. Ho farem amb la llibreria axios:

npm install axios --save

(al directori arrel de l'aplicació)

Now, send the HTTP POST request along with the form data to the node js server. We will send the data as an object because we have used the body-parser at the backend to pluck the data from the request and save it in the database.

Write the following code inside the create.component.js file. La part que ens interessa és la del submit:

...
  onSubmit(e) {
    e.preventDefault();
    const obj = {
      person_name: this.state.person_name,
      business_name: this.state.business_name,
      business_gst_number: this.state.business_gst_number
    };
    axios.post('http://localhost:4000/business/add', obj)
        .then(res => console.log(res.data));
    
    this.setState({
      person_name: '',
      business_name: '',
      business_gst_number: ''
    })
  }
...

veiem com enviem les dades al servidor.

En aquests moments, el formulari ja hauria de funcionar, i hem de veure en el MongoDB les dades que introduïm des del formulari:

$ mongo
> use reactcrud
switched to db reactcrud
> db.business.find().pretty()

#10: Display the backend data

Definim el component TableRow per construir una taula HTML amb les dades que vinguin de la consulta a la BD. Script TableRow.js':

// TableRow.js

import React, { Component } from 'react';

class TableRow extends Component {
  render() {
    return (
        <tr>
          <td>
            {this.props.obj.person_name}
          </td>
          <td>
            {this.props.obj.business_name}
          </td>
          <td>
            {this.props.obj.business_gst_number}
          </td>
          <td>
            <button className="btn btn-primary">Edit</button>
          </td>
          <td>
            <button className="btn btn-danger">Delete</button>
          </td>
        </tr>
    );
  }
}

export default TableRow;

I a index.component.js importo aquest component, i cada vegada que s'actualitzi les dades (componentDidMount()) s'haurà de rederitzar de nou les dades fent una consulta al servidor:

axios.get('http://localhost:4000/business')

Fitxer index.component.js:

// index.component.js

import React, { Component } from 'react';
import axios from 'axios';
import TableRow from './TableRow';

export default class Index extends Component {

  constructor(props) {
      super(props);
      this.state = {business: []};
    }
    componentDidMount(){
      axios.get('http://localhost:4000/business')
        .then(response => {
          this.setState({ business: response.data });
        })
        .catch(function (error) {
          console.log(error);
        })
    }
    tabRow(){
      return this.state.business.map(function(object, i){
          return <TableRow obj={object} key={i} />;
      });
    }

    render() {
      return (
        <div>
          <h3 align="center">Business List</h3>
          <table className="table table-striped" style={{ marginTop: 20 }}>
            <thead>
              <tr>
                <th>Person</th>
                <th>Business</th>
                <th>GST Number</th>
                <th colSpan="2">Action</th>
              </tr>
            </thead>
            <tbody>
              { this.tabRow() }
            </tbody>
          </table>
        </div>
      );
    }
  }

#11: Edit and Update Functionality

Fins ara funciona el crear elements nous, i llistar-los. Per acabar, he d'afegir les funcionalitats d'editar i esborrar les dades.

A TableRow.js afegir el link d'edició:

// TableRow.js

import { Link } from 'react-router-dom';

<Link to={"/edit/"+this.props.obj._id} className="btn btn-primary">Edit</Link>

I ara programem la funcionalitat d'editar a edit.component.js:

// edit.component.js

import React, { Component } from 'react';
import axios from 'axios';

export default class Edit extends Component {
  constructor(props) {
    super(props);
    this.onChangePersonName = this.onChangePersonName.bind(this);
    this.onChangeBusinessName = this.onChangeBusinessName.bind(this);
    this.onChangeGstNumber = this.onChangeGstNumber.bind(this);
    this.onSubmit = this.onSubmit.bind(this);

    this.state = {
      person_name: '',
      business_name: '',
      business_gst_number:''
    }
  }

//important per què es vegin les dades després dels canvis (mirar a comentaris)
componentDidUpdate() {
axios.get('http://localhost:4000/business')
.then(response => {
this.setState({ business: response.data });
})
.catch(function (error) {
console.log(error);
})
}

  componentDidMount() {
      axios.get('http://localhost:4000/business/edit/'+this.props.match.params.id)
          .then(response => {
              this.setState({ 
                person_name: response.data.person_name, 
                business_name: response.data.business_name,
                business_gst_number: response.data.business_gst_number });
          })
          .catch(function (error) {
              console.log(error);
          })
    }

  onChangePersonName(e) {
    this.setState({
      person_name: e.target.value
    });
  }
  onChangeBusinessName(e) {
    this.setState({
      business_name: e.target.value
    })  
  }
  onChangeGstNumber(e) {
    this.setState({
      business_gst_number: e.target.value
    })
  }

  onSubmit(e) {
    e.preventDefault();
    const obj = {
      person_name: this.state.person_name,
      business_name: this.state.business_name,
      business_gst_number: this.state.business_gst_number
    };
    axios.post('http://localhost:4000/business/update/'+this.props.match.params.id, obj)
        .then(res => console.log(res.data));
    
    this.props.history.push('/index');
  }
 
  render() {
    return (
        <div style={{ marginTop: 10 }}>
            <h3 align="center">Update Business</h3>
            <form onSubmit={this.onSubmit}>
                <div className="form-group">
                    <label>Person Name:  </label>
                    <input 
                      type="text" 
                      className="form-control" 
                      value={this.state.person_name}
                      onChange={this.onChangePersonName}
                      />
                </div>
                <div className="form-group">
                    <label>Business Name: </label>
                    <input type="text" 
                      className="form-control"
                      value={this.state.business_name}
                      onChange={this.onChangeBusinessName}
                      />
                </div>
                <div className="form-group">
                    <label>GST Number: </label>
                    <input type="text" 
                      className="form-control"
                      value={this.state.business_gst_number}
                      onChange={this.onChangeGstNumber}
                      />
                </div>
                <div className="form-group">
                    <input type="submit" 
                      value="Update Business" 
                      className="btn btn-primary"/>
                </div>
            </form>
        </div>
    )
  }
}

So, what we have done is, we have used the component lifecycle method to fetch the data from the API.

That data needs to be displayed inside the textbox because this is an edit form. Next, it is the same thing as we have written the code in the create.component.js file.

#12: Delete the data

Now, the only thing remaining is to delete the data. To define the delete function inside TableRow.js file.

// TableRow.js

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';

class TableRow extends Component {

  constructor(props) {
        super(props);
        this.delete = this.delete.bind(this);
    }
    delete() {
        axios.get('http://localhost:4000/business/delete/'+this.props.obj._id)
            .then(console.log('Deleted'))
            .catch(err => console.log(err))
    }
  render() {
    return (
        <tr>
          <td>
            {this.props.obj.person_name}
          </td>
          <td>
            {this.props.obj.business_name}
          </td>
          <td>
            {this.props.obj.business_gst_number}
          </td>
          <td>
            <Link to={"/edit/"+this.props.obj._id} className="btn btn-primary">Edit</Link>
          </td>
          <td>
            <button onClick={this.delete} className="btn btn-danger">Delete</button>
          </td>
        </tr>
    );
  }
}

export default TableRow;

Now, we have completed React CRUD Example or MERN Stack Tutorial From Scratch.

NOTA: com es comenta en els comentaris, falta posar el següent codi a index.component.js per tal de què es vegin els canvis que realitzem.

//important per què es vegin les dades després dels canvis (mirar a comentaris)
componentDidUpdate() {
axios.get('http://localhost:4000/business')
.then(response => {
this.setState({ business: response.data });
})
.catch(function (error) {
console.log(error);
})
}

creat per Joan Quintana Compte, abril 2020

Eines de l'usuari
Espais de noms
Variants
Accions
Navegació
Institut Jaume Balmes
Màquines recreatives
CNC
Informàtica musical
joanillo.org Planet
Eines