Reactjs using redux ("function name") is not a function - reactjs

I am receiving the error vesselList is not a function in my homescreen that's using a redux slice
the error is in vesselList() in the dispatch in the useEffect hook please suggest if there is a better way i can reform this code.
the Slice :
vesselSlice :
export const vesselSlice = createSlice({
name: "vesselList",
initialState: {
vessels: [],
},
reducers: {
vesselList: (state, action) => {
state.value = action.payload;
},
},
});
export const {
vesselList,
} =
(keyword = "") =>
async (dispatch) => {
try {
dispatch({ type: VESSEL_LIST_REQUEST });
const { data } = await axios.get(
"http://127.0.0.1:8000/api/vessels/info"
);
dispatch({
type: VESSEL_LIST_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: VESSEL_LIST_FAIL,
payload:
error.response && error.response.data.detail
? error.response.data.detail
: error.message,
});
}
};
export default vesselSlice.reducer;
HomeScreen.js :
function HomeScreen() {
const dispatch = useDispatch();
const Listvessel = useSelector((state) => state.vesselList);
const { error, loading, vessels } = Listvessel;
useEffect(() => {
dispatch(vesselList());
}, [dispatch]);
return (
<div>
Fleet vessels :
<div className="fleet-vessels-info">
{vessels.map((vessel) => (
<VesselCard vessel={vessel} />
))}
</div>
</div>
);
}
export default HomeScreen;

Because vesselList not yet export, why you not use createAsyncThunk to fecth url and dispatch to redux store?

Related

react : datas displaying in the wrong order after submit

