React-Redux: Action isn't properly being triggered - reactjs

I'm trying to learn the MERN stack, and I'm going through this tutorial on YouTube. I'm getting stuck in Ep. 7. The issue I think I'm having is my ADD_ITEM action is never triggered, and so the state is never updated, and I have to reload the page in order to see any items added. The DELETE_ITEM action works properly, so I suspect there may be an issue with the ADD_ITEM action being called from a form in a modal, but I'm unsure.
Picture of my Redux DevTools after refreshing the page, deleting 2 items, and trying to add 1:
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) {
console.log(action.type);
switch(action.type) {
case GET_ITEMS:
return { ...state, items: action.payload, loading: false };
case ADD_ITEM:
return { ...state, items: [action.payload, ...state] };
case DELETE_ITEM:
return { ...state,
items: state.items.filter(item => item._id !== action.payload)
};
case ITEMS_LOADING:
return {
...state,
loading: true
};
default:
return state;
}
}
ItemModal.js
import React, { Component } from 'react';
import { Button, Modal, ModalHeader, ModalBody, Form, FormGroup, Label, Input } from 'reactstrap';
import { connect } from 'react-redux';
import { addItem } from '../actions/itemActions';
class ItemModal extends Component {
state = {
modal: false,
name: ''
}
toggle = () => {
this.setState({
modal: !this.state.modal
});
}
onChange = (e) => {
this.setState({ [e.target.name]: e.target.value })
}
onSubmit = (e) => {
e.preventDefault();
const newItem = {
name: this.state.name
}
this.props.addItem(newItem);
this.toggle();
}
render() {
return(
<div>
<Button
color="dark"
style={{marginBotton: '2rem'}}
onClick={this.toggle}
>Add Item</Button>
<Modal
isOpen={this.state.modal}
toggle={this.toggle}
>
<ModalHeader
toggle={this.toggle}
>
Add To Shopping List
</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label for="item">Item</Label>
<Input
type="text"
name="name"
id="item"
placeholder="Add shopping item"
onChange={this.onChange}
/>
</FormGroup>
<Button
color="dark"
style={{marginTop: '2rem'}}
block
>Submit</Button>
</Form>
</ModalBody>
</Modal>
</div>
)
}
}
const mapStateToProps = state => ({
item: state.item
});
export default connect(mapStateToProps, { addItem })(ItemModal);
itemActions.js
import axios from 'axios';
import { GET_ITEMS, ADD_ITEM, DELETE_ITEM, ITEMS_LOADING } from './types';
export const getItems = () => dispatch => {
dispatch(setItemsLoading());
axios.get('/api/items').then(res => dispatch({
type: GET_ITEMS,
payload: res.data
}))
};
export const deleteItem = (id) => dispatch => {
axios.delete(`/api/items/${id}`).then(res => dispatch({
type: DELETE_ITEM,
payload: id
}))
};
export const addItem = (item) => dispatch => {
axios.post('/api/items', item).then(res => dispatch({
type: ADD_ITEM,
payload: res.data
}))
};
export const setItemsLoading = () => {
return {
type: ITEMS_LOADING
};
};

Related

issue in updating todo onClick checkbox

