React and Express

Hi All, I am having a difficult time with my react app updating items in the backend of my express API i made. Its an array of items on a json file and fetch them using axios, however, when I want to update an item I seem to be failing to get it to update.

import React, { Component } from 'react';
import axios from 'axios';
import { Input, FormGroup, Label, Modal, ModalHeader, ModalBody, ModalFooter, Table, Button } from 'reactstrap';

class Apps extends Component {
  state = {
    items: [],
    newItemModal: {
      title: '',
      description: '',
      url: ''
    },
    editItemData: {
        title: '',
        description: '',
        url: ''
    },
    newItemModal: false,
    editItemModal: false
  }
  componentWillMount() {
    this._refreshItems();
  }
  toggleNewItemModal() {
    this.setState({
      newItemModal: ! this.state.newItemModal
    });
  }
  toggleEditItemModal() {
    this.setState({
      editItemModal: ! this.state.editItemModal
    });
  }
  addItem() {
    axios.post("/api/add", this.state.newItemData).then((response) => {
      let { items } = this.state;

      items.push(response.data);

      this.setState({ items, newItemModal: false, newItemModal: {
        title: '',
        description: '',
        url: ''
      }});
    });
  }
  updateItem() {
    let { title, description, url } = this.state.editItemData;

    axios.put('/api/' + this.state.editItemData.id, {
      title, description, url
    }).then((_response) => {
      this._refreshItems();

      this.setState({
        editItemModal: false, editItemData: { title: '', url: '' }
      })
    });
  }
  editItem(title, description, url) {
    this.setState({
      editItemData: { title, description, url }, editItemModal: ! this.state.editItemModal
    });
  }
  deleteItem(id) {
    axios.delete("/api/:id").then((response) => {
      this._refreshItems();
    });
  }
  _refreshItems() {
    console.log("Mounted")
    axios.get("/api").then(data => this.setState(data.data));
  }

  render() {
    let items = this.state.items.length && this.state.items.map((item) => {
      return (
        <tr key={item.id}>
          <td>{item.title}</td>
          <td>{item.description}</td>
          <td>{item.url}</td>
          <td>
            <Button color="success" size="sm" className="mr-2" onClick={this.editItem.bind(this, item.title, item.url)}>Edit</Button>
            <Button color="danger" size="sm" onClick={this.deleteItem.bind(this, item.id)}>Delete</Button>
          </td>
        </tr>
      )
    });
    return (
      <div className="App container">

      <h1>Project Items App</h1>

      <Button className="my-3" color="primary" onClick={this.toggleNewItemModal.bind(this)}>Add Item</Button>

      <Modal isOpen={this.state.newItemModal} toggle={this.toggleNewItemModal.bind(this)}>
        <ModalHeader toggle={this.toggleNewItemModal.bind(this)}>Add a new Item</ModalHeader>
        <ModalBody>
          <FormGroup>
            <Label for="title">Title</Label>
            <Input id="title" value={this.state.newItemModal.title} onChange={(e) => {
              let { newItemModal } = this.state;

              newItemModal.title = e.target.value;

              this.setState({ newItemModal });
            }} />
          </FormGroup>

          <FormGroup>
          <Label for="description">description</Label>
          <Input id="description" value={this.state.newItemModal.description} onChange={(e) => {
              let { newItemModal } = e.target.value;

              newItemModal.description = e.target.value;

              this.setState({ newItemModal });
            }} />
          </FormGroup>

          <FormGroup>
            <Label for="url">url</Label>
            <Input id="url" value={this.state.newItemModal.url} onChange={(e) => {
              let { newItemModal } = this.state;

              newItemModal.url = e.target.value;

              this.setState({ newItemModal });
            }} />
          </FormGroup>

        </ModalBody>
        <ModalFooter>
          <Button color="primary" onClick={this.addItem.bind(this)}>Add Item</Button>{' '}
          <Button color="secondary" onClick={this.toggleNewItemModal.bind(this)}>Cancel</Button>
        </ModalFooter>
      </Modal>

      <Modal isOpen={this.state.editItemModal} toggle={this.toggleEditItemModal.bind(this)}>
        <ModalHeader toggle={this.toggleEditItemModal.bind(this)}>Edit a new Item</ModalHeader>
        <ModalBody>
          <FormGroup>
            <Label for="title">Title</Label>
            <Input id="title" value={this.state.editItemData.title} onChange={(e) => {
              let { editItemData } = this.state;

              editItemData.title = e.target.value;

              this.setState({ editItemData });
            }} />
          </FormGroup>

          <FormGroup>
          <Label for="description">description</Label>
          <Input id="description" value={this.state.editItemModal.description} onChange={(e) => {
              let { editItemModal } = this.state;

              editItemModal.description = e.target.value;

              this.setState({ editItemModal });
            }} />
          </FormGroup>

          <FormGroup>
            <Label for="url">url</Label>
            <Input id="url" value={this.state.editItemData.url} onChange={(e) => {
              let { editItemData } = this.state;

              editItemData.url = e.target.value;

              this.setState({ editItemData });
            }} />
          </FormGroup>

        </ModalBody>
        <ModalFooter>
          <Button color="primary" onClick={this.updateItem.bind(this)}>Update Item</Button>{' '}
          <Button color="secondary" onClick={this.toggleEditItemModal.bind(this)}>Cancel</Button>
        </ModalFooter>
      </Modal>


        <Table>
          <thead>
            <tr>
              <th>Title</th>
              <th>description</th>
              <th>URL</th>
              <th>Actions</th>
            </tr>
          </thead>

          <tbody>
            {items}
          </tbody>
        </Table>
      </div>
    );
  }
}

