There is a component:
const Navbar = ({...props}) => {
const dispatch = useDispatch()
const navigation = useSelector(state => state.navigation)
const [nav] = navigation.navs.filter(nav => nav.slug === props.navGroup)
useEffect(() => {
dispatch(fetchNavigation());
}, [dispatch])
return (
<nav className={styles.nav}>
{nav
? nav.all_elements.map(link =>
<Link
className={styles.navLink}
to={link.url}
key={link.id}
>
{link.name}
</Link>
)
: <div>Something else</div>
}
</nav>
)
}
Reducer:
import { createSlice } from "#reduxjs/toolkit";
import { fetchNavigation } from "../services/fetchNavigation"
const initialState = {
"count": 0,
"next": null,
"previous": null,
"navs": [],
"loaded": false
}
const navSlice = createSlice({
name: 'navigation',
initialState: initialState,
extraReducers: {
[fetchNavigation.fulfilled]: (state = initialState, action) => {
state.count = action.payload.count;
state.next = action.payload.next;
state.previous = action.payload.previous;
state.loaded = true
state.navs = action.payload.results;
}
},
})
Async action:
import { createAsyncThunk } from "#reduxjs/toolkit";
import { instance } from "../../API/config"
export const fetchNavigation = createAsyncThunk(
"navigation/fetchNavigation", async () => {
const response = await instance.get(`nav/`)
return response.data
}
);
Problem:
I show the component in the header and footer
therefore, it makes two requests to API, how can i check that the data is already in the store before calling createAsyncThunk?
I tried to get the current state of the 'loaded' key through getState() but it does not change InitialState before the request, so nothing came out
Related
Hi folks I am new to React just learning reduxjs/toolkit for application state management. I have created simple application for handling posts. Currently what I am doing is loading list of posts using async Thunk called fetchPosts. My async thunk is being called twice on application load and I have literally no idea what is making to do so. I am using react version 18.2.0 and here is my post slice.
import { createAsyncThunk, createSlice, nanoid } from "#reduxjs/toolkit";
import {sub} from 'date-fns';
import axios from 'axios';
const POST_URL = 'http://jsonplaceholder.typicode.com/posts';
const initialState = {
posts : [],
status : 'idle',
error : null
};
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
const response = await axios.get(POST_URL);
return response.data;
});
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers:{
postAdded: {
reducer(state, action) {
state.posts.push(action.payload);
},
prepare(title,content, userId){
return {
payload:{
id: nanoid(),
title,
content,
userId,
date: new Date().toISOString(),
reactions: {
thumbsUp: 0,
wow:0,
heart:0,
rocket:0,
coffee:0
}
}
}
}
},
reactionAdded(state, action) {
const {postId, reaction} = action.payload;
const existingPost = state.posts.find(post => post.id === postId);
if (existingPost) {
existingPost.reactions[reaction]++;
}
}
},
extraReducers(builder){
builder.addCase(fetchPosts.pending, (state, action) => {
state.status = 'loading';
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded';
let min = 1;
const loadedPosts = action.payload.map(post =>
{
post.date = sub(new Date(), {minutes: min++}).toISOString();
post.reactions = {
thumbsUp: 0,
wow: 0,
heart:0,
rocket:0,
coffee:0
}
return post;
});
state.posts = loadedPosts;
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message
})
}
});
export const {postAdded,reactionAdded} = postsSlice.actions;
export const selectAllPosts = (state) => state.posts.posts;
export const getPostsStatus = (state) => state.posts.status;
export const getPostsError = (state) => state.posts.error;
export default postsSlice.reducer;
And here is my Post list component.
import React from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { selectAllPosts, getPostsError, getPostsStatus, fetchPosts } from './postsSlice';
import { useEffect } from 'react';
import PostExcerpt from './PostExcerpt';
const PostsList = () => {
const dispatch = useDispatch();
const posts = useSelector(selectAllPosts);
const postStatus = useSelector(getPostsStatus);
const postError = useSelector(getPostsError);
useEffect(() => {
console.log('post status: ', postStatus);
if(postStatus === 'idle')
{
dispatch(fetchPosts());
}
},[postStatus])
let content;
if(postStatus === 'loading'){
console.log('loading');
content = <p>"Loading..."</p>
}
else if(postStatus === 'succeeded'){
console.log('succeded');
const orderedPosts = posts.slice().sort((a,b) => b.date.localeCompare(a.date));
content = orderedPosts.map(post => <PostExcerpt key={post.id} post={post}/>);
}
else if (postStatus === 'failed'){
content = <p>{postError}</p>;
}
return (
<section>
<h2>Posts</h2>
{content}
</section>
);
}
export default PostsList
So this was happening due to the mount -> unmount -> remount behaviour of component implemented in react version 18 useEffect.
I am new in redux and redux-toolkit. I am trying to get data from api.
I get error and can not receive data from api. I am using redux-toolkit library.
This is my App.js:
function App() {
const companies = useSelector(state => state.companyList);
console.log(companies)
return (
<div className="App">
<header className="App-header">
{companies.map(company => {
return(
<h1>{company.name}</h1>
)
})}
<h1>hello</h1>
</header>
</div>
);
}
export default App;
This is createSlice.js
const getCompanies = axios.get(
"https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8"
).then((response) => {
console.log(response.data)
return response.data;
}).catch((ex) => {
console.log(ex)
})
export const companySlice = createSlice({
name: "companyList",
initialState: {value: getCompanies},
reducers: {
addCompnay: (state, action) => {},
},
});
export default companySlice.reducer;
Here is store.js
import { configureStore } from "#reduxjs/toolkit";
import companyReducer from "./features/companySlice/compnayList";
export const store = configureStore({
reducer:{
companyList: companyReducer,
}
})
In the browser, I receive this error:
enter image description here
You are making a lot of mistakes here so be sure to check out some tutorials and docs: https://redux-toolkit.js.org/tutorials/quick-start
You need to use createAsyncThunk and handle the response in extraReducers: https://redux-toolkit.js.org/rtk-query/usage/migrating-to-rtk-query#implementation-using-createslice--createasyncthunk
In companySlice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
export const getCompanies = createAsyncThunk(
"companyList/getCompanies",
async () => {
try {
const response = await axios.get(
"https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8"
);
return response.data;
} catch (error) {
console.error(error);
}
});
const companySlice = createSlice({
name: "companyList",
initialState: {
company: {},
isLoading: false,
hasError: false
},
extraReducers: (builder) => {
builder
.addCase(getCompanies.pending, (state, action) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(getCompanies.fulfilled, (state, action) => {
state.company = action.payload;
state.isLoading = false;
state.hasError = false
})
.addCase(getCompanies.rejected, (state, action) => {
state.hasError = true
state.isLoading = false;
})
}
});
// Selectors
export const selectCompanies = state => state.companyList.company;
export const selectLoadingState = state => state.companyList.isLoading;
export const selectErrorState = state => state.companyList.hasError;
export default companySlice.reducer;
Then you import selectCompanies wherever you want to use it and access it with useSelector.
In App.js:
import { useSelector } from "react-redux";
import { selectCompanies } from "WHEREEVER selectCompanies IS EXPORTED FROM";
function App() {
// Company list state
const companies = useSelector(selectCompanies);
// Rest of the code
.....
.....
.....
.....
}
export default App;
{i am working on a MERN stack project. In my frontend i am using redux for state management. but i am facing while fetching products from backend if i add single reducer in redux store to fetch All Products it works fine but when i add second reducer to get Product details or wrap that single reducer in brackets in my store it shows error and fetch all products also gives
{products: Array(0), loading: false, error: ''}
error
Uncaught TypeError: products.map is not a function
at ProductList (ProductList.jsx:31:1)
at renderWithHooks (react-dom.development.js:16305:1)
at mountIndeterminateComponent (react-dom.development.js:20074:1)
at beginWork (react-dom.development.js:21587:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
at invokeGuardedCallback (react-dom.development.js:4277:1)
at beginWork$1 (react-dom.development.js:27451:1)
at performUnitOfWork (react-dom.development.js:26557:1)
at workLoopSync (react-dom.development.js:26466:1)
here is my code
store.js
import { configureStore } from "#reduxjs/toolkit";
import { productSlice, productDetailSlice } from "./Slice/ProductSlice.js";
const store = configureStore({
reducer: {
products:productSlice.reducer,
productDetails:productDetailSlice.reducer
},
});
export default store;
ProductSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
export const getProducts = createAsyncThunk(
"products/getProducts",
async () => {
const res = await axios.get("/product/");
return res.data;
}
);
export const getProductDetails = createAsyncThunk(
"productDetail/getProductDetails",
async (id) => {
const res = await axios.get(`/product/${id}`);
return res.data;
}
);
export const productSlice = createSlice({
name: "products",
initialState: {
products: [],
loading: false,
error: "",
},
reducers: {},
extraReducers: {
[getProducts.pending]: (state, action) => {
state.loading = true;
},
[getProducts.fulfilled]: (state, action) => {
state.loading = false;
state.products = action.payload.products;
},
[getProducts.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload;
},
},
});
export const productDetailSlice = createSlice({
name: "productDetail",
initialState: {
loading: false,
productDetail: [],
error: null,
},
reducers: {},
extraReducers: {
[getProductDetails.pending]: (state, action) => {
state.loading = true;
},
[getProductDetails.fulfilled]: (state, action) => {
state.loading = false;
state.productDetail = action.payload.product;
},
[getProductDetails.rejected]: (state, action) => {
state.loading = false;
state.error = action.payload;
},
},
});
ProductList.jsx
import React, { useEffect } from "react";
import Banner from "../../Components/Banner/Banner";
import Footer from "../../Components/Footer/Footer";
import Heading from "../../Components/Heading/Heading";
import Navbar from "../../Components/Navbar/Navbar";
import Product from "../../Components/Product/Product";
import "./productlist.css";
import { useDispatch, useSelector } from "react-redux";
import { getProducts } from "../../Redux/Slice/ProductSlice.js";
import Loader from "../../Components/Loader/Loader";
const ProductList = () => {
const products = useSelector((state) => state.products);
console.log(products);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getProducts());
}, []);
return (
<div>
<Banner />
<Navbar />
{products.loading ? (
<Loader />
) : (
<div className="container">
<div className="filters"></div>
<div className="products">
<div className="list">
{products.map((product) => (
<Product
key={product._id}
name={product.name}
price={product.price}
seller={product.seller}
images={product.images}
id={product._id}
/>
))}
</div>
</div>
</div>
)}
<Footer />
</div>
);
};
export default ProductList;
export default ProductList
ProductDetails
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getProductDetails } from "../../Redux/Slice/ProductSlice.js";
const ProductDetails = ({ match }) => {
const product = useSelector((state) => state.product);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getProductDetails(match.params.id));
}, []);
let price = 279.95;
return (
<div>
</div>
);
};
export default ProductDetails;
export default ProductDetails
By changing from
const store = configureStore({
reducer: productSlice.reducer,
});
to
const store = configureStore({
reducer: {
products: productSlice.reducer
},
})
you change the shape of your state.
What was state.loading before is now state.products.loading - and what was state.products before is now state.products.products.
You will have to change your selectors accordingly.
in below react/ redux toolkit app , in userslice file I am trying to export my entities piece of state and import in main file , when I try to console in comes undefined , not sure why its undefined ,
but When I trying to pull the {entities} directly form state its working fine, would like to know why its showing undefined in console, if anyone knows please check ?
below is the state part which I am getting undefined
export const { SelectUserList } = (state) => state.userslist.entities;
below is the console which shows undefiend
console.log(SelectUserList);
my slice file is below
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
export const fetchuserlist = createAsyncThunk(
"userslist/fetchusers",
async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
}
);
const userSlice = createSlice({
name: "userslist",
initialState: {
entities: [],
loading: false,
},
reducers: {
// userAdded(state, action) {
// state.entities.push(action.payload);
// },
},
extraReducers: {
[fetchuserlist.pending]: (state, action) => {
state.loading = true;
},
[fetchuserlist.fulfilled]: (state, action) => {
state.entities = [...state.entities, ...action.payload];
state.loading = false;
},
[fetchuserlist.rejected]: (state, action) => {
state.loading = false;
},
},
});
export const { userAdded, userUpdated, userDeleted } = userSlice.actions;
export const { SelectUserList } = (state) => state.userslist.entities;
export default userSlice.reducer;
me component file is below
import React from "react";
import { fetchuserlist, SelectUserList } from "./features/userSlice";
import { useDispatch, useSelector } from "react-redux";
const Mainlist = () => {
const dispatch = useDispatch();
const { entities } = useSelector((state) => state.users);
console.log(SelectUserList);
return (
<div>
<button onClick={() => dispatch(fetchuserlist())}>Load list</button>
{entities?.map((s) => (
<div className="user_list" key={s.id}>
<h4>{s.id}</h4>
<h6>{s.email}</h6>
<button>delete</button>
<button>edit</button>
</div>
))}
</div>
);
};
export default Mainlist;
In your slice you are declaring the function in the wrong way. You should declare the SelectUserList function like this:
export const SelectUserList = (state) => state.userslist.entities;
In your component file you should access the entities returned in SelectUserList with a useSelector. Like this:
const usersListEntities = useSelector(SelectUserList);
I am creating a react/redux toolkit app , I am saving data into redux , this is like a posts page and when user clicks a post it goes to single post page,
the data is saving in redux state and working fine, but when I refresh the detail page the redux data is gone not sure why its happening I want to keep data even if user refresh the page also.
my posts slice
import {
createSlice,
// createSelector,
// PayloadAction,
createAsyncThunk,
} from "#reduxjs/toolkit";
import axios from "axios";
export const fetchAllPostsAsync = createAsyncThunk(
"posts/fetchAllPostsAsync",
async (_, thunkAPI) => {
try {
const response = await axios.get("http://localhost:5000/posts/");
return response.data;
} catch (error) {
throw thunkAPI.rejectWithValue({ error: error.message });
}
}
);
export const postsSlice = createSlice({
name: "posts",
initialState: {
allposts: [],
loading: "idle",
error: "",
selectedPost: {},
},
reducers: {
selectedpost: (state, action) => {
state.selectedPost = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(fetchAllPostsAsync.pending, (state, action) => {
state.allposts = [];
state.loading = "Loading";
});
builder.addCase(fetchAllPostsAsync.fulfilled, (state, action) => {
state.allposts = action.payload;
state.loading = "loaded";
});
builder.addCase(fetchAllPostsAsync.rejected, (state, action) => {
state.allposts = "data not loaded";
state.loading = "error";
state.error = action.error.message;
});
},
});
export const { selectedpost } = postsSlice.actions;
export default postsSlice.reducer;
my posts page
import React, { useEffect } from "react";
import SinglePost from "./SinglePost";
import { useSelector, useDispatch } from "react-redux";
import { fetchAllPostsAsync } from "../features/posts/postsSlice";
const Posts = () => {
const dispatch = useDispatch();
const posts = useSelector((state) => state.posts.allposts);
console.log(posts);
useEffect(() => {
dispatch(fetchAllPostsAsync());
}, [dispatch]);
return (
<div>
<button>Add new </button>
{posts?.map(({ _id, ...rest }) => (
<SinglePost key={_id} rest={rest} id={_id} />
))}
</div>
);
};
export default Posts;
my details page
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { selectedpost } from "../features/posts/postsSlice";
const Details = () => {
const { postId } = useParams();
const [postshere, setPostshere] = useState(selectedpost);
const dispatch = useDispatch();
const posts = useSelector((state) => state.posts.allposts);
const selectedpostsingle = useSelector((state) => state.posts.selectedPost);
const selectedPostArray = posts?.filter((post) => post._id === postId);
useEffect(() => {
dispatch(selectedpost(setPostshere(selectedPostArray[0])));
}, [dispatch]);
console.log(postId, "ffffff");
return (
<div>
<div key={postshere._id}>
{postshere.title} {postshere.message}
</div>
</div>
);
};
export default Details;
this is single post page
import React from "react";
import { Link } from "react-router-dom";
const SinglePost = ({ rest, id }) => {
// let history = useHistory();
// function handleClick() {
// history.push(`/${rest._id}`);
// }
console.log(rest);
return (
<Link to={`/details/${id}`}>
<div className="post">
<div className="post__title">{rest.title}</div>
<div className="post__body">{rest.message}</div>
<div className="post__body">{rest.date}</div>
</div>
</Link>
);
};
export default SinglePost;
error message
×
TypeError: Cannot read property 'title' of undefined
Details
src/components/Details.js:20
17 | console.log(postId, "ffffff");
18 | return (
19 | <div>
> 20 | <div key={postshere._id}>
| ^ 21 | {postshere.title} {postshere.message}
22 | </div>
23 | </div>
View compiled