if you know react (using context api) ,you can look into my issue
this is my github repo : https://github.com/nareshkumar1201/todo-react
i am facing issue , where i wanted to update/toggle the completed property in todo list ---
present state of the app is that -one can add todo ,delete todo , and now i am working on the checkbox -on click of checkbox it should update the completed property in todo list , issue is that when i click on checkbox for the first time it is working fine but if is select/uncheck the todo for the 2nd time its not working ... i don,t know where i am going wrong ??
it will a great help if some one look into issue
import React, { Fragment, useContext } from "react";
import PropTypes from "prop-types";
import TodoContext from "../context/TodoContext";
const TodoItem = ({ todo }) => {
// console.log(todo);
const todoContext = useContext(TodoContext);
const { deleteTodo, updateTodo } = todoContext;
// const { id, todo, completed } = todo;
**const onChange = (e) => {
console.log("todoItem: update req", todo.id);
updateTodo(todo.id);
};**
const onClick = () => {
console.log("todoItem: delete req", todo.id);
deleteTodo(todo.id);
};
return (
<Fragment>
<div className="container col-12 todo-item-container">
<ul className="list-group w-100">
<li className="list-group-item">
**<input
type="checkbox"
name="todo"
// value={todo.todo}
onChange={onChange}
/>**
<span className="text-info"> {todo.todo}</span>
<button className="btn float-right ">
<i
className="fa fa-pencil-square-o text-info "
aria-hidden="true"
></i>
</button>
<button className="btn float-right" onClick={onClick}>
{" "}
<i className="fa fa-trash text-info" aria-hidden="true"></i>
</button>
</li>
</ul>
</div>
</Fragment>
);
};
TodoItem.propTypes = {
todo: PropTypes.object.isRequired,
};
export default TodoItem;
This is state:
import React, { useReducer } from "react";
import TodoContext from "./TodoContext";
import TodoReducer from "./TodoReducer";
import { v1 as uuid } from "uuid";
import {
ADD_TODO,
EDIT_TODO,
DELETE_TODO,
DISPLAY_TODOS,
UPDATE_TODO,
} from "./types";
const TodoState = (props) => {
const initialState = {
todos: [
{
id: 1,
todo: "Do cook food",
completed: false,
},
{
id: 2,
todo: "Clean Room",
completed: false,
},
{
id: 3,
todo: "Wash Car",
completed: false,
},
],
};
const [state, dispatch] = useReducer(TodoReducer, initialState);
//add todo
const addTodo = (todo) => {
todo.id = uuid();
console.log(todo);
// todo.completed = false;
dispatch({ type: ADD_TODO, payload: todo });
};
//display todo
const displayTodos = () => {
dispatch({ type: DISPLAY_TODOS, payload: state.todos });
};
//edit todo
//delete todo
const deleteTodo = (id) => {
console.log("payload id", id);
dispatch({ type: DELETE_TODO, payload: id });
};
//update todo
**const updateTodo = (id) => {
console.log("in todoState", id);
dispatch({ type: UPDATE_TODO, payload: id });
};**
return (
<TodoContext.Provider
value={{
todos: state.todos,
addTodo,
displayTodos,
deleteTodo,
updateTodo,
}}
>
{props.children}
</TodoContext.Provider>
);
};
export default TodoState;
This is reducer:
import {
ADD_TODO,
EDIT_TODO,
DELETE_TODO,
DISPLAY_TODOS,
UPDATE_TODO,
} from "./types";
// import todoContext from "./TodoContext";
const TodoReducer = (state, action) => {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload],
};
case DISPLAY_TODOS:
return {
...state,
todos: action.payload,
};
case DELETE_TODO:
return {
...state,
todos: state.todos.filter((todoObj) => todoObj.id !== action.payload),
};
**case UPDATE_TODO:
console.log("in reducer :", action.payload);
return {
...state,
todos: state.todos.map((todoObj) => {
if (todoObj.id === action.payload) {
todoObj.completed = !todoObj.completed;
}
return todoObj;
}),
};**
default:
return state;
}
};
export default TodoReducer;
Screen images:on click on checkbox for the first time -working fine
if click on 2nd todo checkbox its not working
There was issue with your return in UPDATE_TODO.
I have fixed same for you, Do test and let me know.
While making changes to the object in todos array you need to copy it using the spread operator and then make changes and then return the object.
case UPDATE_TODO:
console.log("in reducer :", action.payload);
return {
...state,
todos: state.todos.map((todoObj) => {
if (todoObj.id === action.payload) {
return { ...todoObj, completed: !todoObj.completed };
}
return todoObj;
}),
};

redux props receiving data twice

I am using redux to get the async data and response. In the below component when i post recipe and from server i get response through redux the success modal popup twice. reducer is running only once i have checked eveything, only component has problem. the problem could be with the lifecycle method.
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import {withRouter} from 'react-router-dom';
import {connect} from 'react-redux';
import * as actionCreators from '../../actions/recipe-action/index';
import { Modal, Button } from "antd";
import Spinner from '../../UI/spinner';
class PostRecipe extends Component {
state = {
url: '',
visible: false,
}
showModal = () => {
this.setState({ visible: true });
};
onChangeHandler = (e) => {
this.setState({[e.target.name]: e.target.value});
}
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: ""});
this.setState({ visible: false });
};
handleCancel = e => {
this.setState({ visible: false });
};
render() {
const { postRecipes } = this.props;
if(postRecipes.loading) {
return <Spinner />;
}else if(postRecipes.success.ok) {
// this success model popup twice after uploading the recipe
Modal.success({
content: "Recipe Uploaded"
});
}else if(postRecipes.failure.error) {
Modal.error({
title: "Error while uploading recipe",
});
}
return (
<div>
<div>
<Button type="primary" onClick={this.showModal}>
Add Recipe
</Button>
<Modal
title="Add Recipe"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<input
style={{ width: "100%", padding: "5px", fontSize: "15px" }}
type="text"
placeholder="enter the url"
name="url"
value={this.state.url}
onChange={this.onChangeHandler}
/>
</Modal>
</div>
</div>
);
}
}
const mapStateToProps = ({ postRecipeReducers }) => {
const { recipe: { post: postRecipes } } = postRecipeReducers;
return {
postRecipes
}
};
const mapStateToDispatch = dispatch => {
return {
recipe: (url) => dispatch(actionCreators.postRecipes(url))
}
}
export default withRouter(connect(mapStateToProps, mapStateToDispatch)(PostRecipe));
// my action creators
import {POST_RECIPE_LOADING, POST_RECIPE_SUCCESS, POST_RECIPE_FAILURE, POST_RECIPE_RESET} from '../types';
import {GET_RECIPE_LOADING, GET_RECIPE_SUCCESS, GET_RECIPE_FAILURE, GET_RECIPE_RESET} from '../types';
import Parse from 'parse';
export const postRecipes = (url) => async(dispatch) => {
try {
dispatch({type: POST_RECIPE_LOADING, payload: null});
const {data} = await Parse.Cloud.run('post_recipe', {url: url});
dispatch({type: POST_RECIPE_SUCCESS, payload: data});
} catch(e) {
dispatch({type: POST_RECIPE_FAILURE, payload: {message: e.message}})
}
}
export const getRecipes = () => async (dispatch) => {
try {
dispatch({type: GET_RECIPE_LOADING, payload: null});
const {data} = await Parse.Cloud.run('get_recipe');
dispatch({type: GET_RECIPE_SUCCESS, payload: data});
} catch(e) {
dispatch({type: GET_RECIPE_FAILURE, payload: {message: e.message}})
}
};
Try this:
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: "", visible: false});
};
state variable of class is a object with two keys: url and visible. You have to set both at once.
I would try implementing a constructor function to make sure that you have this bound to your local state.
In this code block,
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: ""});
this.setState({ visible: false });
};
you could set the whole state in one line like this,
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: "", visible: false});
}
I don't know that this will fix your problem. Just a bit of house keeping.