Here is my code to update, when I click on the Add Item modal, it opens up but when typing info into it I get an error: TypeError: Cannot create property ‘title’ on boolean ‘true’, so how or where did I miss something?

Can you provide a bit more detail here? What kind of failure are you seeing? Are there any errors showing in the browser console?

Also, do you have the code for the backend somewhere?

Hi Randell, thats the only error I get: TypeError: Cannot create property ‘title’ on boolean ‘true’

Here is the code from the backend:

const express = require('express');
const fileHandler = require('fs');
const app = express();
const router = express.Router();

// Middleware 
app.use(express.json());

let items = JSON.parse(fileHandler.readFileSync(`WebProject.json`));

// When the user navigates to the localhost once running the below will display
app.get('/api', (req, res) => {
    fileHandler.readFile(`WebProject.json`, 'utf8', (err, data) => {
        if (err) {
            res.send(`File not found. Please post to create the file.`);
        } else {
            // res.send(`Welcome to my first API\n\nSee below array list: \n\n${data}`);
            const parsedData = JSON.parse(data);
            res.json({ items: parsedData });
        }
    })
    // res.json({ items })
});

// Post request to post new Items to the json array
app.post('/api/add', (req, res) => {
    let newId = items[items.length - 1].id + 1;
    // Create a new object
    let newItem = Object.assign({
        id: newId,
        title: req.query.title,
        description: req.query.description,
        url: req.query.url
    });
    // Push the object into the array
    items.push(newItem);
    // The write the array to the json file
    fileHandler.writeFile(`WebProject.json`, JSON.stringify(items), (err) => {
        if (err) throw err;
        res.json(items)
        console.log(items);
    });
    // If there is no error resolve and send
    // res.send(`The item has been added to the json array list`);
});