I'm using react with Context Api and react hook form to add a new post. When I submit a post, the new post is in the last position but when I refresh the page, the post goes to the top position. I want the new post to be in the top position immediately. Do you know what I did wrong, please?
PostsContext.tsx
export interface Post {
id: number
content: string
thumbnail: {
url: string
}
created_at: string
updated_at: string
userId: number
}
export interface PostsState {
posts: Post[]
}
export type PostsAction =
| { type: 'SET_POSTS'; payload: Post[] }
| { type: 'ADD_POST'; payload: Post }
const initialState: PostsState = {
posts: [],
}
const reducer = (state: PostsState, action: PostsAction) => {
switch (action.type) {
case 'SET_POSTS':
return { posts: action.payload }
case 'ADD_POST':
return { posts: [...state.posts, action.payload] }
default:
return state
}
}
export const PostsContext = createContext<{
state: PostsState
dispatch: React.Dispatch<PostsAction>
}>({
state: initialState,
dispatch: () => null,
})
export const PostsProvider = ({ children }: PropsWithChildren<{}>) => {
const [state, dispatch] = useReducer(reducer, initialState)
return <PostsContext.Provider value={{ state, dispatch }}>{children}</PostsContext.Provider>
}
Feed.tsx
const Feed = () => {
const { state, dispatch } = usePostsContext()
useEffect(() => {
const fetchPosts = async () => {
const res = await fetch(`${import.meta.env.VITE_API_URL}/api/posts`, {
credentials: 'include',
})
const data = await res.json()
if (res.ok) {
dispatch({ type: 'SET_POSTS', payload: data })
}
}
fetchPosts()
}, [])
return (
<div className="container mx-auto">
<PostForm />
{state.posts.length < 1 ? (
<div className="mt-4 p-8 text-center border border-gray-200 rounded-lg">
<h2 className="text-2xl font-medium">There's nothing here...</h2>
<p className="mt-4 text-sm text-gray-500">
Created posts will appear here, try creating one!
</p>
</div>
) : (
state.posts.map((post) => <Posts key={post.id} post={post} />)
)}
</div>
)
}
export default Feed
PostForm.tsx
const PostForm = () => {
const { dispatch } = usePostsContext()
const {
register,
handleSubmit,
control,
watch,
reset,
formState: { isSubmitSuccessful, errors },
} = useForm<FormInput>({
defaultValues: {
content: '',
},
resolver: yupResolver(postSchema),
})
const selectedFile = watch('thumbnailFile')
const onSubmit: SubmitHandler<FormInput> = async (data) => {
const formData = new FormData()
formData.append('content', data.content)
formData.append('thumbnailFile', data.thumbnailFile[0])
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/posts`, {
method: 'POST',
credentials: 'include',
body: formData,
})
const post = await response.json()
if (response.ok) {
console.log('post created', post)
dispatch({ type: 'ADD_POST', payload: post })
reset()
}
}
return (
<MyForm />
)
}
export default PostForm
Looks like your ADD_POST reducer is adding a new post to the end of the list since you are placing the action payload after the destructing of your old posts.
To place a post at the beginning of the list you need to place the action payload before destructing the old list of posts.
i.e.,
case 'ADD_POST':
return { posts: [action.payload, ...state.posts] }

react toastify with redux from axios API

i am trying to send the error messages that sent from my server ( express ) to axios and the error message displays in toastify component but the error message doesn't show up here is the login axios function with the toastify how can i display toastify message inside my page from redux ?
here is my code :
// redux controller
const login = async (username, password) => {
await axios.post("/login",{username,password,},
{ withCredentials: true });};
// reducer page
export function generateError(prop) {
return function (dispatch) {
dispatch({
type: "USER_FAIL"
});
toast.error(prop);
};
}
export function generateSuccess(prop) {
return function (dispatch) {
dispatch({
type: "USER_SUCCESS"
});
toast.success(prop);
};
}
export const login = createAsyncThunk(
"/login",
async ({ username, password }) => {
try {
const data = await authService.login(username, password);
if (data) {
if (data.errors) {
const { username, password } = data.errors;
if (username) generateError(username)
else if (password) generateError(password);
} else {
generateSuccess(data.success);
}
}
return { user: data };
} catch (error) {
console.log(error);
}
}
);
// login page
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ username, password }));
}
i am using react-tostify and #redux-toolkit but the message doesn't display inside my page
i fixed it and here is my code :
// auth.js ( redux page )
export const login = createAsyncThunk(
"/login",
async ({ username, password }) => {
try {
const {data} = await axios.post(
"/login",
{
username,
password,
},
{ withCredentials: true }
);
return { user: data };
} catch (error) {
console.log(error);
}
});
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
const authSlice = createSlice({
name: "auth",
initialState,
extraReducers: {
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
}})
const { reducer } = authSlice; export default reducer;
Login Page :
const { isLoggedIn } = useSelector((state) => state.auth);
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ username, password })).then(data => {
console.log(data)
if (data.payload.user) {
if (data.payload.user.errors) {
const { username, password } = data.payload.user.errors;
if (username) generateError(username)
else if (password) generateError(password);
} else {
generateSuccess(data.success);
navigate("/dashboard");
}
}
})
}
i realized when i back the data it has an object name payload i used it to get the error messages from express and then i put the message in toastify function gettingError and here it is
const generateError = error => {
toast.error(error, {
position: "bottom-right",
})
}
Hai I'm also looking for the same problem while searching I found a solution at with this : react-toastify-with-redux
my Code : authAction.js
import 'react-toastify/dist/ReactToastify.min.css';
import { toast} from 'react-toastify';
export const registerUser = (userData) => dispatch =>{
axios.post('user/register',userData)
.then(res=>toast.success('Your Account Created Successfully 👍'))
.then(res=> window.location = '/authentication/sign-in')
.catch(err=>dispatch(
{
type: GET_ERRORS,
payload: err.response.data
}
),toast.error("Error 😣"))
// .catch((err)=> {return })
};
On your signUp page just add
<ToastContainer />
That's all ...
This answer is probably late. But I came across this problem and decided to do it my way. I know there is toast. promise to handle promises and I don't want to call dispatch.then every time. So I can up with passing dispatch to my action wrapper. Here is my code.
// utils.ts
type ArgumentTypes<F extends CallableFunction> = F extends (
...args: infer A
) => any
? A[0]
: never;
export const withToast = <T = AnyAction | typeof createAsyncThunk>(
action: T,
{ pending, error, success }: ToastPromiseParams<T>
) => {
return (
dispatch: ReturnType<typeof useAppDispatch>,
actionParams?: ArgumentTypes<T & CallableFunction> | void
) => {
const promise = dispatch(
(action as CallableFunction)(actionParams as any)
).unwrap();
toast.promise(promise, {
pending,
error,
success,
});
};
};
// actions.ts
export const login = createAsyncThunk(
"user/login",
async (payload: {
email: string;
password: string;
}): Promise<Partial<LoginAPIResponse>> => {
const { data } = await axios.post(`${API}/${LOGIN_EP}/`, payload);
return data;
}
);
export const loginWithToast = withToast(login, {
pending: "Logging in...",
error: {
render: (error: any) => {
return error?.password || error?.email
? "Invalid email or password"
: "Something went wrong";
},
},
success: "Logged in successfully",
});
// usage in component
const dispatch = useAppDispatch();
loginWithToast(dispatch, {
email: values.email.value,
password: values.password.value,
});
First createAsyncThunk:
import { coreAxios } from "utilities/axios"; // Own customized axios
import { createAsyncThunk } from "#reduxjs/toolkit";
const BASE_URL = process.env.REACT_APP_MAIN_URL
export const GetProducts = createAsyncThunk(
"inventory/GetProducts",
async () => {
const {data} = await coreAxios.get(`${BASE_URL}/api/product/list/`);
return data
}
);
Second createSlice:
import { createSlice } from "#reduxjs/toolkit";
import { GetProducts } from "services/inventory/product.service";
import { toast } from 'react-toastify';
export const productSlice = createSlice({
name: "products",
initialState: {
productsList: [],
productsLoading: false,
productsError: null,
},
extraReducers:
(builder) => {
builder.addCase(GetProducts.pending, (state) => {
toast.loading('Promise is pending...')
state.productsLoading = true
});
builder.addCase(GetProducts.fulfilled, (state, action) => {
toast.dismiss();
toast.success('Promise resolved 👌');
state.productsList = action.payload
state.productsLoading = false
state.productsError = null
});
builder.addCase(GetProducts.rejected, (state, action) => {
toast.dismiss();
toast.error('Promise rejected 🤯 😣')
state.productsLoading = false
state.productsError = action.error?.message
});
},
});
export default productSlice.reducer;
Third page:
import { ToastContainer } from 'react-toastify';
import { useSelector, useDispatch } from "react-redux";
import { GetProducts } from 'services/inventory/product.service';
const Product = () => {
const { productsList, productsLoading, productsError } = useSelector((state) => state.products);
const dispatch = useDispatch();
useEffect(() => {
dispatch(GetProducts());
}, []);
return (
<div className="grid crud-demo">
<h1>Hello Alim</h1>
<ToastContainer />
</div>
);
}

React-redux- Fetching data from API

I am working on a project and I need to fetch data from backend or from an API. I tried fetch the data but nothing appears. I think I am doing something wrong in the container. I am a beginner in react-redux, I don't know what I am doing wrong.
I've already read all the posts but nothing seems to works.
my reducer:
const initialState={
articles: [],
};
const rootReducer = (state = initialState, action) => {
const { type, payload }=action;
switch(type) {
case SRETRIEVE_ARTICLE:{
return {
...state,
articles:payload,
};
}
default: return state;
}
}
export default rootReducer;
This is what I have right now in container:
import Articles from 'components/Articles';
import { fetchArticles } from '../../pages/index';
const mapStateToProps = (state) => ({
articles:state.articles
})
const ConnectedArticles = connect(
mapStateToProps,
{fetchArticles}
)(Articles)
export default ConnectedArticles;
pages.js
axios.get('API').then((response) => {
const { data } = response;
dispatch({ type: RETRIEVE_ARTICLES, payload: data });
});
};
const Index = () => {
const articles= useSelector((state) => state.articles);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchArticles);
}, []);
return <>{articles && articles.map((article) => <Article key={article.id} name={article.name} />)}</>;
};
Index.getInitialProps = async () => ({
authRequired: true,
label: 'Dashboard',
});
export default Index;
Also I defined the action type: export const SET_UNOPENED_REWARD = 'SET_UNOPENED_REWARD';
and action const unopenedRewards = (payload) => ({ type: SET_UNOPENED_REWARD, payload });
One very nice way to do data fetching with redux is to use redux toolkit's createAsyncThunk and createSlice functions.
// src/features/articles/articlesSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchArticles = createAsyncThunk("articles/get", async () => {
// Here you can use axios with your own api
const response = await fetch("https://rickandmortyapi.com/api/character");
const json = await response.json();
return json.results;
});
export const slice = createSlice({
name: "articles",
initialState: {
loading: false,
data: []
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchArticles.pending, (state) => {
state.loading = true;
});
builder.addCase(fetchArticles.fulfilled, (state, action) => {
state.data = action.payload;
state.loading = false;
});
builder.addCase(fetchArticles.rejected, (state) => {
state.loading = false;
});
}
});
export default slice.reducer;
// src/features/articles/Articles.js
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchArticles } from "./articlesSlice";
export const Articles = () => {
const articles = useSelector((state) => state.articles.data);
const loading = useSelector((state) => state.articles.loading);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchArticles());
}, []);
return (
<>
{loading && "...loading"}
{articles.map((article) => <Article key={article.id} {...article} />)}
</>
);
};
you should use async and await
let response = await axios.get('https://run.mocky.io/v3/5c045896-3d18-4c71-a4e5-5ed32fbbe2de')
if(response.status==200){
dispatch({ type: RETRIEVE_ARTICLES, payload: data });
}

Cannot get object values in react js

I am passing value into the redux store through reducer. And I am displaying that value in the component.
But it says cannot read property name of undefined
Even whats weird is when I map the product, I can see product value in the console and when I don't map, I don't see the product value in the console. Please help me with this
Please find the code here
Component
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { listProductDetails } from "./actions/productActions";
const Playground = () => {
const dispatch = useDispatch();
const productDetails = useSelector((state) => state.productDetails);
const { product } = productDetails;
useEffect(() => {
dispatch(listProductDetails("pod2"));
}, [dispatch]);
return (
<div>
<h1>
<h1>{product[0].name}</h1>
</h1>
</div>
);
};
export default Playground;
Reducer
export const productDetailsReducers = (state = { product: [] }, action) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return { loading: true, ...state };
case PRODUCT_DETAILS_SUCCESS:
return {
loading: false,
product: action.payload,
};
case PRODUCT_DETAILS_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
Action
export const listProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`/api/products/${id}`);
console.log("this is the data");
console.log(data);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
Store
const reducer = combineReducers({
productDetails: productDetailsReducers,
});
It's always better to have a condition before accessing the data
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { listProductDetails } from "./actions/productActions";
const Playground = () => {
const dispatch = useDispatch();
const productDetails = useSelector((state) => state.productDetails);
const { product } = productDetails;
useEffect(() => {
dispatch(listProductDetails("pod2"));
}, [dispatch]);
return (
<div>
{
product &&
product.length ?
<h1>
<h1>{product[0].name || ""}</h1>
</h1>
: null
}
</div>
);
};
export default Playground;

Async call results to Warning: Maximum update depth exceeded in React & Redux

I have an app built with React, Redux that pulls data from a RESTful service sitting in my local. I tested the implementation with dummy data and works fine. However, when I hook up the async service the calls result in havoc with the below error:
Here is the code
reducer.js
import {
LOAD_ALL_PRODUCTS_SUCCESS,
LOAD_ALL_PRODUCTS_REQUEST,
LOAD_ALL_PRODUCTS_FAIL,
LOAD_PRODUCT_REQUEST,
LOAD_PRODUCT_SUCCESS,
LOAD_PRODUCT_FAIL,
} from './actions';
export const productData = (state = { loader: {}, products: [] }, action) => {
const { type, payload } = action;
switch (type) {
case LOAD_ALL_PRODUCTS_REQUEST: {
return { loader: true, products: [] };
}
case LOAD_ALL_PRODUCTS_SUCCESS: {
return { loader: false, products: payload };
}
case LOAD_ALL_PRODUCTS_FAIL: {
return { loader: false, error: payload };
}
default:
return state;
}
};
thunk.js
import axios from 'axios';
import { mockData } from '../MockData';
import {
loadAllProductFailed,
loadAllProductRequest,
loadAllProductSuccess,
LOAD_PRODUCT_FAIL,
LOAD_PRODUCT_REQUEST,
LOAD_PRODUCT_SUCCESS,
} from './actions';
export const loadInitialProducts = () => async (dispatch) => {
try {
dispatch(loadAllProductRequest());
//this is where the issues is
const response = await axios.get('http://localhost:8080/v1/product/all');
const payload = await response.data;
console.log(payload);
dispatch(loadAllProductSuccess(payload));
} catch (error) {
dispatch(
loadAllProductFailed(
error.response && error.response.data.message
? error.response.data.message
: error.message
)
);
}
};
export const loadProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: LOAD_PRODUCT_REQUEST });
//do a axios api call for product api
dispatch({
type: LOAD_PRODUCT_SUCCESS,
payload: mockData.find(({ productId }) => productId == id),
});
} catch (error) {
dispatch({
type: LOAD_PRODUCT_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const LOAD_ALL_PRODUCTS_REQUEST = 'LOAD_PRODUCTS_REQUEST';
export const loadAllProductRequest = () => ({
type: LOAD_ALL_PRODUCTS_REQUEST,
});
export const LOAD_ALL_PRODUCTS_SUCCESS = 'LOAD_ALL_PRODUCTS_SUCCESS';
export const loadAllProductSuccess = (payload) => ({
type: LOAD_ALL_PRODUCTS_SUCCESS,
payload: payload,
});
export const LOAD_ALL_PRODUCTS_FAIL = 'LOAD_ALL_PRODUCTS_FAIL';
export const loadAllProductFailed = (error) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: error,
});
export const LOAD_PRODUCT_REQUEST = 'LOAD_PRODUCT_REQUEST';
export const loadProductRequest = () => ({
type: LOAD_ALL_PRODUCTS_FAIL,
});
export const LOAD_PRODUCT_SUCCESS = 'LOAD_PRODUCT_SUCCESS';
export const loadProductSuccess = (payload) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: payload,
});
export const LOAD_PRODUCT_FAIL = 'LOAD_PRODUCT_FAIL';
export const loadProductFailed = (error) => ({
type: LOAD_ALL_PRODUCTS_FAIL,
payload: error,
});
Home.js
import React, { useState, useEffect } from 'react';
import { conwayAPI } from '../ConwayAPI';
import { Container, Col, Row } from 'react-bootstrap';
import Product from './Product';
import { useDispatch, useSelector } from 'react-redux';
import _ from 'lodash';
import { loadInitialProducts } from '../app/thunk';
const Home = () => {
//local state maintained only for this component
const [filterProducts, setFilterProducts] = useState([]);
const dispatch = useDispatch();
const productList = useSelector((state) => state.productData);
const { loader, error, products } = productList;
useEffect(() => {
dispatch(loadInitialProducts());
}, [dispatch, products]);
const doSearch = (text) => {
_.isEmpty(text)
? setFilterProducts(products)
: setFilterProducts(
filterProducts.filter((product) =>
product.productName.toLowerCase().includes(text.toLowerCase())
)
);
};
return (
<Container fluid>
<Row md={7} lg={5} className='justify-content-md-center'>
{filterProducts.length &&
filterProducts.map((datam, key) => {
return (
<Col key={key}>
<Product product={datam} key={key} />
</Col>
);
})}
</Row>
</Container>
);
};
export default Home;
When I click on a Nav panel the Home.js gets called and the error starts. What can I do differently to eliminate this error?

Resources