React redux proper updating record and reflecting changes on the screen

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.

React Redux PUT request failing

REACT Redux PUT request gives me following error:
"Proxy error: Could not proxy request /api/stories/5ccf12b5f6b087c2a3fcc21b from localhost:3000 to http://localhost:5002.
[1] See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (ECONNRESET)."
I made sample React Redux CRUD app based on #Brad Traversy tutorial and GET, POST, DELETE requests works fine.
API with mongoose:
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 POST api/items
// #desc Create An Item
// #access Private
router.post("/", auth, (req, res) => {
const newItem = new Item({
title: req.body.title,
body: req.body.body,
author: "Les Frazier"
});
newItem.save().then(item => res.json(item));
});
router.put("/:_id", auth, (req, res) => {
var query = { _id: req.params._id };
var update = {
_id: req.params._id,
title: req.params.title,
body: req.params.body
};
var options = { new: true, useFindAndModify: false };
Item.findByIdAndUpdate(req.params._id, { $set: update }, options);
});
// #route DELETE api/items/:id
// #desc Delete A Item
// #access Private
router.delete("/:id", auth, (req, res) => {
Item.findById(req.params.id)
.then(item => item.remove().then(() => res.json({ success: true })))
.catch(err => res.status(404).json({ success: false }));
});
module.exports = router;
Child component for updating(PUT) data that is failing
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input
} from "reactstrap";
import { connect } from "react-redux";
import { updateStory, deleteStory } from "../../actions/storyActions";
import PropTypes from "prop-types";
class UpdateStoryModal extends Component {
state = {
modal: false,
title: this.props.story.stories.find(
story => story._id === this.props.value
).title,
body: this.props.story.stories.find(story => story._id === this.props.value)
.body
};
static propTypes = {
isAuthenticated: PropTypes.bool
};
toggle = () => {
this.setState({
modal: !this.state.modal
});
};
onChange = e => {
e.target.name === "title"
? this.setState({ title: e.target.value })
: this.setState({ body: e.target.value });
};
//PUT
onSubmit = e => {
e.preventDefault();
const updateStory = {
_id: this.props.value,
title: this.state.title,
body: this.state.body
};
this.props.updateStory(updateStory);
// Close modal
this.toggle();
};
render() {
return (
<div>
{this.props.isAuthenticated ? (
<Button color="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 onSubmit={this.onSubmit}>
<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 color="dark" style={{ marginTop: "2rem" }} block>
Edit story
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
const mapStateToProps = state => ({
story: state.story,
title: state.title,
body: state.body,
isAuthenticated: state.auth.isAuthenticated
});
export default connect(
mapStateToProps,
{ updateStory, deleteStory }
)(UpdateStoryModal);
Child component that is adding(POST) data and works fine
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input
} from "reactstrap";
import { connect } from "react-redux";
import { addStory } from "../../actions/storyActions";
import PropTypes from "prop-types";
class AddStoryModal extends Component {
state = {
modal: false,
title: "",
body: ""
};
static propTypes = {
isAuthenticated: PropTypes.bool
};
toggle = () => {
this.setState({
modal: !this.state.modal
});
};
onChange = e => {
e.target.name === "title"
? this.setState({ title: e.target.value })
: this.setState({ body: e.target.value });
};
onSubmit = e => {
e.preventDefault();
const newStory = {
title: this.state.title,
body: this.state.body
};
this.props.addStory(newStory);
// Close modal
this.toggle();
};
render() {
return (
<div>
{this.props.isAuthenticated ? (
<Button
color="dark"
style={{ marginBottom: "2rem" }}
onClick={this.toggle}
>
Add 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}>Add new story</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label for="story">Title</Label>
<Input
type="text"
name="title"
id="story"
onChange={this.onChange}
/>
<Label for="story">Story</Label>
<Input
type="textarea"
name="body"
rows="20"
onChange={this.onChange}
/>
<Button color="dark" style={{ marginTop: "2rem" }} block>
Add Story
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
const mapStateToProps = state => ({
title: state.title,
body: state.body,
isAuthenticated: state.auth.isAuthenticated
});
export default connect(
mapStateToProps,
{ addStory }
)(AddStoryModal);
Reducer
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: state.stories.map(story =>
story._id === action.payload._id ? (story = action.payload) : story
)
};
case STORIES_LOADING:
return {
...state,
loading: true
};
default:
return state;
}
}
Action
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: res.data
})
)
.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
};
};
Ok so I found main problem...
I have replaced this code"
Item.findByIdAndUpdate(req.params._id, { $set: update }, options);
with this:
Item.findByIdAndUpdate(
req.params._id,
req.body,
{ new: false, useFindAndModify: false },
() => {
console.log("done");
}
);
And the record is being updated now in db. So the main problem is gone.
But whenever I refresh a page I'm still getting this:
Proxy error: Could not proxy request /api/stories/5ccf398fe278beca5efa3d23 from localhost:3000 to http://localhost:5002.
See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (ECONNRESET).

