State is not triggering re-render after change in state - javascript

I am combining two reducers in my React app. One of them is working fine, but one is not triggering re-render after a change in state. But after when I save document or make change in other reducer state, component re-renders for both reducers
reducer which is not working fine:
import {
SET_ACTIVE_SUBJECT,
SET_ACTIVE_TOPIC
} from '../action/action-types'
const initialState = {
activeSubject: '',
activeTopic: ''
}
const makeState = (stateActiveSubject, stateActiveTopic) => {
return {
activeSubject: stateActiveSubject,
activeTopic: stateActiveTopic
}
}
export const uireducer = (state = initialState, action) => {
switch(action.type){
case SET_ACTIVE_SUBJECT:
// this statement is printing new state in console, but not triggering re-render
console.log('New State : ', makeState(action.payload,''));
return makeState(action.payload,'')
case SET_ACTIVE_TOPIC:
return makeState(state.uireducer.activeSubject,action.payload)
default:
return state
}
}
Component which is not re-rendering:
const Topics = ({activeSubject, data}) => {
const classes = useStyles()
const [topics, setTopics] = useState([])
useEffect(() => {
console.log('Active Subject : ', activeSubject);
if(activeSubject){
console.log('Data : ', data.filter(subject => (subject.id === activeSubject))[0].topics);
setTopics(data.filter(subject => (subject.id === activeSubject))[0].topics)
}
}, [])
return (
<List>
{
topics.length > 0 ? topics.map(topic => {
return (
<ListItem button className={classes.listItem} id={topic.id} key={topic.id}>
{topic.name}
<ButtonGroup className={classes.editDelete}>
<IconButton className={classes.icon}>
<Edit />
</IconButton>
<IconButton className={classes.icon}>
<Delete />
</IconButton>
</ButtonGroup>
</ListItem>
)
}) : <div className={classes.waitMessage}><p>Select Subject To Display Topics</p></div>
}
</List>
)
}
const mapStateToProps = state => ({
activeSubject: state.uireducer.activeSubject,
data: state.subjects.data
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(Topics);
reducer which is working fine:
import {
FETCHING_DATA,
FETCHED_DATA,
FETCH_DATA_ERROR
} from '../action/action-types'
const initialState = {
isDataFetching : false,
error: '',
data: [],
}
const makeState = (dataFetching, stateError, stateData) => {
return {
isDataFetching: dataFetching,
error: stateError,
data: stateData,
}
}
export const reducer = (state = initialState, action) => {
switch(action.type){
case FETCHING_DATA:
return makeState(true,'',[])
case FETCHED_DATA:
return makeState(false,'',action.payload)
case FETCH_DATA_ERROR:
return makeState(false,action.payload,[])
default:
return state
}
}
Store here :
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
import {reducer} from '../reducer/reducer'
import { uireducer } from '../reducer/uireducer'
console.log(reducer);
const rootReducer = combineReducers({
subjects: reducer,
uireducer
})
const store = createStore(rootReducer, applyMiddleware(thunk))
export default store

Set activeSubject as dependency for the useEffect, so it will rerun when the value changes.
useEffect(() => {
// ...
}, [activeSubject])

Related

initialState in configureStore doesn't return anything?

I build a MERN stack ecommerce using redux. In the part of cart components I add product to the cart and also to localStorage. When I refresh the page the items disappear from the page but it is still in localStorage and I can't find the problem.
This is my cart reducer code:
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.forEach((i) =>
i.product === isItemExist.product ? item : i
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, item],
};
}
default:
return state;
}
};
and this is my store initialState code:
const initialState = {
cart: {
cartItems: localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [],
},
};
const store = configureStore(
{ reducer },
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
cart.jsx
import React, { Fragment, useEffect, useState } from "react";
import "./Cart.css";
import { CartItems } from "../";
import { useDispatch, useSelector } from "react-redux";
import { addItemsToCart } from "../../actions/cartActions";
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));
};
return (
<Fragment>
<div className="cart__page">
<div className="cart__header">
<p>Product</p>
<p>Quantity</p>
<p>Subtotal</p>
</div>
{cartItems &&
cartItems?.map((item) => (
<div key={item?.product} className="cartContainer">
<CartItems item={item} />
<div className="cart__Input">
<button>+</button>
<input type="number" readOnly value={item?.quantity} />
<button>-</button>
</div>
<p className="Cart__subtotal">
{`$${item?.price * item?.quantity}`}
</p>
</div>
))
}
I'm trying to use useEffect hook but the data come by redux doesn't save in localStorage.
The configureStore function takes only a single configuration object that takes reducer, middleware, devTools, preloadedState, and enhancers properties.
See configureStore.
It appears you are correctly accessing the persisted state from localStorage, but then not passing the initial state correctly to the store configurator.
import { configureStore } from '#reduxjs/toolkit';
const initialState = {
cart: {
cartItems: JSON.parse(localStorage.getItem("cartItems")) ?? [],
},
};
const store = configureStore({
reducer,
preloadedState: initialState,
});
export default store;
If your redux state persistence needs change or grow then I'd suggest taking a look at redux-persist. If you are already familiar with Redux then this is about a 5-15 minute integration the first time.

Implementation of Redux Persist with Redux Toolkit. After implementation, can't see todos

Today i tried to implement redux persist to save todos in the local storage.
I have made everything right using documentation, but the problem now is that i have todos in the local storage, but can't see todos in the app.
Before implementation, everything works fine. Must understand this part of using locaStorage with redux toolkit/redux-persist.
If someone can show where i have made an error.
Thx a lot!
store.js
import {combineReducers, configureStore} from "#reduxjs/toolkit";
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist';
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
import todoReducer from './todoSlice';
const rootReducer = combineReducers({
todoReducer,
});
const persistConfig = {
key: 'root',
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
export const persistor = persistStore(store);
export default store;
todoSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = [];
const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
//Adding todos
addTodos: (state, action) => {
state.push(action.payload);
return state;
},
//remove todos
removeTodos: (state, action) => {
return state.filter((item) => item.id !== action.payload);
},
//update todos
updateTodos: (state, action) => {
return state.map((todo) => {
if (todo.id === action.payload.id) {
return {
...todo,
item: action.payload.item,
};
}
return todo;
});
},
//completed
completeTodos: (state, action) => {
return state.map((todo) => {
if (todo.id === action.payload) {
return {
...todo,
completed: !todo.completed,
};
}
return todo;
});
},
},
});
export const {
addTodos,
removeTodos,
updateTodos,
completeTodos,
} = todoSlice.actions;
export default todoSlice.reducer;
displayTodos.js
import React, {useState} from "react";
import {connect} from "react-redux";
import {
completeTodos,
removeTodos,
updateTodos,
} from "../redux/todoSlice";
import TodoItem from "./TodoItem";
import {AnimatePresence, motion} from "framer-motion";
const mapStateToProps = (state) => {
return {
todos: state,
};
};
const mapDispatchToProps = (dispatch) => {
return {
removeTodo: (id) => dispatch(removeTodos(id)),
updateTodo: (obj) => dispatch(updateTodos(obj)),
completeTodo: (id) => dispatch(completeTodos(id)),
};
};
const DisplayTodos = (props) => {
const [sort, setSort] = useState("active");
return (
<div className="displaytodos">
<div className="buttons">
<motion.button
whileHover={{scale: 1.1}}
whileTap={{scale: 0.9}}
onClick={() => setSort("active")}
>
Active
</motion.button>
<motion.button
whileHover={{scale: 1.1}}
whileTap={{scale: 0.9}}
onClick={() => setSort("completed")}
>
Completed
</motion.button>
<motion.button
whileHover={{scale: 1.1}}
whileTap={{scale: 0.9}}
onClick={() => setSort("all")}
>
All
</motion.button>
</div>
<ul>
<AnimatePresence>
{props.todos.length > 0 && sort === "active"
? props.todos.map((item) => {
return (
item.completed === false && (
<TodoItem
key={item.id}
item={item}
removeTodo={props.removeTodo}
updateTodo={props.updateTodo}
completeTodo={props.completeTodo}
/>
)
);
})
: null}
{/* for completed items */}
{props.todos.length > 0 && sort === "completed"
? props.todos.map((item) => {
return (
item.completed === true && (
<TodoItem
key={item.id}
item={item}
removeTodo={props.removeTodo}
updateTodo={props.updateTodo}
completeTodo={props.completeTodo}
/>
)
);
})
: null}
{/* for all items */}
{props.todos.length > 0 && sort === "all"
? props.todos.map((item) => {
return (
<TodoItem
key={item.id}
item={item}
removeTodo={props.removeTodo}
updateTodo={props.updateTodo}
completeTodo={props.completeTodo}
/>
);
})
: null}
</AnimatePresence>
</ul>
</div>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(DisplayTodos);
I would assume that your problem is with
const rootReducer = combineReducers({
todoReducer,
});
That means that in the end, data from that slice will be available in state.todoReducer.
I would assume that before all this, you had
const store = configureStore({
reducer: {
todos: todoReducer
},
})
so you would now do
const rootReducer = combineReducers({
todos: todoReducer,
});
just the same and your todo slice would stay at state.todos
PS: you should really not be using connect/mapStateToProps/mapDispatchToProps nowadays. Use useSelector/useDispatch instead.

when mapping redux state into props into component it's returning undefined

i'm calling a api and getting my data and setting it to redux state successfully but when i'm mapping it to my component it's first returning undefined then it's calling the the api but i'm using redux-thunk for it
this is my header component
export class header extends Component {
componentDidMount() {
this.props.addUpcomingMovies();
}
render() {
const Movies = this.props.popularMovies.map(movie => (
<div key={movie.id} className="header-slide-container">
<div className="header-slide">
<img
src={`https://image.tmdb.org/t/p/original${movie.poster_path}`}
alt=""
/>
{(() => {
if (!movie.title) {
return null;
}
return <Title movie={movie} />;
})()}
{(() => {
if (!movie.original_title) {
return null;
}
return <OriginalTitle movie={movie} />;
})()}
{(() => {
if (!movie.original_name) {
return null;
}
return <OriginalName movie={movie} />;
})()}
</div>
</div>
));
return (
<div className="header">
<div className="header-container">
<div className="header-slider-wrapper">
{console.log(this.props.popularMovies)}
{<Movies />}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
popularMovies: state.upComingMovies.movies
});
export default connect(
mapStateToProps,
{ addUpcomingMovies }
)(header);
this is my upcoming movie reducer
const upComingMovie = (state = [], action) => {
switch (action.type) {
case "ADD_UPCOMING_MOVIES":
console.log('reducers');
return {
...state,
movies:action.payload
}
default:
return state;
}
};
export default upComingMovie;
combining reducers
import upComingMovie from './upcomingMovies';
import {combineReducers} from 'redux';
const allReducers = combineReducers({
upComingMovies:upComingMovie
})
export default allReducers
upcoming movie action
export const addUpcomingMovies = () => dispatch => {
console.log("fetching");
fetch("https://api.themoviedb.org/3/trending/all/week?api_key=25050db00f2ae7ba0e6b1631fc0d272f&language=en-US&page=1")
.then(res => res.json())
.then(movies =>
dispatch({
type: "ADD_UPCOMING_MOVIES",
payload: movies
})
);
};
this is my store
import { createStore, applyMiddleware,compose } from 'redux';
import thunk from 'redux-thunk';
import allReducers from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(allReducers,initialState,compose(applyMiddleware(...middleware), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()));
export default store;
Your reducer is not object, your reducer is an array
const upComingMovie = (state = [], action) => {}
// ____________________________^
But your are using object
case "ADD_UPCOMING_MOVIES":
return { // <--------------- this is an object notation
...state,
movies:action.payload
}
Solution
const initialState = {
movies: []
}
const upComingMovie = (state = initialState , action) => {}
Your initialState is wrong, it'd be {movies: []}:
const initialState = { movies: []};
// ...
const upComingMovie = (state = initialState, action) => {
// ...
Also use the same initial state in your store.

Updating redux state onClick

I have a component that displays data from the state. I'm using redux for state. I want to be able to click a button and filter the state. But I'm stuck on dispatching the action from the button.
Right now I have a button that is supposed to dispatch the action but it's not being called. I'm not sure if the mapsToDispatchProps is wrong or it's something else.
Here is the actions
import { GET_POLLS, SHOW_APPROVAL } from './types';
const URL = 'https://projects.fivethirtyeight.com/polls/polls.json';
export const getPolls = () => dispatch => {
return fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
}
export const getApproval = () => ({ type: SHOW_APPROVAL })
reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return (payload.type === "trump-approval")
default:
return state
}
}
export default pollReducer;
types
export const GET_POLLS = 'GET_POLLS';
export const POLLS_LOADING = 'POLLS_LOADING';
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_APPROVAL = 'SHOW_APPROVAL';
list that displays data
import React, { Component } from 'react'
import { PollCard } from '../Components/PollCard'
// import FilterLink from './FilterLink'
import * as moment from 'moment';
import { connect } from 'react-redux'
import { getPolls, getApproval } from '../actions/index';
class PollList extends Component {
componentDidMount() {
this.props.getPolls();
}
render() {
console.log("rendering list")
const { polls } = this.props
const range = 30
var dateRange = moment().subtract(range, 'days').calendar();
var filteredPolls = polls.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange)).reverse()
return (
<React.Fragment>
<button onClick={getApproval}>
Get Approval
</button>
{console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
polls: state.polls
});
const mapDispatchToProps = {
getApproval
};
export default connect(
mapStateToProps,
mapDispatchToProps,
{ getPolls, getApproval }
)(PollList);
// export default PollList;
Your mapDispatchToProps() appears to be configured incorrectly. You need to define a function that returns an object, defining a key-value pair for each action you want to make available as a prop in your component.
const mapDispatchToProps = (dispatch) => {
return {
getApproval: () => {
dispatch(getApproval())
},
getPolls: () => {
dispatch(getPolls())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProp)(PollList);
Now getPolls is available as prop and you can use it in componentDidMount()
componentDidMount() {
this.props.getPolls();
}
You should also create an onClick handler for your getApproval action
handleClick = () => {
this.props.getApproval()
}
And then connect it to your onClick event-listener
<React.Fragment>
<button onClick={this.handleClick}>
Get Approval
</button>
console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
Action File
export const getPolls = () => dispatch => {
fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
.catch(errors => {
dispatch({ type: "GET_ERRORS", payload: errors.response.data })
})
}
Reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return state.filter((poll) => {
return poll.type === "trump-approval"
})
case "GET_ERRORS":
return payload
default:
return state
}
}
export default pollReducer;
You are not calling the action function.
// Either destructure it
const { polls, getApproval } = this.props;
<button onClick={getApproval}>
Get Approval
</button>
// Or use this.props.function
<button onClick={this.props.getApproval}>
Get Approval
</button>
// You don't need this
const mapDispatchToProps = {
getApproval
};
// You don't need this
const mapStateToProps = state => {
return {polls: state.polls};
};
export default connect(
mapStateToProps,
// Doing this is easier, cleaner & faster
{ getPolls, getApproval }
)(PollList);
Here you are doing it correctly;
componentDidMount() {
this.props.getPolls();
}

Action doesn't update the store

|I have the following component based on this:
**WarningModal.js**
import React from 'react';
import ReactDOM from 'react-dom';
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Alert, No} from './pure/Icons/Icons';
import Button from './pure/Button/Button';
import Modal from './pure/Modal/Modal';
import {setWarning} from '../actions/app/appActions';
import configureStore from '../store/configureStore';
const store = configureStore();
export const WarningModal = (props) => {
const {message, withCleanup} = props;
const [
title,
text,
leave,
cancel
] = message.split('|');
const handleOnClick = () => {
props.setWarning(false);
withCleanup(true);
}
return(
<Modal>
<header>{title}</header>
<p>{text}</p>
<Alert />
<div className="modal__buttons-wrapper modal__buttons-wrapper--center">
<button
onClick={() => withCleanup(false)}
className="button modal__close-button button--icon button--icon-only button--text-link"
>
<No />
</button>
<Button id="leave-warning-button" className="button--transparent-bg" onClick={() => handleOnClick()}>{leave}</Button>
<Button id="cancel-warning-button" onClick={() => withCleanup(false)}>{cancel}</Button>
</div>
</Modal>
);
}
WarningModal.propTypes = {
withCleanup: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
setWarning: PropTypes.func.isRequired
};
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const WarningModalContainer = connect(mapStateToProps, {
setWarning
})(WarningModal);
export default (message, callback) => {
const modal = document.createElement('div');
document.body.appendChild(modal);
const withCleanup = (answer) => {
ReactDOM.unmountComponentAtNode(modal);
document.body.removeChild(modal);
callback(answer);
};
ReactDOM.render(
<Provider store={store}>
<WarningModalContainer
message={message}
withCleanup={withCleanup}
/>
</Provider>,
modal
);
};
the issue I have is that 'setWarning' doesn't update the state, it does get called as I have a debugger inside the action and the reducer but the actual property doesn't not change to 'false' when:
props.setWarning(false);
gets called.
I use the following to trigger the custom modal:
const togglePromptCondition =
location.hash === '#access-templates' || location.hash === '#security-groups'
? promptCondition
: isFormDirty || isWarning;
<Prompt message={promptMessage} when={togglePromptCondition} />
To test this even further I have added 2 buttons in the application to toggle 'isWarning' (the state property I am talking about) and it works as expected.
I think that although WarningModal is connected in actual fact it isn't.
REDUCER
...
case SET_WARNING:
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
ACTION
...
export const setWarning = status => {
console.log('action called')
return {
type: SET_WARNING,
payload: status
}
};
...
UPDATE
After having to incorporates the following:
const mapStateToProps = state => {
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
I am now getting:
Maybe this could help?
You have to dispatch the actions in the action creator and the type of the action to dispatch should be always string.
Try this
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
console.log(dispatch)
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
const WarningModalContainer = connect(mapStateToProps, mapDispatchToProps)(WarningModal);
REDUCER
...
case 'SET_WARNING':
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...

Categories

Resources