this.props.authState stays the same although I'm dispatching an action in my componentDidMount function:
componentDidMount() {
if (localStorage.getItem('token')) {
dispatch(updateAuthState('AUTHENTICATED'));
}
}
render() {
<div>{this.props.authState}</div>
}
Home.propTypes = {
authState: PropTypes.string
};
const mapStateToProps = (state) => {
return {
authState: state.authState
}
};
const mapDispatchToProps = (dispatch) => {
return {
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
the output is NO_AUTH (the initial value of authState)
Reducer:
export function authState(state = "NO_AUTH", action) {
switch (action.type) {
case 'AUTH_STATE':
return action.authState;
default:
return state;
}
}
any idea why?
You're currently dispatching directly inside the componentDidMount which isn't mapped into:
connect(mapStateToProps, mapDispatchToProps)(Home);
This should do the job:
componentDidMount() {
if (localStorage.getItem('token')) {
this.props.onUpdateAuthState('AUTHENTICATED');
}
}
const mapDispatchToProps = (dispatch) => {
return {
onUpdateAuthState: function(authState) {
dispatch(updateAuthState(authState));
}
}
};
Now, this will get the authState:
const mapStateToProps = (state) => {
return {
authState: state.authState
}
};
If you’re mutating the state then component will not be re rendered. You need to return {...state, authState} from your reducer.
And you verify your updated state in
componentWillReceiveProps(nextProps)
{
}
I hope this would solve the issue.
Related
I'm trying to implement a shopping cart using redux. here is my cart-reducer:
export const cartReducer = (
state = { cartItems: JSON.parse(localStorage.getItem("cartItems") || "[]")},
action) => {
switch (action.type) {
case ADD_TO_CART:
return { ...state,cartItems: action.payload };
}}
Here is the component where I want to show the updated state value accessing it using props in connect and update the cartItems.length after every state update
class Cartsidebar extends Component {
constructor(props) {
super(props);
this.state = {
grandTotal: '',toggle:true
}
}
handleHide(){
this.setState({ toggle: !this.state.toggle})
}
render() {
const {cartItems}=this.props;
console.log(cartItems);
return (
<div>
{cartItems.length}
</div>
)
}
}
export default connect(
(state) => ({
cartItems: state.cart.cartItems,
}),
{ incrementToCart, decreaseToCart, removeFromCart }
)(Cartsidebar);
States are updating fine and state-difference is also showing in redux-dev-tools on every update of redux state but it is not reflecting in cart component.what am i doing wrong here?Thanks in advance.
EDIT:
this is function that execute on add to cart button onclick event:
handleAddToCart=(p)=>{
const cartItems = store.getState().cart.cartItems;
let alreadyExists = false;
cartItems.forEach((x) => {
if (x.discountPer === p.discountPer) {
alreadyExists = true;
}
});
if (!alreadyExists) {
cartItems.push({ ...p });
}
store.dispatch(addToCart(cartItems));
}
And addToCart action creator looks like this:
export const addToCart = (cartItem) => {
return({
type: ADD_TO_CART,
payload: cartItem,
});
};
Issues
You are mutating the state object. You are accessing a reference to the cart array in state and directly pushing into it.
You aren't leveraging the power of Redux and reducers properly.
code
handleAddToCart = (p) => {
const cartItems = store.getState().cart.cartItems; // cartItems is reference to state
let alreadyExists = false;
cartItems.forEach((x) => {
if (x.discountPer === p.discountPer) {
alreadyExists = true;
}
});
if (!alreadyExists) {
cartItems.push({ ...p }); // mutation!!
}
store.dispatch(addToCart(cartItems));
}
Solution
Pass the item you want to add to the cart in the action and move all the logic to update the cart into your reducer.
UI
handleAddToCart = (p) => {
this.props.addToCart(p);
}
...
export default connect(
(state) => ({
cartItems: state.cart.cartItems,
}),
{ addToCart, incrementToCart, decreaseToCart, removeFromCart }
)(Cartsidebar);
reducer
case ADD_TO_CART:
const { payload } = action;
const found = state.cartItems.find(item => item.discountPer === payload.discountPer);
if (found) {
return state;
}
return {
...state,
cartItems: state.cartItems.concat(payload),
};
What you are doing in handleAddToCart is a big no no, and goes against the pattern that Redux tries to enforce. I made some changes to your logic to make it easier, and updated the reducer. In theory, if you make these changes, it should work.
handleAddToCart:
handleAddToCart = (p) => {
const cartItems = store.getState().cart.cartItems;
for (const item of cartItems) {
if (item.discountPer === p.discountPer) {
return;
}
}
store.dispatch(addToCart({ ...p }));
};
reducer:
export const cartReducer = (
state = { cartItems: JSON.parse(localStorage.getItem("cartItems") || "[]") },
action
) => {
switch (action.type) {
case ADD_TO_CART:
return { ...state, cartItems: [...state.cartItems, action.payload] };
}
};
I'm a little confused on passing an object to the redux store. I have successfully created the store and can add items from the initial state. The function also fires when called
Action:
import { GET_ITEM } from './OrderTypes'
export const getItem = (payload) => {
return {
type: GET_ITEM,
payload: { payload }
}
}
Reducer:
import { GET_ITEM } from './OrderTypes'
const initialState = {
orderList: [],
}
const orderReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ITEM: return {
...state,
orderList: [...state.orderList, action.payload]
}
default: return state
}
}
export default orderReducer
Component:
class TestComponentextends Component {
pushItem = () => {
this.props.getItem({
payload: 'test object'
})
}
render() {
return (
<input type='button' value='test btn' onClick={this.pushItem} />
)
}
}
const mapStateToProps = state => {
return {
orderList: state.orderList
}
}
const mapDispatchToProps = dispatch => {
return {
getItem: () => dispatch(getItem())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TestComponent)
What happens: An empty object is added to the orderList array.
What I want to happen: Store the object in pushItem in the orderList array.
Your mapDispatchToProps doesn't pass the arguments to the action creator (see mapDispatchToProps function arguments - 2nd example):
const mapDispatchToProps = dispatch => ({
getItem: (...args) => dispatch(getItem(...args))
})
Even easier is to let react-redux handle the mapping by using mapDispatchToProps as an object:
const mapDispatchToProps = {
getItem
}
I'm learning Redux state management and got stuck with an issue. The mapStateToProps within a component is not triggered when the state changes. Gone through a lot of blogs, couldn't able to figure out the problem.
The store seems to update properly, as the "subscribe" method on store prints new changes. Attached screenshot as well.
Actions.js
export const GET_ITEMS_SUCCESS = "GET_ITEMS_SUCCESS";
export const GET_ITEMS_FAILURE = "GET_ITEMS_FAILURE";
export const getItemsSuccess = (items) => ({
type: GET_ITEMS_SUCCESS, payload: items
});
export const getItemsFailure = (error) => ({
type: GET_ITEMS_FAILURE, error: error
});
export function getItems(dispatch) {
return dispatch => {
fetch(myList)
.then(res => res.json())
.then(res => {
if(res.error) {
throw(res.error);
}
store.dispatch(getItemsSuccess(res));
return res;
})
.catch(error => {
store.dispatch(getItemsFailure(error));
})
}
}
Reducer
let initialState = {items: [], error: null}
function GetItemsReducer (state = initialState, action) {
switch (action.type) {
case GET_ITEMS_SUCCESS:
return Object.assign({}, state, {pending: false, items: action.payload});
case GET_ITEMS_FAILURE:
return Object.assign({}, state, {pending: false, error: action.error});
default:
return state;
}
}
export default const rootReducer = combineReducers({
GetItemsReducer: GetItemsReducer
});
Store
const mystore = createStore(rootReducer, applyMiddleware(thunk, promise));
mystore.subscribe(() => console.log("State Changed;", mystore.getState()));
Component
class Home extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchItems();
}
render() {
return (
<div>{this.props.items.length}</div>
)
}
}
const mapStateToProps = (state) => {
console.log('mapStateToProps ----------> ', state);
return {
items: state.GetItemsReducer.items,
error: state.GetItemsReducer.error
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchItems: bindActionCreators(getItems, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Main
class App extends React.Component {
render() {
return (
<Provider store={mystore}>
<Home />
</Provider>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
Thanks in advance.
I have a problem with displaying data.
In my application I use react and redux.
In the console I will get an error mapStateToProps() in Connect(ListPets) must return a plain object. Instead received undefined.
This is my main component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import loadData from '../actions/actions';
class ListPets extends Component {
componentDidMount() {
const { loadData } = this.props;
loadData();
console.log(loadData );
}
render() {
const { dataPet } = this.props;
return (
<div>
</div>
);
}
}
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = (dispatch) => {
return {
loadData: () => dispatch(loadData())
}
};
This fragment console.log(loadData ); display
ƒ loadData() {
return dispatch(Object(_actions_actions__WEBPACK_IMPORTED_MODULE_7__["default"])());
}
When I add the code {dataPet.data} in div. I get an error]. As if this data was not in the store, I do not know...
this my reducer function
const initialState = {
isFetching: false,
dataPet: [],
};
const fetchDataReducer = (state=initialState, action) => {
switch(action.types) {
case actionTypes.FETCH_DATA_START:
return {
...state,
isFetching: true,
}
case actionTypes.FETCH_DATA_SUCCESS:
return {
...state,
isFetching: false,
dataPet: action.dataPet,
}
case actionTypes.FETCH_DATA_FAIL:
return {
...state,
isFetching: false,
}
};
}
Data is well downloaded, because the console receives the FETCH_DATA_SUCCESS action.
I have no idea how to solve this problem
I made some changes on your code, try this now...should work
https://codesandbox.io/s/z2volo1n6m
In your reducer you have a typo:
const fetchDataReducer = (state=initialState, action) => {
switch(action.types) { // here
It should be action.type not action.types.
If thing is an object in state:
const mapStateToProps = state => ({
thing: state.thing,
});
Then use like:
this.props.thing in your component
I've been trying to retrieve the new state from my vitaminReducer() reducer function, and connect it through mapStateToProps. But when I console.log the state, I get back "the state is {vitamin: undefined}".
This is the Vitamins component where I'm calling mapStateToProps()
(Vitamins.js)
componentDidMount() {
this.props.fetchVitamins();
}
function mapStateToProps(state) {
return {
vitamin: state,
}
};
console.log('the state is', mapStateToProps());
export default connect(mapStateToProps, { fetchVitamins })(Vitamins);
(reducers.js)
function vitaminReducer(state = [], action) {
switch(action.type) {
case FETCH_VITAMINS_SUCCESS:
return [
...state,
action.payload.vitamins
];
default:
return state;
}
}
const reducers = combineReducers({
vitamin: vitaminReducer,
});
I have the data coming through an Express server. I've console logged "vitamins" here and I get the data back, so I know that's not the issue.
(actions.js)
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(handleErrors)
.then(res => res.json())
.then(micros => {
dispatch(fetchVitaminsSuccess(micros));
const vitamins = micros.vitamins;
}
)};
};
export const FETCH_VITAMINS_SUCCESS = 'FETCH_VITAMINS_SUCCESS';
export const fetchVitaminsSuccess = vitamins => ({
type: FETCH_VITAMINS_SUCCESS,
payload: vitamins
});
If I do: "return { vitamin: state.vitamin, }" instead of "return { vitamin: state, }", I get back "TypeError: Cannot read property 'vitamin' of undefined". But that's what I called vitaminReducer in my combineReducers() function at the bottom of reducers.js, so I thought that was the right way to do it.
Thank you everyone for your input! I was able to get it working.
I ditched the mapStateToProps() and instead did this
(Vitamins.js)
componentDidMount() {
this.props.fetchVitamins();
}
renderData() {
const { vitamins } = this.props.vitamins;
return vitamins.map((micro, index) => {
return (
<option value={micro.value} key={index}>{micro.name}</option>
)
})
}
export default connect(
state => ({
vitamins: state.vitamins
}),
{
fetchVitamins
},
)(Vitamins);
I set the dispatch action inside of the fetchVitamins() function
(actions.js)
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(handleErrors)
.then(res => res.json())
.then(micros => {
dispatch({
type: "RECEIVE_VITAMINS",
payload: micros.vitamins
});
}
)};
};
export const RECEIVE_VITAMINS = 'RECEIVE_VITAMINS';
In reducers I set the initialState to the vitamins array, and passed the new state of micros.vitamins from my RECEIVE_VITAMINS action
(reducers.js)
const initialState = {
vitamins: [],
}
function vitaminReducer(state = initialState, action) {
switch(action.type) {
case RECEIVE_VITAMINS:
return {
...state,
vitamins: action.payload
};
default:
return state;
}
}
const reducers = combineReducers({
vitamins: vitaminReducer,
});
Thanks everyone for your help! Let me know if you have any other suggestions :D