// Delete request to delete an item by ID
app.delete('/api/:id', (req, res) => {
    // Get the ID the user inputs
    const id = req.params.id;
    // Filter the list
    const updatedItems = items.filter(item => item.id != id);
    // Find the selected item by ID
    let item = items.find((el) => el.id == id)
    console.log(item)
    // If the user enter an unknow ID tell the the ID does not exist
    if (item === undefined) {
        console.log({
            msg: `Item with the ID of ${id} is not found`
        });
        // If the ID matches, console log the updated list and tell them which ID item is about to be deleted
    } else {
        console.log(updatedItems + `is the updatedList`);
        // Do not include the item to be deleted
        items = updatedItems;
        console.log(`The item with the ID of ${id} is about to be deleted!!!`)
    }
    // Write the updated file to the json file
    fileHandler.writeFile(`WebProject.json`, JSON.stringify(items), (err) => {
        if (err) throw err;
    });
    res.send(`The chosen item is now gone!!!!`)
});

app.put("/api/:id", (req, res) => {
    // If the item being amended is not the title or description send the message
    if (!(req.query.title || !req.query.description)) {
        return res.send("Missing data...");
    }
    fileHandler.readFile(`WebProject.json`, 'utf8', (err, data) => {
        if (err) throw err;
        // Here the item is converted from a string to an object
        const parsedData = JSON.parse(data);
        // Here the itemIndex is being found using the findIndex finding the id of the item in the url in Postman
        const itemIndex = parsedData.findIndex(item => item.id === Number(req.params.id));
        console.log(itemIndex)
        // If the title is being amended change it else if the description is being changed amend the description
        // Only one of the two can be amended
        if (req.query.title) {
            parsedData[itemIndex].title = req.query.title;
        } else if (req.query.description) {
            parsedData[itemIndex].description = req.query.description;
        }
        // Write the data to the json file
        fileHandler.writeFile(`WebProject.json`, JSON.stringify(parsedData), err => {
            if (err) throw err;
            res.json(items);
        });
    });
});

// If the user navigates to a url not found, display the error
app.get("*", (req, res, next) => {
    let err = new Error(
        `Sorry! Can’t find that resource. Please check your URL\n`
    );
    err.statusCode = 404;
    next(err);
});

// If the user navigates to a url not found, display the error
app.get('*', (req, res, next) => {
    let err = new Error(`Sorry! Can’t find that resource. Please check your URL\n`);
    err.statusCode = 404;
    next(err);
});

// Specify the desired port ot the port assigned
const PORT = process.env.PORT || 3001;
//Listen on port set by either the device or port 8080 on localhost
app.listen(PORT, () => {
    console.log(`App is running on port ${PORT}`)
});

module.exports = app;

So essentially what I am trying to do is use the fron-end to update items in the json file via the back-end. Though, in the modal that opens, when I click on the title, description or url, it crashes with the error : TypeError: Cannot create property ‘title’ on boolean ‘true’

Also getting this error

TypeError: this.state.items.map is not a function

Reviewing your code now. First question is, why do you define the same state property twice with two different values? Only the second definition will hold. You first define newItemModal as:

    newItemModal: {
      title: '',
      description: '',
      url: ''
    },

then later as:

    newItemModal: false,

I believe this is the root of your problem.

I was following along with a video and this is done there, also as per my understanding is, is that the newItemModal creates an array of the data being added. Will false break it to prevent the input?

When the app loads, newItemModal is the value false and not an array.

Ah okay, so if I may, how would I rectify this?

Just to add, this is the original code I started with and got stuck so I looked up a video, not sure if I should stick to this code or the one from the video

import React, { Component } from 'react';
import './App.css';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';

class App extends Component {

  state = { items: [] }

  componentDidMount() {
    console.log("Mounted")
    axios.get("/api").then(data => this.setState(data.data));
  }

  handleChange = (event) => {
    this.setState({ [event.target.name]: event.target.value });
    console.log(event.target.name + ":", event.target.value)
  }

