I'm trying to learn the MERN stack, I'm trying to create a simple shopping list app.
I have a simple api to get, post and delete items, this all works using Postman when testing the api.
Locally the get works and I can get items from the db.
When I try to add to the db I get this error.
(node:28550) UnhandledPromiseRejectionWarning: ValidationError: item validation failed: name: Path `name` is required.
[0] at new ValidationError (/Users/user/Documents/_Work/cd/MERN/merntest-redux-1/node_modules/mongoose/lib/error/validation.js:30:11)
[0] at model.Document.invalidate (/Users/user/Documents/_Work/cd/MERN/merntest-redux-1/node_modules/mongoose/lib/document.js:2080:32)
[0] at p.doValidate.skipSchemaValidators (/Users/user/Documents/_Work/cd/MERN/merntest-redux-1/node_modules/mongoose/lib/document.js:1943:17)
[0] at /Users/user/Documents/_Work/cd/MERN/merntest-redux-1/node_modules/mongoose/lib/schematype.js:933:9
[0] at _combinedTickCallback (internal/process/next_tick.js:131:7)
[0] at process._tickCallback (internal/process/next_tick.js:180:9)
[0] (node:28550) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
[0] (node:28550) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
As the api works in postman I think it must be something to do the redux I'm using.
ShoppingList js
import React, {Component} from 'react';
import uuid from 'uuid';
import {connect} from 'react-redux';
import {getItems, deleteItem, addItem} from '../actions/itemActions';
class ShoppingList extends Component{
componentDidMount(){
this.props.getItems()
}
onDeleteClick = (id) => {
this.props.deleteItem(id);
}
render(){
const {items} = this.props.item
return(
<div>
<button
onClick={() => {
const name = prompt('New Item')
if(name){
this.props.addItem(name)
}
}}
>
Add Item
</button>
<ul>
{items.map(({id,name}) =>{
return(
<li key={id}>
<button
onClick={this.onDeleteClick.bind(this, id)}
>
×
</button>
{name}
</li>
)
})}
</ul>
</div>
)
}
}
const mapStateToProps = state => ({
item: state.item
})
export default connect (mapStateToProps, {getItems, deleteItem, addItem})(ShoppingList)
itemActions
import axios from 'axios';
import {GET_ITEMS, ADD_ITEM, DELETE_ITEM, ITEMS_LOADING} from '../actions/types';
export const getItems = () => dispatch =>{
dispatch(setItemsLaoding());
axios
.get('/api/items')
.then(res =>
dispatch({
type: GET_ITEMS,
payload: res.data
})
)
}
export const addItem = (name) => dispatch =>{
axios
.post('/api/items', name)
.then(res =>
dispatch({
type: ADD_ITEM,
payload: res.data
})
)
}
export const deleteItem = (id) =>{
return{
type: DELETE_ITEM,
payload: id
}
}
export const setItemsLaoding = () => {
return{
type: ITEMS_LOADING
}
}
itemReducer.js
import {GET_ITEMS, ADD_ITEM, DELETE_ITEM, ITEMS_LOADING} from '../actions/types';
const initialState = {
items: [],
loading: false
}
export default function(state = initialState, action){
switch(action.type){
case GET_ITEMS:
return{
...state,
items: action.payload,
loading:false
}
case DELETE_ITEM:
return{
...state,
items: state.items.filter(item => item.id !== action.payload)
}
case ADD_ITEM:
return{
...state,
items: [...state.items, { name: action.payload}]
}
case ITEMS_LOADING:
return{
...state,
loading:true
}
default:
return state
}
}
models/Item.js (mongoose model)
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
name:{
type: 'String',
required: true
},
Date:{
type: Date,
default: Date.now
}
})
module.exports = Item = mongoose.model('item', ItemSchema);
routes/api/items.js
const express = require('express');
const router = express.Router();
const Item = require('../../models/Item');
router.get('/', (req, res) => {
Item.find()
.then(items => res.json(items))
.catch(err => console.log(err))
});
router.post('/', (req, res) => {
const newItem = new Item({
name: req.body.name
})
newItem
.save()
.then(item => res.json(item))
.catch(err => console.log(err))
})
router.delete('/:id', (req, res) =>{
Item.findById(req.params.id)
.then(item => item.remove().then(() => res.json({success:true})))
.catch(err => err.status(404).json({success:false}))
})
module.exports = router;
Okay, I did some debugging and here is the problem, you are sending the item name as the key with no value so the API cannot understand it.
Change this code:
export const addItem = (name) => dispatch =>{
axios
.post('/api/items', name)
.then(res =>
dispatch({
type: ADD_ITEM,
payload: res.data
})
)
}
to this code:
export const addItem = name => dispatch => {
const req = {
name,
};
axios.post('/api/items', req).then(res =>
dispatch({
type: ADD_ITEM,
payload: res.data,
})
);
};
I solved this problem by adding this code to routes/api/items.js:
var bodyParser = require("body-parser");
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
Related
I am receiving the error vesselList is not a function in my homescreen that's using a redux slice
the error is in vesselList() in the dispatch in the useEffect hook please suggest if there is a better way i can reform this code.
the Slice :
vesselSlice :
export const vesselSlice = createSlice({
name: "vesselList",
initialState: {
vessels: [],
},
reducers: {
vesselList: (state, action) => {
state.value = action.payload;
},
},
});
export const {
vesselList,
} =
(keyword = "") =>
async (dispatch) => {
try {
dispatch({ type: VESSEL_LIST_REQUEST });
const { data } = await axios.get(
"http://127.0.0.1:8000/api/vessels/info"
);
dispatch({
type: VESSEL_LIST_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: VESSEL_LIST_FAIL,
payload:
error.response && error.response.data.detail
? error.response.data.detail
: error.message,
});
}
};
export default vesselSlice.reducer;
HomeScreen.js :
function HomeScreen() {
const dispatch = useDispatch();
const Listvessel = useSelector((state) => state.vesselList);
const { error, loading, vessels } = Listvessel;
useEffect(() => {
dispatch(vesselList());
}, [dispatch]);
return (
<div>
Fleet vessels :
<div className="fleet-vessels-info">
{vessels.map((vessel) => (
<VesselCard vessel={vessel} />
))}
</div>
</div>
);
}
export default HomeScreen;
Because vesselList not yet export, why you not use createAsyncThunk to fecth url and dispatch to redux store?
I have an app built with React, Redux that pulls data from a RESTful service sitting in my local. I tested the implementation with dummy data and works fine. However, when I hook up the async service the calls result in havoc with the below error:
Here is the code
reducer.js
import {
LOAD_ALL_PRODUCTS_SUCCESS,
LOAD_ALL_PRODUCTS_REQUEST,
LOAD_ALL_PRODUCTS_FAIL,
LOAD_PRODUCT_REQUEST,
LOAD_PRODUCT_SUCCESS,
LOAD_PRODUCT_FAIL,
} from './actions';
export const productData = (state = { loader: {}, products: [] }, action) => {
const { type, payload } = action;
switch (type) {
case LOAD_ALL_PRODUCTS_REQUEST: {
return { loader: true, products: [] };
}
case LOAD_ALL_PRODUCTS_SUCCESS: {
return { loader: false, products: payload };
}
case LOAD_ALL_PRODUCTS_FAIL: {
return { loader: false, error: payload };
}
default:
return state;
}
};
thunk.js
import axios from 'axios';
import { mockData } from '../MockData';
import {
loadAllProductFailed,
loadAllProductRequest,
loadAllProductSuccess,
LOAD_PRODUCT_FAIL,
LOAD_PRODUCT_REQUEST,
LOAD_PRODUCT_SUCCESS,
} from './actions';
export const loadInitialProducts = () => async (dispatch) => {
try {
dispatch(loadAllProductRequest());
//this is where the issues is
const response = await axios.get('http://localhost:8080/v1/product/all');
const payload = await response.data;
console.log(payload);
dispatch(loadAllProductSuccess(payload));
} catch (error) {
dispatch(
loadAllProductFailed(
error.response && error.response.data.message
? error.response.data.message
: error.message
)
);
}
};
export const loadProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: LOAD_PRODUCT_REQUEST });
//do a axios api call for product api
dispatch({
type: LOAD_PRODUCT_SUCCESS,
payload: mockData.find(({ productId }) => productId == id),
});
} catch (error) {
dispatch({
type: LOAD_PRODUCT_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const LOAD_ALL_PRODUCTS_REQUEST = 'LOAD_PRODUCTS_REQUEST';
export const loadAllProductRequest = () => ({
type: LOAD_ALL_PRODUCTS_REQUEST,
});
export const LOAD_ALL_PRODUCTS_SUCCESS = 'LOAD_ALL_PRODUCTS_SUCCESS';
export const loadAllProductSuccess = (payload) => ({
type: LOAD_ALL_PRODUCTS_SUCCESS,
payload: payload,
});
export const LOAD_ALL_PRODUCTS_FAIL = 'LOAD_ALL_PRODUCTS_FAIL';
export const loadAllProductFailed = (error) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: error,
});
export const LOAD_PRODUCT_REQUEST = 'LOAD_PRODUCT_REQUEST';
export const loadProductRequest = () => ({
type: LOAD_ALL_PRODUCTS_FAIL,
});
export const LOAD_PRODUCT_SUCCESS = 'LOAD_PRODUCT_SUCCESS';
export const loadProductSuccess = (payload) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: payload,
});
export const LOAD_PRODUCT_FAIL = 'LOAD_PRODUCT_FAIL';
export const loadProductFailed = (error) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: error,
});
Home.js
import React, { useState, useEffect } from 'react';
import { conwayAPI } from '../ConwayAPI';
import { Container, Col, Row } from 'react-bootstrap';
import Product from './Product';
import { useDispatch, useSelector } from 'react-redux';
import _ from 'lodash';
import { loadInitialProducts } from '../app/thunk';
const Home = () => {
//local state maintained only for this component
const [filterProducts, setFilterProducts] = useState([]);
const dispatch = useDispatch();
const productList = useSelector((state) => state.productData);
const { loader, error, products } = productList;
useEffect(() => {
dispatch(loadInitialProducts());
}, [dispatch, products]);
const doSearch = (text) => {
_.isEmpty(text)
? setFilterProducts(products)
: setFilterProducts(
filterProducts.filter((product) =>
product.productName.toLowerCase().includes(text.toLowerCase())
)
);
};
return (
<Container fluid>
<Row md={7} lg={5} className='justify-content-md-center'>
{filterProducts.length &&
filterProducts.map((datam, key) => {
return (
<Col key={key}>
<Product product={datam} key={key} />
</Col>
);
})}
</Row>
</Container>
);
};
export default Home;
When I click on a Nav panel the Home.js gets called and the error starts. What can I do differently to eliminate this error?
For learning purpose I made this web app where I'm trying to implement crud operations. All works properly except UPDATE, where MongoDB record is updated but changes on the screen are not reflected till the refresh.
I'm still learning therefore not everything is crystal clear, I'm suspecting a problem in a REDUCER... or in the component mapStateToProp object...
What am I doing wrong here?
routes/api
Item.findByIdAndUpdate for sure update's db correctly, but should it also return anything so the reducer/action could react to it?
const express = require("express");
const router = express.Router();
const auth = require("../../middleware/auth");
// Item Model
const Item = require("../../models/stories");
// #route GET api/items
// #desc Get All Items
// #access Public
router.get("/", (req, res) => {
Item.find()
.sort({ date: -1 })
.then(items => res.json(items));
});
// #route PUT api/items
// #desc Update An Item
// #access Private
router.put("/:_id", auth, (req, res) => {
Item.findByIdAndUpdate(
req.params._id,
req.body,
{ new: false, useFindAndModify: false },
() => {}
);
});
module.exports = router;
reducers
import {
GET_STORIES,
ADD_STORY,
DELETE_STORY,
STORIES_LOADING,
UPDATE_STORY
} from "../actions/types";
const initialState = {
stories: [],
loading: false
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_STORIES:
return {
...state,
stories: action.payload,
loading: false
};
case DELETE_STORY:
return {
...state,
stories: state.stories.filter(story => story._id !== action.payload)
};
case ADD_STORY:
return {
...state,
stories: [action.payload, ...state.stories]
};
case UPDATE_STORY:
return {
...state,
stories: action.payload
};
case STORIES_LOADING:
return {
...state,
loading: true
};
default:
return state;
}
}
actions
import axios from "axios";
import {
GET_STORIES,
ADD_STORY,
DELETE_STORY,
UPDATE_STORY,
STORIES_LOADING
} from "./types";
import { tokenConfig } from "./authActions";
import { returnErrors } from "./errorActions";
export const getStories = () => dispatch => {
dispatch(setStoriesLoading());
axios
.get("/api/stories")
.then(res =>
dispatch({
type: GET_STORIES,
payload: res.data
})
)
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
export const addStory = story => (dispatch, getState) => {
axios
.post("/api/stories", story, tokenConfig(getState))
.then(res => {
dispatch({
type: ADD_STORY,
payload: res.data
});
})
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
export const updateStory = story => (dispatch, getState) => {
axios
.put(`/api/stories/${story.id}`, story, tokenConfig(getState))
.then(res => {
dispatch({
type: UPDATE_STORY,
payload: story
});
})
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
export const deleteStory = id => (dispatch, getState) => {
axios
.delete(`/api/stories/${id}`, tokenConfig(getState))
.then(res => {
dispatch({
type: DELETE_STORY,
payload: id
});
})
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
export const setStoriesLoading = () => {
return {
type: STORIES_LOADING
};
};
component
import React, { Component } from "react";
import {
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input
} from "reactstrap";
import { connect } from "react-redux";
import { updateStory } from "../../actions/storyActions";
import PropTypes from "prop-types";
class UpdateStoryModal extends Component {
constructor(props) {
super(props);
}
state = {
id: this.props.idVal,
modal: false,
title: this.props.titleVal,
body: this.props.bodyVal
};
static propTypes = {
isAuthenticated: PropTypes.bool
};
toggle = () => {
this.setState({
modal: !this.state.modal
});
};
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
onSubmit = e => {
e.preventDefault();
const obj = {
id: this.props.idVal,
title: this.state.title,
body: this.state.body
};
this.props.updateStory(obj);
this.toggle();
};
render() {
return (
<div>
{this.props.isAuthenticated ? (
<button
type="button"
className="btn btn-primary"
size="sm"
onClick={this.toggle}
>
Edit Story
</button>
) : (
<h4 className="mb-3 ml-4">Please log in to manage stories</h4>
)}
<Modal isOpen={this.state.modal} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>Edit story</ModalHeader>
<ModalBody>
<Form>
<FormGroup>
<Label for="story">Title</Label>
<Input
type="text"
name="title"
id="story"
onChange={this.onChange}
value={this.state.title}
/>
<Label for="story">Story</Label>
<Input
type="textarea"
name="body"
rows="20"
value={this.state.body}
onChange={this.onChange}
/>
<button
type="button"
className="btn btn-dark"
style={{ marginTop: "2rem" }}
onClick={this.onSubmit}
>
Edit story
</button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
const mapStateToProps = state => ({
story: state.story,
isAuthenticated: state.auth.isAuthenticated
});
export default connect(
mapStateToProps,
{ updateStory }
)(UpdateStoryModal);
Yes, you want to return the updated item from your MongoDB database so that you have something to work with in your reducer. It looks like you've setup your action-creator to be prepared for that type of logic. So we just need to make a couple updates:
In your express route you would want something like:
router.put("/:_id", auth, (req, res) => {
//this returns a promise
Item.findByIdAndUpdate(
req.params._id,
req.body,
{ new: false, useFindAndModify: false },
() => {}
)
.then((updatedItem) => {
res.json(updatedItem) //we capture this via our promise-handler on the action
})
.catch((error) => {
return res.status(400).json({ couldnotupdate: "could not update item"})
})
});
Then we can tap into that updated item using res.data in your action-creator promise-handler
export const updateStory = story => (dispatch, getState) => {
axios
.put(`/api/stories/${story.id}`, story, tokenConfig(getState))
.then(res => {
dispatch({
type: UPDATE_STORY,
payload: res.data
});
})
.catch(err =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
Now that you have the updated item as an action-payload, we need to update your reducer:
case UPDATE_STORY:
return {
...state,
stories: state.stories.map((story) => {
if(story._id == action.payload._id){
return{
...story,
...action.payload
} else {
return story
}
}
})
};
With that you should be able to take the updated story from your back-end and have it reflected to the front.
I am trying to create a delete button, on my surveys i have the delete button and my end api created however nothing happens when i hit delete on the survey please help.
This is where my delete button and function is.
created a new function called newDelete and passed it the survey._id but that is not working either
SurveyList.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchSurveys } from '../../actions';
import { deleteSurvey } from '../../actions';
class SurveyList extends Component {
componentDidMount() {
this.props.fetchSurveys();
}
newDelete() {
deleteSurvey('{survey._id}');
}
renderSurveys() {
return this.props.surveys.reverse().map(survey => {
return (
<div className="card darken-1" key={survey._id}>
<div className="card-content">
<span className="card-title">{survey.title}</span>
<p>
{survey.body}
</p>
<p className="right">
<button type="button" onClick={this.newDelete}>DELETE</button>
Sent On: {new Date(survey.dateSent).toLocaleDateString()}
</p>
</div>
</div>
);
});
}
render() {
return (
<div>
{this.renderSurveys()}
</div>
);
}
}
function mapStateToProps({ surveys }) {
return { surveys };
}
export default connect(mapStateToProps, { fetchSurveys })(SurveyList);
where i have my api's.
index.js
import axios from "axios";
import { FETCH_USER, FETCH_SURVEYS } from "./types";
export const fetchUser = () => async dispatch => {
const res = await axios.get("/api/current_user");
dispatch({ type: FETCH_USER, payload: res.data });
};
export const submitSurvey = (values, history) => async dispatch => {
const res = await axios.post("/api/surveys", values);
history.push("/surveys");
dispatch({ type: FETCH_USER, payload: res.data });
};
export const fetchSurveys = () => async dispatch => {
const res = await axios.get('/api/surveys');
dispatch({type: FETCH_SURVEYS, payload: res.data })
};
export const deleteSurvey = (values, history) => async dispatch => {
const res = await axios.delete('api/surveys', {params: {'_id': values}});
history.push("/surveys");
dispatch({ type: FETCH_USER, payload: res.data });
};
this is the part of the code where i am trying to just delete the surveys. any help will be greatly appreciated thanks.nothing happens no errors nor any console issues.
I create a simple case to test my actions, but my store.dispatch is not returning a promise. Can someone tell me what is wrong?
Action.js code:
export const handleSubmit = inputData =>
(dispatch) => {
axios.post(`${API_URL}/process-input/`, { input: inputData })
.then((resp) => {
dispatch({
type: 'UPDATE_OUTPUT',
payload: resp.data,
});
})
.catch((e) => {
dispatch({
type: 'UPDATE_OUTPUT',
payload: e.message,
});
});
};
And my test:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import * as actions from './../../../../src/modules/inputData/action';
import { API_URL } from './../../../../src/constants';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('InputData actions', () => {
test('Test input value update', () => {
moxios.install();
moxios.stubRequest(`${API_URL}/process-input/`, { status: 200, response: 'A nice test result' });
const store = mockStore();
return store.dispatch(actions.handleSubmit('anyData'))
.then(() => {
expect(store.getActions()).toEqual([
{ type: 'UPDATE_OUTPUTT', payload: 'A nice test result' },
]);
});
});
});
The error that is returning is: Cannot read property 'then' of undefined
Your redux action is not returning a promise. You should return the axios.post function.
export const handleSubmit = inputData =>
(dispatch) => {
return axios.post(`${API_URL}/process-input/`, { input: inputData })
.then((resp) => {
dispatch({
type: 'UPDATE_OUTPUT',
payload: resp.data,
});
})
.catch((e) => {
dispatch({
type: 'UPDATE_OUTPUT',
payload: e.message,
});
});
};