I've been trying to add a 'delete from cart' action to my redux setup. So far I can add items to a whitelist I have set up in my store but I'm not sure on how to delete items from the cart. This is my store:
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import reducer from './reducers';
import thunkMiddleware from 'redux-thunk';
import {createLogger} from 'redux-logger';
const store = createStore(
reducer,
undefined,
compose(
applyMiddleware(createLogger(), thunkMiddleware),
autoRehydrate()
)
);
persistStore(store, {whitelist: ['cart']});
export default store;
These are my reducers:
import {ADD_CART} from './actions';
import { REHYDRATE } from 'redux-persist/constants';
export default Reducer;
var initialState = {
cart:{},
data: [],
url: "/api/comments",
pollInterval: 2000
};
function Reducer(state = initialState, action){
switch(action.type){
case REHYDRATE:
if (action.payload && action.payload.cart) {
return { ...state, ...action.payload.cart };
}
return state;
case ADD_CART:
return {
...state,
cart: [...state.cart, action.payload]
}
default:
return state;
};
}
And these are my actions:
export const ADD_CART = 'ADD_CART';
export function addCart(item){
return {
type: ADD_CART,
payload: item
}
};
export function removeCart(item){
return{
type: REMOVE_CART,
payload: item
}
};
In my Cart component is where I want to have a delete from cart button where you can choose a specific item to delete:
import React, { Component } from 'react';
import {addCart} from './Shop';
import { connect } from 'react-redux';
export class Cart extends Component {
constructor(props) {
super(props);
this.state = {items: this.props.cart,cart: [],total: 0};
}
...
render() {
return(
<div className= "Webcart" id="Webcart">
{this.countTotal()}
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
onCartAdd: (cart) => {
dispatch(addCart(cart));
},
}
}
function mapStateToProps(state) {
return { cart: state.cart };
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart);
I want to be able to select an specific item from the cart array and delete it. I believe this should be done through an action with redux since my array is being saved in my store. How can I do this?
You are right, just dispatch the removeCart action (which you have already defined) from your component:
const mapDispatchToProps = (dispatch) => {
return {
onCartRemove: (cart) => {
dispatch(removeCart(cart));
},
onCartAdd: (cart) => {
dispatch(addCart(cart));
},
}
}
and update the state by handle it in your reducer
case REMOVE_CART:
return {
...state,
cart: state.cart.filter((item) => payload !== item)
}
Related
I dispatched an action in a react-redux application, the states of the reducer with that action was updated with the payload from the action as required. In addition to this, the states of the another reducer that does not not have such an action was updated with that same payload even when the corresponding action was not dispatched. what exactly is happening?
//my action for reducer 1
import { CREATE, UPDATE, DELETE, FETCH_ALL, ITEMS_LOADING } from '../constants/actionTypes';
import * as api from '../api/index';
export const getPosts = () => async (dispatch) => {
dispatch(setItemsLoading());
const data = await api.fetchPosts
.then((res) => {
dispatch({ type: FETCH_ALL, payload: res.data });
}).catch(() => {
console.log("there is error");
})
}
//my action for reducer 2
import { GET_POST } from '../constants/actionTypes';
import * as api from '../api/index';
export const getSinglePosts = (id) => (dispatch) => {
return{
type: GET_POST, payload: id
}
}
//reducer 1
import { CREATE, UPDATE, DELETE, FETCH_ALL, ITEMS_LOADING } from '../constants/actionTypes';
const initialState = {
posts: [],
allLoaded:false,
loading: false
}
export default (state = initialState, action) => {
switch (action.type) {
case DELETE:
return {
...state,
posts: state.posts.filter((post) => post._id !== action.payload)
};
case UPDATE:
return {
...state,
posts: state.posts.map((post) => post._id === action.payload._id ? action.payload : post)
};
case FETCH_ALL:
return {
...state,
posts: action.payload,
allLoaded:true,
loading: false
};
case CREATE:
return {
...state,
posts: [action.payload, ...state.posts]
};
case ITEMS_LOADING:
return {
...state,
loading: true
};
default:
return state;
}
}
//reducer2
import {
GET_POST
} from '../constants/actionTypes';
const initialState = {
info:null,
postLoaded:false
}
export default function (state = initialState, action) {
switch (action.type) {
case GET_POST:
return {
...state,
postLoaded: true,
info:action.payload
};
default:
return state;
}
}
//homepage where action was dispatched
import React, { useState, useEffect } from 'react';
import PageNavbar from './PageNavbar';
import { getPosts } from '../../myActions/posts';
import { useSelector, useDispatch } from 'react-redux';
//import { getSinglePosts } from '../../myActions/single';
import { useHistory } from 'react-router-dom';
import Button from '#material-ui/core/Button';
function MyBlog() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
const posts = useSelector((state) => state.posts.posts);
const history = useHistory();
return (
<>
<PageNavbar />
<div align="center">
{posts.map((post) => (
<>
<Button href="/user/singlePost" color="primary">
{post.title}
</Button>
<br />
</>))}
</div>
</>
)
}
export default MyBlog
//root reducer
import { combineReducers } from 'redux';
import posts from './myposts';
import error from './error';
import auth from './auth';
import info from './singlePost';
export default combineReducers({
info,
posts,
auth,
error
})
//thanks
I'm trying to access my Redux groceries array from my grocery list component, and when I try to access the state with this.props.groceries, it returns 'undefined'. I'm still trying to wrap my head around some Redux concepts, but I think I'm really close. In my store.js, I'm logging
store.subscribe(() => {
console.log('store changed', store.getState());
});
And getState() is displaying my correct groceries array, with all the groceries inside it. I'm just not sure how to access this state from my groceries list component. Thanks!
Overview of my GroceryList component:
import { connect } from "react-redux";
import { bindActionCreators, createStore } from 'redux';
import * as groceryActions from '../../redux/actions/grocery-actions';
class GroceryList extends Component {
constructor(props) {
super(props);
this.state = {
};
}
addGroceryToList() {
this.props.addGrocery(newGroceryItem);
console.log(this.props.groceries); //Logs undefined
}
render() {
return(
//something
)
}
}
const mapStateToProps = (state) => ({
groceries: state.groceries.groceries
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{ addGrocery: groceryActions.addGrocery, },
dispatch
)
export default connect(mapStateToProps, mapDispatchToProps)(GroceryList);
Grocery action:
let groceryIndex = 0;
export const addGrocery = grocery => dispatch => {
dispatch({
type: 'ADD_GROCERY',
id: groceryIndex++,
grocery
});
};
Grocery reducer:
export const groceries = (state = [], action) => {
switch (action.type) {
case "ADD_GROCERY":
return [
...state,
grocery(action.grocery, action),
];
default:
return state
}
}
export const grocery = (state, action) => {
switch (action.type) {
case "ADD_GROCERY":
return {
id: action.id,
grocery: action.grocery,
};
default:
return state
}
}
Reducer combiner:
import { combineReducers } from 'redux';
import { groceries } from './grocery-reducer';
const reducer = combineReducers({
groceries: groceries,
});
export default reducer;
Store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
if (typeof window === 'undefined') {
global.window = {}
}
const enhancer = compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__
? window.__REDUX_DEVTOOLS_EXTENSION__()
: f => f
);
/* eslint-disable no-underscore-dangle */
const store = createStore(
reducer,
{}, // initial state
enhancer
)
store.subscribe(() => {
console.log('store changed', store.getState());
});
/* eslint-enable */
export default store
App.js
import { Provider, connect } from 'react-redux';
import { bindActionCreators } from "redux";
import * as groceryActions from "./src/redux/actions/grocery-actions";
import store from './src/redux/store';
class App extends React.Component {
state = {
};
render() {
return (
<Provider store={store}>
<Layout />
</Provider>
);
}
}
export default (App);
After one of the Redux tutorials decided to implement that facny action->reducer->store->view chain for simple app with only login part.
Seems like all setted up but when I run my app - in mapStateToProps(currentState) no any sign of the any custom state fields which I expected to see! (default state from reducer). But the action function is fine, as you can see on the screenshot
I can't see whats wrong here so, decided to ask it.
So here is the code
So, first of all - store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
then login reducer
const initialState = {
user: {
name: '',
password: ''
},
fetching: false
}
export default function login(state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST: {
return { ...state, fetching: true }
}
case LOGIN_SUCCESS: {
return { ...state, user: action.data, fetching: false }
}
case LOGIN_FAIL: {
return { ...state, user: -1, fetching: false }
}
default: {
return state;
}
}
}
and the root (reducers/index.js):
import login from './login/login';
import { combineReducers } from 'redux'
export default combineReducers({
login
});
the action
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAIL
} from '../../constants/login.js';
export function onLoginAttempt(userData) {
return (dispatch) => {
dispatch({
type: LOGIN_REQUEST,
user: userData
})
tryLogin(userData);
}
};
function tryLogin(userData) {
let url = 'SignIn/Login ';
return (dispatch) => {
axios.post(url, userData)
.then((response) => dispatch({
type: LOGIN_SUCCESS,
data: response.data
})).error((response) => dispatch({
type: LOGIN_FAIL,
error: response.error
}))
}
};
So here is entrance point:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/app.js';
import configureStore from './store/configureStore';
let store = createStore(configureStore);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("content")
);
and here is the app.js (Login is just sompe custom div with two fields nothing special)
import React, { Component } from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
import Login from '../components/login/Login';
import * as pageActions from '../actions/login/login'
class App extends Component {
render() {
const { user, fetching } = this.props;
const { onLoginAttempt } = this.props.pageActions;
return <div>
<Login name={user.name} password={user.password} fetching={fetching} onLoginAttempt={onLoginAttempt} />
</div>
}
}
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I see that you have state which looks like:
reduxState = {
login: {
user: {
name: '',
password: ''
},
fetching: false
}
}
but then you try to access properties that don't exist.
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
I think you need to:
function mapStateToProps(currentState) {
return {
user: currentState.login.user,
fetching: currentState.login.fetching
}
}
You have this line let store = createStore(configureStore); on your entry point. However, inside configureStore, you have a call to createStore()
Basically you're calling something like createStore(createStore(reducers)). That's probably the cause of the problem.
You should probably call it like
let store = configureStore( /* pass the initial state */ )
I'm developing a react-redux app and I can get access to the reducers via routes. Now I'm facing the trouble of getting access to a specific reducer without using routes.
Here is my reducers.js:
const initialState = {
loading: false,
topics: []
};
export default createReducer(initialState, {
[LOADING_DATA]: (state, action) => {
return Object.assign({}, state, {
loading: action.loading
});
}
});
This is my actions.js:
export function loading (loading) {
return {
type: LOADING_DATA,
payload: {loading}
};
}
And this is what I have on my component:
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux';
import * as moduleActionCreators from '...';
import * as actionCreators from '...';
class MyComponent extends Component {
...
render () {
return (<div>
...
</div>;
}
}
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(Object.assign({}, moduleActionCreators, actionCreators), dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
Normally in the mapStateToProps I reference the reducer variables as loading: state['my_reference_to_reducer'].loading but I can't figure it out how to tell the component to reference my reducers.js in order to get loading as props.
I would appreciate a light on this.
You need to set up the state in mapStateToProps function in order to access it:
const mapStateToProps = (state) => {
return {
loading: state.loading
}
}
Then you should be able to use it as this.props.loading in MyComponent.
Your reducer can look like this:
export default function reducer(state = {}, action) {
switch(action.type) {
case 'LOADING_DATA':
return Object.assign({}, state, {
...state,
loading: action.payload.loading
})
I recommend you to use redux ducks pattern as it keeps action creators and reducers at the same file, saves you time and makes it easier to read and use. For example:
loading.js
// Actions
const LOADING_DATA = 'LOADING_DATA'
// Action Creators
export const loadingData = (data) => {
return {
type: LOADING_DATA,
payload: {
loading: data
}
}
}
// Reducer
export default function reducer(state = {
loading: 'DATA zeroed'
}, action) {
switch(action.type) {
case 'LOADING_DATA':
return Object.assign({}, state, {
...state,
loading: action.payload.loading
})
default:
return state
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import MyComponent from './MyComponent';
import configureStore from './configureStore'
const store = configureStore()
ReactDOM.render(
<MyComponent store={store}/>,
document.getElementById('root')
);
configureStore.js
import { createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import loadingData from './loading'
const configureStore = () => {
return createStore(loadingData, composeWithDevTools())
}
export default configureStore
MyComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadingData } from './loading';
class MyComponent extends Component {
constructor(props){
super(props)
this.onLoadingData = this.onLoadingData.bind(this)
}
componentDidMount() {
this.props.loadingData('no more undefined')
}
onLoadingData() {
this.props.loadingData('DATA')
}
render() {
console.log(this.props.loading)
return (
<div>
<h2>MyComponent</h2>
<button onClick={this.onLoadingData}>Load Data</button>
<p>{this.props.loading}</p>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.loading
}
}
const mapDispatchToProps = {
loadingData
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent)
When I click the DIV in Home container, I have confirmed the set function is called (I see the console log)
teamReducer function is never called. Maybe bindActionCreators should be used differently? How can i have my action creator send action to reducer to update the league store?
// teamReducer.js
export function teamReducer(state = initialState, action){
switch (action.type) {
case 'SET_TEAM':
return {
...state,
called: true
};
default:
return state;
}
};
// reducers/index.js
import { combineReducers } from 'redux';
import { routeReducer } from 'redux-simple-router';
import { teamReducer } from './teamReducer';
const rootReducer = combineReducers({
routing: routeReducer,
league: teamReducer,
});
export default rootReducer;
// actions/setTeam.js
export function setTeam(team, position) {
console.log(team, position);
return {
type: 'SET_TEAM',
team,
position
};
}
}
// Home.js
import React, { PropTypes, Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {setTeam } from '../../actions/teams';
const mapStateToProps = ({league}) => {
return {
called: league.called
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
setTeam,
}, dispatch);
};
#connect(mapStateToProps, mapDispatchToProps)
export class Home extends Component {
constructor(props) {
super(props);
}
render() {
const {set} = this.props.setTeam
return <div onClick={set} />
}
}
The issue in the render function. You use destructuring assignment wrong.
render() {
const {set} = this.props.setTeam;
return <div onClick={set} />
}
This assignment is the same as in the following code:
const set = this.props.setTeam.set;
But setTeam is a function and doesn't have set property. The correct code is:
render() {
const {setTeam} = this.props;
return <div onClick={setTeam} />
}