  handleSubmit() {
    axios.post("api/add").then(res => this.setState(res.response));
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <h1 className="h1Heading">Welcome to my Projects Page</h1>
          <br />
          <h2 className="h2Info">Below is a list of projects, which can be amended or new ones can be added as well</h2>
          <br />
          <h3 className="projectsList">Below is a list of all projects</h3>
          <ul>
            {this.state.items.length && this.state.items.map(item => //console.log(item) ||
              <li
                key={item.id}> <br />
                <strong><span role="img" aria-label="PC">💻</span> {item.title}</strong> <br />
                <span role="img" aria-label="Document">📑</span> {item.description} <br />
                <span role="img" aria-label="Link">🔗</span> {item.url}
              </li>
            )}
          </ul>
          <br />
          <br />
          <Form id="formName" onSubmit={this.handleSubmit}>
            <Form.Group>
              <Form.Label>Enter Project Info:</Form.Label>
              <br />
              <Form.Control type="text" placeholder="Enter Title" id="title" name="title" onChange={this.handleChange} />
              <br />
              <Form.Control type="text" placeholder="Enter Description" id="descriotion" name="description" onChange={this.handleChange} />
              <br />
              <Form.Control type="text" placeholder="Enter URL" id="url" name="url" onChange={this.handleChange} />
            </Form.Group>
            <Button variant="primary" type="submit">Submit</Button>
          </Form>
          <br />
          <br />
          <br />
        </header>
      </div>
    )
  }

}

export default App;

You can stick with the code for the video but you need to decide what value makes the most sense for the state property newItemModal. Should it be a Boolean value or an object. Maybe the video actually has a different property name for the Boolean which is really close to the same spelling of newItemModal?

Are you sure you did not mean to write newItemData instead of newItemModal for the following:

class App extends Component {
  state = {
    items: [],
    newItemModal: {
      title: '',
      description: '',
      url: ''
    },

Yeah, watched it again, seems to be with a few errors and amendments due to the way my API is created, see below the updated working code

import React, { Component } from 'react';
import axios from 'axios';
import { Input, FormGroup, Label, Modal, ModalHeader, ModalBody, ModalFooter, Table, Button } from 'reactstrap';

class App extends Component {
  state = {
    items: [],
    newItemData: {
      title: '',
      description: '',
      url: ''
    },
    editItemData: {
      id: "",
      title: "",
      description: "",
      url: ""
    }
  }

  componentDidMount() {
    this._refreshItems();
  }

  toggleNewItemModal() {
    this.setState({
      newItemModal: !this.state.newItemModal
    });
  }

  toggleEditItemModal() {
    this.setState({
      editItemModal: !this.state.editItemModal
    });
  }

  addItem() {
    axios.post("/api/add", this.state.newItemData).then((response) => {
      let { items } = this.state;
      items.push(response.data);
      this.setState(
        {
          items,
          newItemModal: false,
          newItemData: {
            title: '',
            description: '',
            url: ''
          }
        }, this._refreshItems()
      );
    });
  }

  updateItem() {
    let { id, title, description, url } = this.state.editItemData;
    console.log("id", id);
    axios.put("/api/" + id, {
      title,
      description,
      url
    })
      .then(response => {
        this._refreshItems();
        this.setState({
          editItemModal: false,
          editItemData: { id: "", title: "", description: "", url: "" }
        });
      });
  }

  editItem(id, title, description, url) {
    this.setState({
      editItemData: { id, title, description, url },
      editItemModal: !this.state.editItemModal
    });
  }

  deleteItem(id) {
    axios.delete(`/api/${id}`).then((response) => {
      this._refreshItems();
    });
  }

  _refreshItems() {
    console.log("Mounted")
    axios.get("/api").then(data => this.setState(data.data));
  }

  render() {
    let items = this.state.items.length && this.state.items.map((item) => {
      return (
        <tr key={item.id}>
          <td>{item.title}</td>
          <td>{item.description}</td>
          <td>{item.url}</td>
          <td>
            <Button
              color="success"
              size="sm"
              className="mr-2"
              onClick={this.editItem.bind(
                this,
                item.id,
                item.title,
                item.description,
                item.url
              )}>
              Edit
            </Button>

            <Button
              color="danger"
              size="sm"
              onClick={this.deleteItem.bind(
                this, 
                item.id
                )}>
              Delete
            </Button>

          </td>
        </tr>
      )
    });
    return (
      <div className="App container">

        <h1>Project Items App</h1>

        <Button className="my-3" color="primary" onClick={this.toggleNewItemModal.bind(this)}>Add Item</Button>

        <Modal isOpen={this.state.newItemModal} toggle={this.toggleNewItemModal.bind(this)}>
          <ModalHeader toggle={this.toggleNewItemModal.bind(this)}>Add a new Item</ModalHeader>
          <ModalBody>

            <FormGroup>
              <Label for="title">Title</Label>
              <Input id="title" value={this.state.newItemData.title} onChange={(e) => {
                let { newItemData } = this.state;
                console.log(`This here: ${this.state.newItemData.title}`)
                newItemData.title = e.target.value;
                this.setState({ newItemData });
              }} />
            </FormGroup>

            <FormGroup>
              <Label for="description">Description</Label>
              <Input id="description" value={this.state.newItemData.description} onChange={(e) => {
                let { newItemData } = this.state;
                newItemData.description = e.target.value;
                this.setState({ newItemData });
              }} />
            </FormGroup>

            <FormGroup>
              <Label for="url">url</Label>
              <Input id="url" value={this.state.newItemData.url} onChange={(e) => {
                let { newItemData } = this.state;
                newItemData.url = e.target.value;
                this.setState({ newItemData });
              }} />
            </FormGroup>

          </ModalBody>
          <ModalFooter>
            <Button color="primary" onClick={this.addItem.bind(this)}>Add Item</Button>{' '}
            <Button color="secondary" onClick={this.toggleNewItemModal.bind(this)}>Cancel</Button>
          </ModalFooter>
        </Modal>

        <Modal isOpen={this.state.editItemModal} toggle={this.toggleEditItemModal.bind(this)}>
          <ModalHeader toggle={this.toggleEditItemModal.bind(this)}>Edit a new item</ModalHeader>
          <ModalBody>

            <FormGroup>
              <Label for="title">Title</Label>
              <Input id="title" value={this.state.editItemData.title} onChange={(e) => {
                let { editItemData } = this.state;
                editItemData.title = e.target.value;
                this.setState({ editItemData });
              }} />
            </FormGroup>

            <FormGroup>
              <Label for="description">Description</Label>
              <Input id="description" value={this.state.editItemData.description} onChange={(e) => {
                let { editItemData } = this.state;
                editItemData.description = e.target.value;
                this.setState({ editItemData });
              }} />
            </FormGroup>

          </ModalBody>
          <ModalFooter>
            <Button color="primary" onClick={this.updateItem.bind(this)}>Update Item</Button>{' '}
            <Button color="secondary" onClick={this.toggleEditItemModal.bind(this)}>Cancel</Button>
          </ModalFooter>
        </Modal>


        <Table>
          <thead>
            <tr>
              <th>Title</th>
              <th>Description</th>
              <th>url</th>
              <th>Actions</th>
            </tr>
          </thead>

          <tbody>
            {items}
          </tbody>
        </Table>
      </div>
    );
  }
}

export default App;

and the changes to the API is here

const express = require('express');
const fileHandler = require('fs');
const app = express();
const router = express.Router();

// Middleware 
app.use(express.json());

let items = JSON.parse(fileHandler.readFileSync(`WebProject.json`));

// When the user navigates to the localhost once running the below will display
app.get('/api', (req, res) => {
    fileHandler.readFile(`WebProject.json`, 'utf8', (err, data) => {
        if (err) {
            res.send(`File not found. Please post to create the file.`);
        } else {
            // res.send(`Welcome to my first API\n\nSee below array list: \n\n${data}`);
            const parsedData = JSON.parse(data);
            res.json({ items: parsedData });
        }
    })
    // res.json({ items })
});

// Post request to post new Items to the json array
app.post('/api/add', (req, res) => {
    let newId = items[items.length - 1].id + 1;
    // Create a new object
    let newItem = Object.assign({
        id: newId,
        title: req.body.title,
        description: req.body.description,
        url: req.body.url
    });
    // Push the object into the array
    items.push(newItem);
    // The write the array to the json file
    fileHandler.writeFile(`WebProject.json`, JSON.stringify(items), (err) => {
        if (err) throw err;
        res.json(items)
        console.log(items);
    });
    // If there is no error resolve and send
    // res.send(`The item has been added to the json array list`);
});

// Delete request to delete an item by ID
app.delete('/api/:id', (req, res) => {
    // Get the ID the user inputs
    const id = req.params.id;
    // Filter the list
    const updatedItems = items.filter(item => item.id != id);
    // Find the selected item by ID
    let item = items.find((el) => el.id == id)
    console.log(item)
    // If the user enter an unknow ID tell the the ID does not exist
    if (item === undefined) {
        console.log({
            msg: `Item with the ID of ${id} is not found`
        });
        // If the ID matches, console log the updated list and tell them which ID item is about to be deleted
    } else {
        console.log(updatedItems + `is the updatedList`);
        // Do not include the item to be deleted
        items = updatedItems;
        console.log(`The item with the ID of ${id} is about to be deleted!!!`)
    }
    // Write the updated file to the json file
    fileHandler.writeFile(`WebProject.json`, JSON.stringify(items), (err) => {
        if (err) throw err;
    });
    res.send(`The chosen item is now gone!!!!`)
});

app.put("/api/:id", (req, res) => {
    // If the item being amended is not the title or description send the message
    console.log("id", req.params.id);
  
    if (!(req.body.title || !req.body.description)) {
      return res.send("Missing data...");
    }
    fileHandler.readFile(`WebProject.json`, "utf8", (err, data) => {
      if (err) throw err;
      // Here the item is converted from a string to an object
  
      const parsedData = JSON.parse(data);
      // Here the itemIndex is being found using the findIndex finding the id of the item in the url in Postman
      const itemIndex = parsedData.findIndex(
        item => item.id === Number(req.params.id));
  
      // If the title is being amended change it else if the description is being changed amend the description
      // Only one of the two can be amended
      if (req.body.title) {
        parsedData[itemIndex].title = req.body.title;
      } else if (req.body.description) {
        parsedData[itemIndex].description = req.body.description;
      }
      // Write the data to the json file
      fileHandler.writeFile(`WebProject.json`,JSON.stringify(parsedData),err => {
          if (err) throw err;
          res.json(items);
        }
      );
    });
  });

// If the user navigates to a url not found, display the error
app.get("*", (req, res, next) => {
    let err = new Error(
        `Sorry! Can’t find that resource. Please check your URL\n`
    );
    err.statusCode = 404;
    next(err);
});

// If the user navigates to a url not found, display the error
app.get('*', (req, res, next) => {
    let err = new Error(`Sorry! Can’t find that resource. Please check your URL\n`);
    err.statusCode = 404;
    next(err);
});

// Specify the desired port ot the port assigned
const PORT = process.env.PORT || 3001;
//Listen on port set by either the device or port 8080 on localhost
app.listen(PORT, () => {
    console.log(`App is running on port ${PORT}`)
});

module.exports = app;

Glad to see you were able to get something working.

1 Like

FYI - There should be no need for the _refreshItems method. You should already be updating the state items property with setState after performing the applicable post, put, and delete requests. Updating the state will cause a re-render of the current items in the list.

I did try without the _refreshItems but for some reason after adding a new item it doesn’t refresh or show the item untill the page is refreshed, don’t know why yet though, but will figure this out :slight_smile:

It is because you need to pass back items in the response of the add, delete, and put. Then you can use setState to update items.

:thinking: I need to figure out how to add that. In the back-end yes?

You were already doing it for the get endpoint.

True yes, had a look now. Its my front-end that needs to be set