I am using react-redux with redux and redux-toolkit. And according to this example, i created an async dispatch that calls the reducer action when resolved.
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
export const BlogSlice = createSlice({
name: "Blog",
initialState: {
BlogList: null,
},
reducers: {
getBlogList: (state, action) => {
console.log(action.payload);
state.BlogList = action.payload;
}
},
});
export const { getBlogList } = BlogSlice.actions;
export const getBlogListAsync = (user_id) => (dispatch) => {
axios.get(`/api/blog/getblogs/${user_id}`).then((res) => {
console.log(res.data);
dispatch(getBlogList(res.data.result));
});
};
export const selectBlogList = (state) => state.Blog.BlogList;
export default BlogSlice.reducer;
I have used it in a component accordingly so that, the component dispatches getBlogListAsync and that logs the res.data but getBlogList is not being dispatched. I tried putting other console.log() but don't understand what is wrong.
A similar Slice is working perfectly with another Component.
It is hard to say for sure what's wrong here because there is nothing that is definitely wrong.
res.data.result?
You are logging res.data and then setting the blog list to res.data.result. My best guess as to your mistake is that res.data.result is not the the correct property for accessing the blogs, but I can't possibly know that without seeing your API.
console.log(res.data);
dispatch(getBlogList(res.data.result));
missing middleware?
Is there any chance that "thunk" middleware is not installed? If you are using Redux Toolkit and omitting the middleware entirely, then the thunk middleware will be installed by default. Also if this were the case you should be getting obvious errors, not just nothing happening.
it seems fine...
I tested out your code with a placeholder API and I was able to get it working properly. Maybe this code helps you identify the problem on your end. Code Sandbox Demo.
import React from "react";
import { createSlice, configureStore } from "#reduxjs/toolkit";
import axios from "axios";
import { Provider, useDispatch, useSelector } from "react-redux";
export const BlogSlice = createSlice({
name: "Blog",
initialState: {
BlogList: null
},
reducers: {
getBlogList: (state, action) => {
console.log(action.payload);
state.BlogList = action.payload;
}
}
});
export const { getBlogList } = BlogSlice.actions;
const store = configureStore({
reducer: {
Blog: BlogSlice.reducer
}
});
export const getBlogListAsync = (user_id) => (
dispatch: Dispatch
) => {
// your url `/api/blog/getblogs/${user_id}`
const url = `https://jsonplaceholder.typicode.com/posts?userId=${user_id}`; // placeholder URL
axios.get(url).then((res) => {
console.log(res.data);
// your list: res.data.result <-- double check this
const list = res.data; // placeholder list
dispatch(getBlogList(list));
});
};
export const selectBlogList = (state) => state.Blog.BlogList;
const Test = () => {
const dispatch = useDispatch();
const blogs = useSelector(selectBlogList);
const user_id = "1";
return (
<div>
<button onClick={() => dispatch(getBlogListAsync(user_id))}>
Load Blogs
</button>
<h3>Blog Data</h3>
<div>{JSON.stringify(blogs)}</div>
</div>
);
};
export default function App() {
return (
<Provider store={store}>
<Test />
</Provider>
);
}
Related
Problem: My next.js app crash on client side because of empty store object, but if I try to read this object in getServerSideProps it`s ok.
I have 2 pages in my app, profile/[id] and post/[id], all of them have getServerSideProps
User flow:
User coming on profile/[id] by friend`s link
On profile/[id] page he has profile data and 3x3 posts grid, every post is a link to post/[id]
Click on post
Navigate to post/[id] - here he has some post data: username, image, createdAt etc...
Expected: Server render html for post page after successful request
Received: Client crash after trying to read field of empty object
Question: Can you tell my what's wrong with my code? I have HYDRATE for postSlice and default next-redux-wrapper code so I'm confused.
Code:
store.ts
import {configureStore} from "#reduxjs/toolkit";
import {createWrapper} from "next-redux-wrapper";
import profileSlice from './reducers/profileSlice';
import postsSlice from './reducers/postsSlice';
import postSlice from './reducers/postSlice';
export const makeStore = () =>
configureStore({
reducer: {
profile: profileSlice,
posts: postsSlice,
post: postSlice
},
devTools: true
});
export type Store = ReturnType<typeof makeStore>;
export type RootState = ReturnType<Store['getState']>;
export const wrapper = createWrapper<Store>(makeStore);
_app.tsx
//
...imports
//
function App({Component, ...rest}: AppProps) {
const {store, props} = wrapper.useWrappedStore(rest);
const {pageProps} = props;
return (
<Provider store={store}>
<ApolloProvider client={client}>
<GlobalStyle/>
<ThemeProvider theme={theme}>
<LookyHead />
<Component {...pageProps} />
</ThemeProvider>
</ApolloProvider>
</Provider>
);
}
export default App;
postSlice.ts
//
some imports and interfaces...
//
export const postSlice = createSlice({
name: 'post',
initialState,
reducers: {
setPost: (state, action) => {
state.post = action.payload
}
},
extraReducers: {
[HYDRATE]: (state, action) => {
return {
...state,
...action.payload.post,
};
},
},
});
export const { setPost } = postSlice.actions;
export default postSlice.reducer;
Post component of posts grid on profile, here i have link to post/[id]
function Post({previewUrl, likesCount, commentsCount, duration, id}: Props) {
some code...
return (
<Link href={`/post/${id}`}>
<Container
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
<img src={previewUrl} alt="image"/>
<PostFooter
isHover={isHover}
likesCount={likesCount}
commentsCount={commentsCount}
time={time}
/>
</Container>
</Link>
);
}
export default memo(Post);
getServerSideProps in post/[id]
export const getServerSideProps =
wrapper.getServerSideProps(
(store) =>
async ({query}) => {
const id = query!.id as string
try {
const {data} = await client.query<Res, Vars>({
query: GET_POST,
variables: {
postId: id
}
});
console.log(data.publicPost) // Here I have data!
store.dispatch(setPost(data.publicPost))
} catch (e) {
console.log(e)
}
return {
props: {}
}
})
export default Post;
Data component inside post/[id], where client crash
//
imports...
//
function Data() {
const {post} = useAppSelector(({post}) => post) // looks weird but its ok
const parsed = parseISO(post?.createdAt) // Here my client fall
const date = format(parsed, 'dd MMMM yyyy, HH:MM', {locale: enGB})
return (
...
);
}
export default Data;
I'm trying to learn Redux. I'm trying a test app and I'm stuck on this error and have no idea why the reducer isn't updating the state. I've looked at the common problems that cause this and can't seem to find those errors in mine, my reducer doesn't change the state
logItem.js
import React from 'react';
import {Link} from 'react-router-dom';
import {connect} from 'react-redux';
import {addCurrent} from '../../actions/logAction';
const LogItem = (props) => {
const {message, tech, date, attention} = props.singleLog;
console.log(props.currentLog)
const updateLog = (singleLog) => {
updateLog(singleLog, singleLog._id)
}
const current = (singleLog) => {
console.log(singleLog)
addCurrent(singleLog)
}
return (
<div>
<h3>{message}</h3>
<p className={`text ${attention}`}>was last updated by {tech} on {date}</p>
<Link onClick={() => current(props.singleLog)} to='/addLog'>update</Link>
<button>delete</button>
</div>
)
}
const mapStateToProps = (state) => ({
currentLog : state.log.current
})
export default connect(mapStateToProps, {addCurrent})(LogItem);
logaction.js
export const addCurrent = (singleLog) => {
console.log(singleLog)
return({type: SET_CURRENT, payload: singleLog})
}
import { SET_LOADING, GET_LOGS, LOGS_ERROR, ADD_LOG, UPDATE_LOG, SET_CURRENT } from '../actions/types';
const initialState = {
logs: null,
loading: true,
current: null,
errors: null
}
logReducer.js
import { SET_LOADING, GET_LOGS, LOGS_ERROR, ADD_LOG, UPDATE_LOG, SET_CURRENT } from '../actions/types';
const initialState = {
logs: null,
loading: true,
current: null,
errors: null
}
export default (state = initialState, action) => {
console.log(action.type)
switch(action.type) {
case SET_CURRENT:
console.log("5")
console.log(action.payload)
return {
...state,
current: action.payload,
errors:null
}
default:
return state;
}
}
Your action does not get dispatched, not sure why you claim the reducer doesn't do anything when obviously the action isn't even getting dispatched. Please use redux devtools next time so you at least know what's going on and can articulate a better question.
You should replace addCurrent(singleLog) with props.addCurrent(singleLog)
Try to replace
export const addCurrent = (singleLog) => {
console.log(singleLog)
return({type: SET_CURRENT, payload: singleLog})
}
with
export const addCurrent = (singleLog) => dispatch => {
console.log(singleLog)
dispatch({type: SET_CURRENT, payload: singleLog})
}
I am making a cart functionality using redux toolkit's createSlice. But in the browser's application tab, the value of the localStorage is showing as [object Object]. Can someone help me with this please?
cartSlice.js
import { createSlice } from '#reduxjs/toolkit';
import axios from 'axios'
const cartItemsFromStorage = localStorage.getItem('cartItems') ? localStorage.getItem('carts') : []
export const cartSlice = createSlice({
name: 'cart',
initialState: {
cartItems: cartItemsFromStorage,
},
reducers: {
add: (state, action) => {
const item = action.payload
const existItem = state.cartItems.find(x => x.product === item.product)
if (existItem) {
const currentItems = state.cartItems.map(x => x.product === existItem.product ? item : x)
state.cartItems = [...currentItems]
} else {
state.cartItems = [...state.cartItems, item]
localStorage.setItem('cartItems', state.cartItems)
}
},
// remove: (state, action) => {
// },
},
});
const { add } = cartSlice.actions;
export const selectCartItems = state => state.cart.cartItems;
export const addToCart = (id, qty) => async (dispatch) => {
const { data } = await axios.get(`/api/products/${id}`)
dispatch(add({
product: data._id,
name: data.name,
imgae: data.imgae,
price: data.price,
countInStock: data.countInStock,
qty
}))
}
export default cartSlice.reducer;
CartScreen.js
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectCartItems, addToCart } from '../features/cartSlice'
const CartScreen = ({ match, location, history }) => {
const productId = match.params.id
const qty = location.search ? Number(location.search.split('=')[1]) : 1
const dispatch = useDispatch()
const cartItems = useSelector(selectCartItems)
useEffect(() => {
if (productId) {
dispatch(addToCart(productId, qty))
}
console.log(`cartItems: ${cartItems}`)
}, [dispatch, productId, qty])
return (
<div>
Cart
</div>
)
}
export default CartScreen
The next time I refresh the page, the initialState is not there, insted it shows [object Object]. I know the problem is with localStorage. Please correct me.
I think the problem is you are doing localStorage stuff in a reducer action. Reducer only can do simple operations and modify the state, so I encourage you to try to pass localStorage calls into the thunk action.
I am adding and deleting items in an array using the createSlice() function from the redux-toolkit library.
The addProject reducer function works fine but the removeRpoject doesn't work.
//projects.js
import { createSlice } from "#reduxjs/toolkit";
let lastId = 0;
const slice = createSlice({
name: "projects",
initialState: [],
reducers: {
projectAdded: (projects, action) => {
projects.push({
id: ++lastId,
name: action.payload.name,
});
},
projectRemoved: (projects, action) =>
(projects = projects.filter((pro) => pro.id !== action.payload.id)),
},
});
export const { projectAdded, projectRemoved } = slice.actions;
export default slice.reducer;
//store.js
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./projects";
const store = configureStore({ reducer: reducer });
export default store;
//index.js
import store from "./store/store";
import { projectAdded, projectRemoved } from "./store/projects";
const unsubscribe = store.subscribe(() => {
console.log("store Changed", store.getState());
});
store.dispatch(projectAdded({ name: "Project 1" }));
store.dispatch(projectRemoved({ id: 1 }));
You are replacing the root object of your state (projects) - this kills the immer change detection.
The simplest way is to just return the filtered array without assigning it to the draft object first:
projectRemoved: (projects, action) => projects.filter((pro) => pro.id !== action.payload.id),
See also:
https://immerjs.github.io/immer/docs/update-patterns#array-mutations
I am trying to get redux working in my react-native app. Basically, I have a signIn action defined in my authActions.js file:
const signInAction = () => {
return {
type: 'signIn',
};
};
export { signInAction };
Then I have an authReducer defined as this in authReducer.js:
const initialState = {
isAuthenticated: false,
}
const authReducer = (state = initialState, action) => {
switch(action.type) {
case "signIn":
return Object.assign({}, state, {
isAuthenticated: true,
})
default: return state;
}
};
export default authReducer;
I combine that reducer in my rootReducer.js file
import { combineReducers } from 'redux';
import auth from 'app/src/redux/reducers/authReducer.js';
const rootReducer = combineReducers({
auth,
});
export default rootReducer;
and then created a store in reduxIndex.js:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from 'app/src/redux/reducers/rootReducer.js';
let store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
export default store;
I wrapped my app in a <Provider> component, and that seems to be working fine (I can read from the state and see the value of isAuthenticated. However, when I try to dispatch an action using mapDispatchToProps in one of my views the function is undefined:
// More imports
// ...
import { connect } from 'react-redux';
import { signInAction } from 'app/src/redux/actions/authActions.js';
const mapStateToProps = (state) => {
return {};
}
const mapDispatchToProps = (dispatch) => {
return {
onSignIn: () => { dispatch(signInAction) },
};
}
class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
}
}
onSignInPress() {
// ******* this is where the error occurrs ****
this.props.onSignIn();
}
render() {
const {navigation} = this.props;
return (
<View style={SignInStyles.container}>
<ScrollView>
<View>
<Button
large
title="SIGN IN"
backgroundColor={colors.primary}
onPress={this.onSignInPress}
/>
</View>
</ScrollView>
</View>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
I cant really see where I am going wrong, but im sure its a simple mistake somewhere. The specific error I get is :
"undefined is not an object. Evaluating this.props.onSignIn"
The onSignInPress callback isn't bound to any particular object, so when it gets called this is undefined.
The easy way to fix it is to use arrow syntax to make it always be bound. In your class definition:
onSignInPress = () => {
this.props.onSignIn();
}
Google found me this Medium article from Miron Machnicki which explains the differences and possible alternative syntaxes in pretty good detail.