Edit table values using react redux - javascript

I'm learning React-Redux and I'm stuck in a part of learning, I'm trying to edit the user value from table using the form that already exists, thereby taking the value of the line, and passing to the form, and also saving the user id in a variable or something of the type to update the user, I do not know how to proceed with that part
my actions: src/actions/user.js
import { ADD_USER, DELETE_USER, UPDATE_USER, FETCH_USER } from '../constants/ActionTypes';
import axios from 'axios';
const apiUrl = 'http://localhost:8080/api/v1';
export const createUser = ({ name, cpfcnpj }) => {
return (dispatch) => {
return axios.post(`${apiUrl}/users/`, { name, cpfcnpj })
.then(response => {
dispatch(createUserSuccess(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
export const createUserSuccess = (data) => {
return {
type: ADD_USER,
payload: {
_id: data._id,
name: data.name,
cpfcnpj: data.cpfcnpj
}
}
};
export const deleteUserSuccess = id => {
return {
type: DELETE_USER,
payload: {
id
}
}
}
export const deleteUser = (id) => {
console.log(id)
return (dispatch) => {
return axios.delete(`${apiUrl}/users/${id}`)
.then(response => {
dispatch(deleteUserSuccess(id))
})
.catch(error => {
throw (error);
});
};
};
export const updateUserSuccess = (data) => {
return {
type: UPDATE_USER,
payload: {
_id: data._id,
name: data.name,
cpfcnpj: data.cpfcnpj
}
}
}
export const updateUser = (id, name, cpfcnpj) => {
console.log(id, name, cpfcnpj)
return (dispatch) => {
return axios.put(`${apiUrl}/users/${id}`, { name, cpfcnpj })
.then(response => {
dispatch(updateUserSuccess(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
export const fetchUsers = (users) => {
return {
type: FETCH_USER,
users
}
};
export const fetchAllUsers = () => {
return (dispatch) => {
return axios.get(`${apiUrl}/users/`)
.then(response => {
dispatch(fetchUsers(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
my Smart Component CreateUser: src/containers/CreateUser.js
import { connect } from 'react-redux';
import { createUser, updateUser } from '../actions/user';
import NewUser from '../components/NewUser';
const mapDispatchToProps = dispatch => {
return {
onAddUser: user => {
dispatch(createUser(user));
}
};
};
export default connect(
null,
mapDispatchToProps
)(NewUser);
my Dummy Component NewUser: src/components/NewUser.js
import React, { Component } from 'react';
class NewUser extends Component {
state = {
name: '',
cpfcnpj: '',
isEdit: false
};
handleInputChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
if (!this.state.isEdit) {
if (this.state.name.trim() && this.state.cpfcnpj.trim()) {
this.props.onAddUser(this.state);
this.handleReset();
}
} else {
if (this.state.name.trim() && this.state.cpfcnpj.trim()) {
this.props.onEdit(this.state);
this.handleReset();
}
}
};
handleReset = () => {
this.setState({
name: '',
cpfcnpj: ''
});
};
render() {
return (
<div>
<form className="form-inline" onSubmit={this.handleSubmit}>
<div className="form-group margin-right">
<input
type="text"
placeholder="Name"
className="form-control"
name="name"
onChange={this.handleInputChange}
value={this.state.name}
/>
</div>
<div className="form-group margin-right">
<input
type="text"
placeholder="CPF/CNPJ"
className="form-control"
name="cpfcnpj"
onChange={this.handleInputChange}
value={this.state.cpfcnpj}>
</input>
</div>
<div className="form-group">
<button type="submit" className={this.state.isEdit ? "btn btn-success margin-right hidden" : "btn btn-success margin-right"}>
<span className="glyphicon glyphicon-plus" aria-hidden="true"></span>
Adicionar
</button>
<button type="submit" className={this.state.isEdit ? "btn btn-primary margin-right" : "btn btn-primary margin-right hidden"}>
<span className="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
Salvar
</button>
<button type="button" className="btn btn-default margin-right" onClick={this.handleReset}>
<span className="glyphicon glyphicon-erase" aria-hidden="true"></span>
Limpar
</button>
</div>
</form>
</div>
);
}
}
export default NewUser;
src/containers/UserList.js
import React from 'react';
import { connect } from 'react-redux';
import User from '../components/User';
import { deleteUser, updateUser } from '../actions/user';
function UserList({ users, onDelete, onEdit }) {
if (!users.length) {
return (
<div className="margin-top">
No Users
</div>
)
}
return (
<div className="margin-top">
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Nome</th>
<th scope="col">CPF/CNPJ</th>
</tr>
</thead>
<tbody>
{users.map(user => {
return (
<User user={user} onDelete={onDelete} key={user._id} onEdit={onEdit} />
);
})}
</tbody>
</table>
</div>
);
}
const mapStateToProps = state => {
return {
users: state.users
};
};
const mapDispatchToProps = dispatch => {
return {
onDelete: id => {
dispatch(deleteUser(id));
},
onEdit: id => {
dispatch(updateUser(id))
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList);
src/reducers/userReducer.js
import { ADD_USER, DELETE_USER, UPDATE_USER, FETCH_USER } from '../constants/ActionTypes';
export default function userReducer(state = [], action) {
switch (action.type) {
case ADD_USER:
return [...state, action.payload];
case DELETE_USER:
return state.filter(user => user._id !== action.payload.id);
case UPDATE_USER:
return [...state, action.payload];
case FETCH_USER:
return action.users;
default:
return state;
}
}
Solution I thought but could not reproduce
I need to get the value of the id of the value table item that comes as key in the UserList, pass to the onClick parameter of the user component button, and pass the value of the id when I click edit in some table item to the Form in NewUser, in order to be able to edit the table item using onEdit in NewUser.
Stucked in solution ma_dev_15
I created a const initialState, with current user, but my userReducer State just looks the users,
src/recuders/userReducer.js
import { ADD_USER, DELETE_USER, UPDATE_USER, UPDATE_CURRENT_USER, FETCH_USER } from '../constants/ActionTypes';
const initialState = {
users: [],
currentUser: {},
}
export default function userReducer(state = initialState, action) {
switch (action.type) {
case ADD_USER:
return [...state, action.payload];
case DELETE_USER:
return state.filter(user => user._id !== action.payload.id);
case UPDATE_USER:
return updateObject(state, action)
case UPDATE_CURRENT_USER:
return [...state, action.currentUser];
case FETCH_USER:
return action.users;
default:
return state;
}
}
function updateObject(array, action) {
return array.map((item, index) => {
if (item._id !== action.payload._id) {
return item
}
return {
...item,
...action.payload
}
})
}
export reducers
src/reducers/index.js
import { combineReducers } from 'redux';
import users from './userReducer';
import currentUser from './userReducer';
export default combineReducers({
users: users,
currentUser: currentUser
});
user actions: src/actions/user.js
//Ommited
export const updateCurrentUserSuccess = (currentUser) => {
return {
type: UPDATE_CURRENT_USER,
currentUser
}
}
export const updateCurrentUser = (id) => {
return (dispatch) => {
return axios.get(`${apiUrl}/users/${id}`)
.then(response => {
dispatch(updateCurrentUserSuccess(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
//Ommited
I make my UserList connected to CreateUser
src/components/containers/UserList
import React from 'react';
import { connect } from 'react-redux';
import User from '../components/User';
import { deleteUser, updateCurrentUser } from '../actions/user';
import NewUser from '../components/NewUser';
function UserList({ users, onDelete, onEditUser }) {
if (!users.length) {
return (
<div className="margin-top">
No Users
</div>
)
}
return (
<div className="margin-top">
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Nome</th>
<th scope="col">CPF/CNPJ</th>
</tr>
</thead>
<tbody>
{users.map(user => {
return (
<User user={user} onDelete={onDelete} onEditUser={onEditUser} key={user._id} />
);
})}
</tbody>
</table>
</div>
);
}
const mapStateToProps = state => {
return {
users: state.users
};
};
const mapDispatchToProps = dispatch => {
return {
onDelete: id => {
dispatch(deleteUser(id));
},
onEditUser: (id) => {
dispatch(updateCurrentUser(id))
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList, NewUser);
And when I click in Edit, and try to see console.log(Store.getState) in NewUser.js just returns me all users, I don't have currentUser.

You can create userReducer state like this:
const initialState = {
users: [],
currentUser :{},
}
export default function userReducer(state = initialState, action) {
switch (action.type) {
case ADD_USER:
return [...state, action.payload];
case DELETE_USER:
return state.filter(user => user._id !== action.payload.id);
case UPDATE_USER:
return [...state, action.payload];
case FETCH_USER:
return action.users;
default:
return state;
}
}
After that let me tell you few simple steps:
On clicking edit button pass userId and corres to that action update
the currentUser in userReducer.
Make the NewUser component connected component and get the currentUser from store and set to the input fields.
On successfull edit update the currentUser as empty and update users list
I hope you got what I mean. Let me know if you stuck somewhere.

Related

Error in Cart Page while updating product quantity

Please help me with my REACT problem. On the cart page, while I try to increase the quantity or decrease the number of quantity products, I get this type of error. I have attached the error in the Image. Please see the Image.
Here's My Code:
Cart.js. Trying to update the product quantity on the cart page. But getting errors as you see in the picture.
import React, { Fragment } from 'react';
import "./Cart.css";
import CartItemCard from "./CartItemCard";
import { useSelector, useDispatch } from 'react-redux'
import { addItemsToCart } from '../../action/cartAction';
const Cart = () => {
const dispatch = useDispatch();
const { cartItems } = useSelector((state) => state.cart);
const increaseQuantity = (id, quantity, stock) => {
const newQty = quantity + 1 ;
if(stock <= quantity) {
return;
}
dispatch(addItemsToCart(id, newQty));
};
const decreaseQuantity = (id, quantity) => {
const newQty = quantity - 1 ;
if (1 >= quantity) {
return;
}
dispatch(addItemsToCart(id, newQty));
};
return (
<Fragment>
<div className="cartPage">
<div className="cartHeader">
<p>Product</p>
<p>Quantity</p>
<p>Subtotal</p>
</div>
{cartItems && cartItems.map((item) => (
<div className="cartContainer" key={item.product} >
<CartItemCard item={item} />
<div className="cartInput">
<button onClick={() => decreaseQuantity(item.product, item.quantity)} > - </button>
<input type="number" readOnly value={item.quantity} />
<button onClick={() => increaseQuantity(item.product, item.quantity, item.stock)} > + </button>
</div>
<p className="cartSubtotal">{`$${item.price*item.quantity}`}</p>
</div>
))}
<div className="cartGrossTotal">
<div></div>
<div className="cartGrossTotalBox">
<p>Gross Total</p>
<p>{`$600`}</p>
</div>
<div></div>
<div className="checkOutBtn">
<button>Check Out</button>
</div>
</div>
</div>
</Fragment>
);
};
export default Cart;
Here's the Action:
cartAction.js
import {ADD_TO_CART} from "../constants/cartConstants";
import axios from "axios";
export const addItemsToCart = (id, quantity) => async (dispatch,getState) => {
const { data } = await axios.get(`/api/v1/product/${id}`);
dispatch({
type: ADD_TO_CART,
payload: {
product: data.product._id,
name: data.product.name,
price: data.product.price,
image: data.product.images[0].url,
stock: data.product.Stock,
quantity,
},
});
localStorage.setItem("cartItems",JSON.stringify(getState().cart.cartItems));
};
Cart Reducer Code.
CartReducer:
import {ADD_TO_CART} from "../constants/cartConstants";
export const cartReducer = (state = { cartItems: [] }, action ) => {
switch (action.type) {
case ADD_TO_CART:
const item = action.payload;
const isItemExist = state.cartItems.find(
(i) => i.product === item.product
);
if(isItemExist) {
return {
...state,
cartItems: state.cartItems.map((i) =>
i.product === isItemExist.product ? true : i
),
};
} else {
return{
...state,
cartItems: [...state.cartItems, item],
};
}
default:
return state;
}
};
React Store.
Store:
import { createStore,combineReducers,applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "#redux-devtools/extension";
import { productDetailsReducer, producReducer } from "./reducers/productReducer";
import { forgotPasswordReducer, profileReducer, userReducer } from "./reducers/userReducer";
import { cartReducer } from "./reducers/cartReducer";
const reducer = combineReducers({
products: producReducer,
productDetails: productDetailsReducer,
user: userReducer,
profile: profileReducer,
forgotPassword: forgotPasswordReducer,
cart: cartReducer,
});
let initialState = {
cart: {
cartItems: localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [],
}
};
const middleware = [thunk]
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware)
));
export default store;
Thanks, everyone for your comments and view.
I got the solution. Here's in the CartReducer code I added true instead of the item. this is the solution. Thank you.
if(isItemExist) {
return {
...state,
cartItems: state.cartItems.map((i) =>
i.product === isItemExist.product ? item : i
),
}
};

How to combine the previous data with the new data in React and display it at the same time

I'm newbie to Reactjs. The problem I'm encountered:
I want to capture and display new data when I scroll down
I take the data every time I go down and save it in Redax, but because it has pagination, it only shows 10 and does not add to the previous one.
this is my code
import { memo, useEffect, useState } from "react";
import Collapse from "#kunukn/react-collapse";
import CustomHeader from "SharedComponents/CustomHeader";
import InfiniteScroll from "react-infinite-scroller";
import "./styles.scss";
import Item from "./Components/Item/Item";
import { dispatchItemToRedux, getListServiceTransactions } from "Redux/Actions";
import { useDispatch, useSelector } from "react-redux";
import CircleLoading from "SharedComponents/Loading/CircleLoading";
import EmptyList from "./Components/EmptyList";
import ReducerTypes from "Redux/Types/ReducerTypes";
const Page = () => {
const ListServiceTransactions = useSelector(
(state) => state.app.ListServiceTransactions
);
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const [type, setType] = useState("");
const [open, setOpen] = useState(false);
const transactionItems = ListServiceTransactions?.items?.data;
const transactionPagination = ListServiceTransactions?.items;
const typeFilters = ListServiceTransactions?.type_filters;
const resultFilters = [];
useEffect(() => {
setLoading(true);
dispatch(getListServiceTransactions(null, 1));
setLoading(false);
}, []);
const handleLoadMore = () => {
const {
total,
per_page,
current_page,
next_page_url,
} = transactionPagination;
if (total > per_page && next_page_url) {
dispatch(getListServiceTransactions(type, current_page + 1)).then(
(res) => {
transactionItems.push(res);
}
);
}
};
const clickFilter = (value) => {
dispatch(getListServiceTransactions(type, 1));
setLoading(true);
setOpen(false);
setLoading(false);
};
return (
<div className="list-transactions-screen">
<CustomHeader title="لیست تراکنش های خدمات" />
<div className="p-4">
<div className="box-filter-tarnsactions mb-3">
<div className="button-open-collapse">
<div
className={`box-value-filter ${
width && open == true ? "animation-filter" : null
}`}
>
{typeTitle}
</div>
<button className="btn-filter-tarnsactions" onClick={OpenBox}>
||
</button>
</div>
<Collapse
isOpen={open}
transition="height 400ms cubic-bezier(0.4, 0, 0.2, 1)"
>
{resultFilters
? resultFilters.map((typeItem) => {
return (
<p
className="m-0 my-2 p-2 bg-white border rounded"
onClick={() => clickFilter(typeItem)}
>
{typeItem.label}
</p>
);
})
: null}
</Collapse>
</div>
<div className="all-tarnsactions-list">
{!loading ? (
transactionItems?.length > 0 ? (
transactionItems ? (
<InfiniteScroll
pageStart={1}
loadMore={handleLoadMore}
hasMore={true}
threshold={200}
loader={
!transactionPagination.next_page_url &&
transactionPagination.next_page_url === null ? null : (
<CircleLoading key={0} />
)
}
>
{transactionItems.map((item) => {
return <Item item={item} />;
})}
</InfiniteScroll>
) : (
<CircleLoading />
)
) : (
<EmptyList />
)
) : (
<CircleLoading />
)}
</div>
</div>
</div>
);
};
export default memo(Page);
enter code here
this is my action
function getListServiceTransactions(
type: any = null,
page: number = 1,
) {
return function (dispatch: ReduxDispatch, getState: ReduxGetState) {
return new Promise(function (resolve, reject) {
const params = {
type: type,
page: page,
};
axios({
method: "GET",
baseURL: Api.PUBLIC_API_V2,
url: "other-services/transaction/list",
params: params,
})
.then((res) => {
dispatch(
dispatchItemToRedux({
type: ReducerTypes.LIST_SERVICE_TRANSACTIONS,
payload: res.data.data,
})
);
resolve(res);
console.log("raft to action v req zad v in response", res);
})
.catch((err) => {
reject(err);
});
});
};
}
this is my reducer
function appReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case ReducerTypes.SET_STEPPER:
return { ...state, activeStep: action.payload };
case ReducerTypes.TOGGLE_BUTTON_SPINNER:
return { ...state, showButtonSpinner: action.payload.value };
case ReducerTypes.LOCATION_PERMISSION:
return { ...state, locationPermission: action.payload };
case ReducerTypes.TOGGLE_LOADING:
return { ...state, showLoading: action.payload.value };
case ReducerTypes.TOGGLE_LOADING_EVERLAY:
return { ...state, showLoadingEverlay: action.payload.value };
case ReducerTypes.TOGGLE_SHOW_SIDEBAR:
return { ...state, showSidebar: action.payload.value };
case ReducerTypes.TOGGLE_SHOW_NOTIFICATION:
return { ...state, showNotification: action.payload.value };
case ReducerTypes.TOAST_MESSAGE_MODAL:
return {
...state,
toastMessage: { ...action.payload.toastMessage },
};
case ReducerTypes.MAIN_PAGE_DATA:
return {
...state,
mainPageState: action.payload,
};
case ReducerTypes.CAMPAIGN_BUSINESSES_DATA:
return {
...state,
campaignBusinessesData: action.payload,
};
case ReducerTypes.CAMPAIGN_BUSINESSES_ITEMS:
return {
...state,
campaignBusinessesItems: action.payload,
};
case ReducerTypes.LOADING_PINS:
return { ...state, loadingPins: action.payload.value };
case ReducerTypes.APP_CATEGORIES:
return {
...state,
categories: action.payload.categories,
structuredCategories: action.payload.structuredCategories,
};
case ReducerTypes.APP_DISCOUNTED_BUSINESS:
return {
...state,
discountedBusinesses: action.payload.discountedBusinesses,
};
**case ReducerTypes.LIST_SERVICE_TRANSACTIONS:
return {
...state,
ListServiceTransactions: action.payload,
};**
case ReducerTypes.FAQ_LIST:
return { ...state, faqList: action.payload.faqList };
default:
return state;
}
}
export default appReducer;
I just need to put the binaries together and handle the rest of the work myself
I think what you need to do is to merge the old state together with the new one in the reducer, I am not exactly sure which key you mean, but I assume ListServiceTransactions, if that's the case, then in your reducer file, it should be:
**case ReducerTypes.LIST_SERVICE_TRANSACTIONS:
return {
...state,
// Combining previous state with the new one
ListServiceTransactions: [
...state.ListServiceTransactions,
...action.payload
],
};**

Issue with Increment and Decrement Component

I'm pretty new to react, and I am trying to make an Increment and decrement component in in my cart screen. The issue that I'm having is that I'm struggling to get it to work and to only add as many items as are contained within the countInStock stored in my database.
Below, I have included what i have so far. I would really appreciate any help or advice on how to do this.
Note: The handlers associated with the increment and decrement component are removeItemFromCart and addToCart
CartScreen.js
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { addToCart, removeFromCart, removeItemFromCart } from '../actions/cartActions';
import MessageBox from '../components/MessageBox';
export default function CartScreen(props) {
const productId = props.match.params.id;
const qty = props.location.search
? Number(props.location.search.split('=')[1])
: 1;
const cart = useSelector((state) => state.cart);
const { cartItems } = cart;
const dispatch = useDispatch();
const addToCartHandler = (productId, qty) =>{
useEffect(() => {
if (productId && countInStock > qty) {
dispatch(addToCart(productId, qty));
}
}, [dispatch, productId, qty]);
}
const removeItemFromCartHandler = (id) => {
dispatch(removeItemFromCart(id));
};
const removeFromCartHandler = (id) => {
// delete action
dispatch(removeFromCart(id));
};
const checkoutHandler = () => {
props.history.push('/signin?redirect=shipping');
};
return (
<div className="row top">
<div className="col-2">
<h1>Shopping Cart</h1>
{cartItems.length === 0 ? (
<MessageBox>
Cart is empty. <Link to="/body">Go Shopping</Link>
</MessageBox>
) : (
<ul>
{cartItems.map((item) => (
<li key={item.product}>
<div className="row">
<div className="min-30">
<Link to={`/product/${item.product}`}>{item.name}</Link>
</div>
<div>
<button
onClick={() =>
addToCartHandler(item, item.qty - 1)
}
variant="light"
disabled={item.qty === 1}
>
<i className="fas fa-minus-circle"></i>
</button>{' '}
<span>{item.qty}</span>{' '}
<button
variant="light"
onClick={() =>
removeItemFromCartHandler(item, item.qty + 1)
}
disabled={item.qty === item.countInStock}
>
<i className="fas fa-plus-circle"></i>
</button>
</div>
<div>
<button
type="button"
onClick={() => removeFromCartHandler(item.product)}
>
Delete
</button>
</div>
</div>
</li>
))}
</ul>
)}
</div>
</div>
);
}
CartActions.js
import Axios from 'axios';
import {
CART_ADD_ITEM,
CART_REMOVE_ITEM,
CART_REMOVER_ITEM,
CART_SAVE_SHIPPING_ADDRESS,
CART_SAVE_PAYMENT_METHOD,
} from '../constants/cartConstants';
export const addToCart = (productId, qty) => async (dispatch, getState) => {
const { data } = await Axios.get(`/api/products/${productId}`);
dispatch({
type: CART_ADD_ITEM,
payload: {
name: data.name,
image: data.image,
price: data.price,
profit: data.profit,
countInStock: data.countInStock,
product: data._id,
seller: data.seller,
qty,
},
});
localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems));
};
export const removeItemFromCart = (productId) => (dispatch, getState) => {
dispatch({ type: CART_REMOVER_ITEM, payload: productId });
localStorage.setItem('cartItems', JSON.stringify(getState().cart.cartItems));
};
CartReducers.js
import {
CART_ADD_ITEM,
CART_EMPTY,
CART_REMOVE_ITEM,
CART_REMOVER_ITEM,
CART_SAVE_PAYMENT_METHOD,
CART_SAVE_SHIPPING_ADDRESS,
} from '../constants/cartConstants';
export const cartReducer = (state = { cartItems: [] }, action) => {
switch (action.type) {
case CART_ADD_ITEM:
const item = action.payload;
const existItem = state.cartItems.find((x) => x.product === item.product);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.product === existItem.product ? item : x
),
};
} else {
return { ...state, cartItems: [...state.cartItems, item] };
}
case CART_REMOVE_ITEM:
return {
...state,
cartItems: state.cartItems.filter((x) => x.product !== action.payload),
};
case CART_REMOVER_ITEM:
return {
...state,
cartItems: state.cartItems.filter((x) => x.product !== action.payload),
};
case CART_SAVE_SHIPPING_ADDRESS:
return { ...state, shippingAddress: action.payload };
case CART_SAVE_PAYMENT_METHOD:
return { ...state, paymentMethod: action.payload };
case CART_EMPTY:
return { ...state, cartItems: [] };
default:
return state;
}
};

React view only gets fresh data on page reload?

I'm building a simple campgrounds CRUD app to get some practice with the MERN stack and Redux.
Adding a campground is working fine. I'm routing to the campgrounds list page after adding a campground. But unless I reload the page, fresh data isn't retrieved.
I figured it has something to do with React's lifecycle methods.
Code:
manageCampground.js
import React, { Component } from 'react';
import TextField from '#material-ui/core/TextField';
import Card from '#material-ui/core/Card';
import Button from '#material-ui/core/Button';
import '../../styles/addCampground.css';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
actionAddCampground,
getCampgroundDetails,
actionUpdateCampground
} from './actions/campgroundActions';
class AddCampground extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
description: '',
cost: ''
};
}
componentDidMount() {
const campground = this.props.campground;
if (campground._id) {
this.props.getCampgroundDetails(campground._id);
this.setState({
name: campground.name,
description: campground.description,
cost: campground.cost
});
}
}
handleChange = e => {
const { name, value } = e.target;
this.setState({
[name]: value
});
};
addCampground = () => {
const name = this.state.name;
const description = this.state.description;
const cost = this.state.cost;
this.props.actionAddCampground({
name,
description,
cost
});
this.props.history.push('/home');
console.log('Campground added successfully');
};
updateCampground = () => {
const name = this.state.name;
const description = this.state.description;
const cost = this.state.cost;
this.props.actionUpdateCampground({
name,
description,
cost
});
this.props.history.push('/home');
console.log('Updated successfully');
};
render() {
console.log(this.props);
return (
<Card className="add-campground-card">
<TextField
name="name"
className="textfield"
label="Campground name"
variant="outlined"
value={this.state.name}
onChange={e => this.handleChange(e)}
/>
<TextField
name="description"
className="textfield"
label="Campground description"
variant="outlined"
value={this.state.description}
onChange={e => this.handleChange(e)}
/>
<TextField
name="cost"
className="textfield"
type="number"
label="Campground cost"
variant="outlined"
value={this.state.cost}
onChange={e => this.handleChange(e)}
/>
{!this.props.campground._id ? (
<Button
variant="contained"
color="primary"
onClick={this.addCampground}>
Add Campground
</Button>
) : (
<Button
variant="contained"
color="primary"
className="update-campground-btn"
onClick={this.updateCampground}>
Update Campground
</Button>
)}
</Card>
);
}
}
const mapStateToProps = state => {
return {
campground: state.campgroundList.singleCampground || ''
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
actionAddCampground,
getCampgroundDetails,
actionUpdateCampground
},
dispatch
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddCampground);
campgroundList.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getAllCampgrounds } from './actions/campgroundActions';
import Header from '../common/Header';
import Card from '#material-ui/core/Card';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import '../../styles/landingPage.css';
class CampgroundLanding extends Component {
componentDidMount() {
this.props.getAllCampgrounds();
console.log('From component did mount');
}
render() {
const { campgrounds } = this.props;
return (
<>
<Header />
{campgrounds.map(campground => (
<Card className="campground-card" key={campground._id}>
<CardActionArea>
<CardContent>
<Typography gutterBottom variant="h5"
component="h2">
{campground.name}
</Typography>
<Typography variant="body2" color="textSecondary"
component="p">
{campground.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Link
style={{ textDecoration: 'none', color: 'white' }}
to={`/campgrounds/${campground._id}`}>
<Button size="small" color="primary">
View Details
</Button>
</Link>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
))}
<Link
style={{ textDecoration: 'none', color: 'white' }}
to="/campgrounds/add">
<Button color="primary">Add Campground</Button>
</Link>
</>
);
}
}
const mapStateToProps = state => {
return {
campgrounds: state.campgroundList.campgrounds
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
getAllCampgrounds
},
dispatch
);
};
export default connect(
mapStateToProps,
null
)(CampgroundLanding);
campgroundActions.js
import {
GET_ALL_CAMPGROUNDS,
ADD_CAMPGROUND,
GET_CAMPGROUND_DETAILS,
EDIT_CAMPGROUND
} from '../actionTypes/types';
import axios from 'axios';
const API_URL = `http://localhost:5000/api`;
export const getAllCampgrounds = () => {
return dispatch => {
axios
.get(`${API_URL}/campgrounds`)
.then(res => {
dispatch({
type: GET_ALL_CAMPGROUNDS,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const actionAddCampground = campground => {
return dispatch => {
axios
.post(`${API_URL}/campgrounds`, campground)
.then(res => {
console.log(res);
dispatch({
type: ADD_CAMPGROUND,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const getCampgroundDetails = id => {
return dispatch => {
axios
.get(`${API_URL}/campgrounds/${id}`)
.then(res => {
dispatch({
type: GET_CAMPGROUND_DETAILS,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const actionUpdateCampground = id => {
return dispatch => {
axios
.put(`${API_URL}/campgrounds/${id}`)
.then(res => {
console.log(res);
dispatch({
type: EDIT_CAMPGROUND,
payload: res
});
})
.catch(err => console.log(err));
};
};
campgroundReducers.js
import {
GET_ALL_CAMPGROUNDS,
ADD_CAMPGROUND,
GET_CAMPGROUND_DETAILS,
EDIT_CAMPGROUND
} from '../actionTypes/types';
const initialState = {
campgrounds: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_ALL_CAMPGROUNDS:
const { campgroundList } = action.payload.data;
state.campgrounds = campgroundList;
return {
...state
};
case ADD_CAMPGROUND:
const { campground } = action.payload.data;
return {
...state,
campground
};
case GET_CAMPGROUND_DETAILS:
const { singleCampground } = action.payload.data;
return { ...state, singleCampground };
case EDIT_CAMPGROUND:
const { editedCampground } = action.payload.data;
return { ...state, editedCampground };
default:
return state;
}
};
If I'm using componentDidUpdate, it's leading to infinite loop.
componentDidUpdate(prevProps) {
if (prevProps.campgrounds !== this.props.campgrounds) {
this.props.getAllCampgrounds();
}
}
I know I'm going wrong somewhere, but I can't figure out where.
You have to fix your componentDidUpdate method to avoid this infinity loop. Your are trying to compare objects via === method which will always fail according to this example:
const a = {key: 1}
const b = {key: 1}
console.log(a === b); // false
You could use for example isEqual method from lodash module to compare objects like you want https://lodash.com/docs/4.17.15#isEqual
console.log(_.isEqual({key: 1}, {key: 1})) // true
console.log({key: 1} === {key: 1}) // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Actually why do you want refresh data in this case? When you add object with success into the store this will refresh your component so you don't have to request for fresh data.

createStore not changing from initial state to Action proved value

Im trying to get access a variable called isSuperAdmin, It basically tells me if the logged in user is a super admin or not allowing me to disable some features.
I currently have no access to the variable in the current page however my redux action is showing it as being there, I think I may have configured something incorrectly, as of now my code doesn't change from the initial state value of null to the bool value isSuperUser. Here is the page that I am trying to use this variable.
import React, { PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import { connect } from 'react-redux';
import Modal from '../Modal';
import Summary from '../Summary';
import s from './BookingDetailsModal.scss';
import AmendConsumerDetails from './AmendConsumerDetails';
import ChangeBookingSession from './ChangeBookingSession';
import payReservationCashActionCreator from '../../actions/payReservationCash';
import payReservationCardActionCreator from '../../actions/payReservationCard';
import payRestActionCreator from '../../actions/payRest';
import refundCashActionCreator from '../../actions/refundCash';
import cancelReservationActionCreator from '../../actions/cancelReservation';
import formatPrice from '../../../../../core/formatPrice';
import {
BOXOFFICE_HIDE_BOOKING_DETAILS,
BOXOFFICE_SET_BOOKING_DETAILS_ACTION_TYPE,
resendConfirmationEmail as resendConfirmationEmailActionCreator,
} from '../../actions';
function renderActionButtons({
isSuperAdmin,
setActionType,
resendConfirmationEmail,
order: {
type: orderType,
paid: orderPaid,
amount: orderAmount,
refundedAt: orderRefundedAt,
canceledAt: orderCanceledAt,
sessionId,
},
isCreatingPayment,
payReservationCard,
payReservationCash,
payRest,
refundCash,
cancelReservation,
}) {
debugger;
return (
<div className={s.buttonsContainer}>
<div className={s.buttonsContainer}>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
setActionType('AMEND_CONSUMER_DETAILS');
}}
>Amend consumer details</button>
</div>
{ sessionId ?
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
setActionType('CHANGE_SESSION');
}}
>Move to another session</button>
</div> : null
}
<div className={s.buttonContainer}>
<button disabled>Amend tickets or products</button>
</div>
{ orderType === 'reservation' && isCreatingPayment && !orderPaid ?
<div>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
payReservationCash();
}}
>Pay Reservation CASH</button>
</div>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
payReservationCard();
}}
>Pay Reservation CARD</button>
</div>
</div> :
null
}
{ orderType === 'deposit' && isCreatingPayment && !orderPaid ?
<div>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
payRest('CASH');
}}
>Pay Rest CASH</button>
</div>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
payRest('CARD');
}}
>Pay Rest CARD</button>
</div>
</div> :
null
}
{ !orderRefundedAt && orderPaid ?
<div className={s.buttonContainer}>
<button
disabled={isSuperAdmin}
onClick={(e) => {
e.preventDefault();
refundCash(orderAmount);
}}
>Refund CASH, {formatPrice(orderAmount)}</button>
</div> : null
}
{ orderCanceledAt === null && orderType === 'reservation' ?
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
cancelReservation();
}}
>Cancel Reservation</button>
</div> : null
}
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
resendConfirmationEmail();
}}
>Resend confirmation email</button>
</div>
</div>
</div>
);
}
renderActionButtons.propTypes = {
isSuperAdmin: PropTypes.bool.isRequired,
setActionType: PropTypes.func.isRequired,
resendConfirmationEmail: PropTypes.func.isRequired,
order: PropTypes.shape({
type: PropTypes.string.isRequired,
paid: PropTypes.bool.isRequired,
sessionId: PropTypes.string.isRequired,
amount: PropTypes.number.isRequired,
// reservationPaidCashAt: PropTypes.string.isRequired,
// reservationPaidCardAt: PropTypes.string.isRequired,
}).isRequired,
payReservationCard: PropTypes.func.isRequired,
payReservationCash: PropTypes.func.isRequired,
payRest: PropTypes.func.isRequired,
isCreatingPayment: PropTypes.bool.isRequired,
refundCash: PropTypes.func.isRequired,
cancelReservation: PropTypes.func.isRequired,
};
const components = {
AMEND_CONSUMER_DETAILS: AmendConsumerDetails,
CHANGE_SESSION: ChangeBookingSession,
};
function renderAction(actionType, props) {
const Component = components[actionType];
return <Component {...props} />;
}
function BookingDetailsModal(props) {
const { hideOrderDetails, orderId, bookingDetailsActionType } = props;
return (
<Modal onClose={hideOrderDetails}>
<div className={s.container}>
<div className={s.summaryContainer}>
<Summary orderId={orderId} withEdits={false} />
</div>
<div className={s.actionsContainer}>
{bookingDetailsActionType ?
renderAction(bookingDetailsActionType, props) :
renderActionButtons(props)
}
</div>
</div>
</Modal>
);
}
BookingDetailsModal.propTypes = {
orderId: PropTypes.string.isRequired,
hideOrderDetails: PropTypes.func.isRequired,
bookingDetailsActionType: PropTypes.oneOf([
'AMEND_CONSUMER_DETAILS',
]),
};
const mapStateToProps = (state, { orderId }) => (
{
ui: { bookingDetailsActionType },
ui: { isSuperAdmin },
orders: {
data: { [orderId]: order },
edits: { [orderId]: orderEdits },
},
}
) => ({
bookingDetailsActionType,
isSuperAdmin,
order,
isCreatingPayment: orderEdits.isCreatingPayment,
});
const mapDispatchToProps = (dispatch, { orderId }) => ({
hideOrderDetails: () => dispatch({ type: BOXOFFICE_HIDE_BOOKING_DETAILS }),
setActionType: actionType =>
dispatch({ type: BOXOFFICE_SET_BOOKING_DETAILS_ACTION_TYPE, actionType }),
resendConfirmationEmail: () => dispatch(resendConfirmationEmailActionCreator(orderId)),
payReservationCard: () => dispatch(payReservationCardActionCreator(orderId)),
payReservationCash: () => dispatch(payReservationCashActionCreator(orderId)),
payRest: type => dispatch(payRestActionCreator(orderId, type)),
refundCash: amount => dispatch(refundCashActionCreator(orderId, amount)),
cancelReservation: () => dispatch(cancelReservationActionCreator(orderId)),
});
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(s)(BookingDetailsModal));
My Redux tab on page load shows the following:
type(pin): "BOXOFFICE_IS_SUPER_USER"
isSuperAdmin(pin): true
This is how I have used createStore to access the variable:
const isSuperAdmin = createStore(null, {
[BOXOFFICE_IS_SUPER_USER]: isSuperAdmin => isSuperAdmin,
});
I then proceeded to add it to the reducer at the bottom.
edit I have changed the variable isSuperAdmin in the createStore to true and this can be read perfectly fine, it must now be an issue with the variable passed to the action in the first place.
Here is the code where I get the value of the variable and pass it on:
Export default ({ knex }) => authenticateAdmin(knex)(
async (req, res) => {
try {
const { eventId } = req;
const event = await fetchEvent(knex, eventId);
const isSuperAdmin = await res.isSuperAdmin;
res.send({ event, isSuperAdmin});
} catch (err) {
res.send(err.stack);
console.error(err.stack); // eslint-disable-line no-console
throw err;
}
}
);
And the dispatch:
export const fetchEvent = () => async (dispatch, getState) => {
try {
const state = getState();
const { auth: { password } } = state;
const response = await fetch('/api/event', {
headers: {
Accept: 'application-json',
'X-Password': password,
},
});
if (response.status === 200) {
const { event, isSuperAdmin } = await response.json();
dispatch({ type: BOXOFFICE_SET_EVENT, event });
dispatch({ type: BOXOFFICE_IS_SUPER_USER, isSuperAdmin });
} else {
localStorage.removeItem('password');
dispatch({ type: BOXOFFICE_UNAUTHENTICATE });
}
} catch (err) {
console.log(err); // eslint-disable-line no-console
throw err;
}
};
EDIT
Here is the reducer:
export default combineReducers({
isSuperAdmin, ------- My variable
isProcessingPayment,
isSelectDateCollapsed,
isLoadingBookings,
shouldShowBookings,
shouldShowDepositModal,
shouldShowReservationModal,
shouldShowConsumerDetailsModal,
shouldShowDiscountModal,
shouldShowOrderConfirmationModal,
bookingFilter,
selectedOrderId,
sendConfirmationEmail,
bookingIds,
orderDetailsId,
bookingDetailsActionType,
});
I guess the way you defined your mapStateToProps is incorrect.
Updated the code
try following:
const mapStateToProps = ({
ui: {
bookingDetailsActionType,
isSuperAdmin
},
orders: {
data,
edits
}
}, {
orderId
}) => {
const order = data[orderId],
orderEdits = edits[orderId];
return {
bookingDetailsActionType,
isSuperAdmin,
order,
isCreatingPayment: orderEdits.isCreatingPayment
};
};
I finally have a solution! Turns out my issue was not setting a property type for my isSuperUser variable. Despite my colleague telling me that it will work without any property type (which still makes sense to me and confuses me as to why it wont work?!).
A simple change in the index.js file from:
[BOXOFFICE_IS_SUPER_USER]: isSuperAdmin => isSuperAdmin,
to
[BOXOFFICE_IS_SUPER_USER]: (state, { isSuperAdmin }) => isSuperAdmin,
and adding a property type to the show.js file where I used res.send()
res.send({ event, isSuperAdmin: isSuperAdmin});
Im still at a loss as to why it won't work with no property type but oh well...!

Categories

Resources