React/Redux: GET axios call causing infinite loop, onSubmit method is not causing visual output to the user

After clicking on submit button in form with addBook action nested inside, data is passed into DB, but not outputting imidiately to the screen (i have to refresh page each time to output newly added data from DB).
I tried to put my getBooks function into componentDidUpdate() lifecycle hook, but it causes infinite loop.
getBooks action
export const getBooks = () => dispatch => {
axios.get('https://damianlibrary.herokuapp.com/library')
.then(res => dispatch({
type: GET_BOOKS,
payload: res.data
}))
};
addBook action
export const addBook = book => dispatch => {
axios.post('https://damianlibrary.herokuapp.com/library', book)
.then(res => dispatch({
type: ADD_BOOK,
payload: res.data
}))
};
bookReducer
const initialState = {
books: []
}
export default function(state = initialState, action) {
switch(action.type) {
case GET_BOOKS:
return {
...state,
books: action.payload
};
case DELETE_BOOK:
return {
...state,
books: state.books.filter(book => book.book_id !== action.payload)
};
case ADD_BOOK:
return {
...state,
eventDetails: [action.payload, ...state.books]
};
default:
return state;
}
}
Form.js component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addBook, getBooks } from '../../actions/bookActions';
import './Form.css';
class Form extends Component {
state = {
name: '',
author: '',
isbn: ''
}
componentDidMount () {
this.props.getBooks();
}
onChangeHandler = (e) => {
this.setState({ [e.target.name]: e.target.value })
};
onSubmitHandler = (e) => {
const {name, author, isbn} = this.state
const newBook = {
name: name,
author: author,
isbn: isbn
}
this.props.addBook(newBook);
this.setState({
name: '',
author: '',
isbn: ''
})
e.preventDefault();
}
render() {
const { name, author, isbn } = this.state;
return (
<div className='formContainer'>
<div className='form'>
<form className='bookForm' onSubmit={this.onSubmitHandler.bind(this)}>
<div className='inputs'>
<input
type='text'
name='name'
placeholder='Book name'
onChange={this.onChangeHandler}
value={name}/>
<input
type='text'
name='author'
placeholder='Book author'
onChange={this.onChangeHandler}
value={author}/>
<input
type='text'
name='isbn'
placeholder='ISBN'
onChange={this.onChangeHandler}
value={isbn}/>
</div>
<div className='buttonSpace'>
<button>Add book</button>
</div>
</form>
</div>
</div>
)
}
}
const mapStateToProps = (state) => ({
book: state.book
});
export default connect(mapStateToProps, { addBook, getBooks })(Form);
In the reducer you should return an updated reducer object. In the ADD_BOOK you add new property eventDetails. Do you use it somewhere?
Your new reducer look that: { books: [ initial book list ], eventDetails: [initial book list and new book]}. When you change eventDetails to books in ADD_BOOK, your book list will update without additional requests.

Resources