Data is loading in redux state but not getting rendered on screen - javascript

So I am rendering a simple post list in React.js using react-redux with axios
The data is coming from Laravel API
I have no idea what to do. I have posted my code below!
postActions.js file
export const authUserPosts = () => async (dispatch, getState) => {
try {
dispatch({
type: POST_LIST_REQUEST,
});
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
Authorization: `Bearer ${userInfo.meta.token}`,
},
};
const { data } = await axios.get('api/v1/posts', config);
dispatch({
type: POST_LIST_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: POST_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
postReducer.js
export const postAuthUserReducer = (state = { posts: [] }, action) => {
switch (action.type) {
case POST_LIST_REQUEST:
return {
loading: true,
};
case POST_LIST_SUCCESS:
return {
loading: false,
posts: action.payload,
};
case POST_LIST_FAIL:
return {
loading: false,
error: action.payload,
};
default:
return state;
}
};
Data is loaded in redux state and here's the screenshot of it
I am mapping this data inside my PostScreen.js like this.
{posts.map((element) => (
<div className="mb-4" key={element.id}>
<div>
<Link to="/" className="font-bold">
{element.body}
</Link>
<span className="text-gray-600 text-small ml-20">
{element.created_at}
</span>
<Link to="/" className="bg-red-500">
Delete
</Link>
</div>
<p className="mb-2"></p>
</div>
))}
I get the error TypeError: Cannot read property 'map' of undefined

you have data object inside posts. posts.data.map will work.

Related

Redux : useSelector returns undefined but returns data at 3 rd time

I am getting the data from the api as expected but the problem here is I am getting it in a 3rd attempt which is causing the error in my application when there's no data to show.
I am testing it printing on a console but it's the same error. As Soon As I Refresh My Page The Error Comes Flooding In The Console
Reducer
export const productDetailsReducer = (state = { products: {} }, action) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return {
...state,
loading: true,
};
case PRODUCT_DETAILS_SUCCESS:
return {
loading: false,
product: action.payload,
};
case PRODUCT_DETAILS_FAIL:
return {
...state,
error: action.payload,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
Component
const ProductDetails = () => {
const dispatch = useDispatch();
const alert = useAlert();
const { id } = useParams();
const { product, loading, error } = useSelector(
(state) => state.productDetails
);
useEffect(() => {
dispatch(getProductDetails(id));
if (error) {
alert.error(error);
dispatch(clearErrors());
}
}, [dispatch, id, alert, error]);
console.log(product);
Action
export const getProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`/api/v1/product/${id}`);
dispatch({
type: PRODUCT_DETAILS_SUCCESS,
payload: data.product,
});
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload: error.response.data.message,
});
}
};
App.js
function App() {
return (
<Router>
<div className="App">
<Header />
<div className="container container-fluid">
<Routes>
<Route path="/" element={<Home />} exact />
<Route path="/product/:id" element={<ProductDetails />} />
</Routes>
</div>
<Footer />
</div>
</Router>
);
}
You haven't set the loading in the initial state I think that's why
export const productDetailsReducer = (state = { products: {} }, action) => {
you are trying to access the loading state before you set the value of the state that's why you are getting the error
you should set the loading and error in the initial state with the default values
const initialState = {products: {}, loading: true, error: null}
and then pass it to the reducer.
export const productDetailsReducer = (state = initialState, action) => {
and change the product into products
hopefully, this will fix your issue.
I have checked your code and I think the problem is with your initial state in
export const productDetailsReducer = (state = { products: {} }, action) =>
try changing state={products:{}} with state={product: {}}

Unable to update a state while using fetch in reducer in React-Redux

I'm working on a react-redux project to learn authentication. I have a SignUp form where I post the details to google firebase from a reducer and update the responseStatus to true which is turn dispatches an action to update 'isLoggedIn' to true to enable few protected routes. The POST is successful and the response is proper, however, when I check the responseStatus from within the .then() while fetch(), I see the state updated, however outside the 'if' statement, I see that status is still the default one. I could feel that I'm getting this issue due to improper understanding of promises, but not exactly which part. Please let me know how do I resolve this?
Here is the code of SignUp form. I've removed few state updating lines of code, to show only the dispatching events and the states that I retrieve from reducers.
const SignUp = () => {
const dispatch = useDispatch();
const responseStatus = useSelector(
(state) => state.DatabaseCommunicationReducer.responseStatus
);
console.log(responseStatus);
const formSubmitHandler = (e) => {
if (!isValidEmail || !isValidPassword || !isConfirmPassword) {
dispatch({
type: "TOGGLE_MODAL",
payload: showModal,
});
} else {
dispatch({
type: "CREATE_ACCOUNT",
payload: {
enteredEmail,
enteredPassword,
},
});
if (responseStatus) {
dispatch({
type: "UPDATE_LOGGEDIN",
payload: true,
});
}
}
e.preventDefault();
};
The following are the codes of 2 reducers that updates the states. The first is the DatabaseCommunicationReducer which posts the user details to firebase.
const initialData = {
token: "",
responseStatus: false,
};
const DatabaseCommunicationReducer = (state = initialData, action) => {
const url =
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[API_KEY]";
if (action.type === "CREATE_ACCOUNT") {
fetch(url, {
method: "POST",
body: JSON.stringify({
email: action.payload.enteredEmail,
password: action.payload.enteredPassword,
returnSecureToken: true,
}),
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.ok) {
state.responseStatus = true;
return response.json();
}
})
.then((data) => {
state.token = data.idToken;
return {
token: state.token,
responseStatus: state.responseStatus,
};
});
}
return state;
};
export default DatabaseCommunicationReducer;
The responsesStatus returned inside the .then() is true while outside of fetch and inside of 'if' is the default one. Here is the issue according to my understanding.
Here is the code of reducer that updates the isLoggedIn
const initialState = {
isLoggedIn: false,
isLogin: false,
};
const LoginReducer = (state = initialState, action) => {
if (action.type === "LOGIN_STATUS") {
state.isLogin = !action.payload;
return {
isLoggedIn: state.isLoggedIn,
isLogin: state.isLogin,
};
}
if (action.type === "UPDATE_LOGGEDIN") {
state.isLoggedIn = action.payload;
return {
isLoggedIn: state.isLoggedIn,
isLogin: state.isLogin,
};
}
return state;
};
export default LoginReducer;
Here is the code in Header component where I set the nav bar according to whether the user is logged in or not based on isLoggedIn
const Header = () => {
const isLoggedIn = useSelector((state) => state.LoginReducer.isLoggedIn);
console.log(isLoggedIn);
return (
<div className={styles.header}>
<h1 className={styles.heading}>
The <span className={styles.span}>Poke</span>World
</h1>
<ul>
{isLoggedIn && (
<li>
<Link className={styles.link} to="/pokelist">
PokeList
</Link>
</li>
)}
{isLoggedIn && (
<li>
<Link className={styles.link} to="/">
Logout
</Link>
</li>
)}
{!isLoggedIn && (
<li>
<Link className={styles.link} to="/">
Login/SignUp
</Link>
</li>
)}
</ul>
</div>
);
};
export default Header;
I hope I've shared what all are required to help you resolve my issue. Thanks in advance.

User Auth with React Context API

I'm using React, Axios and Mongoose. Trying to store a user state but am having trouble with the stored state.user object.
When I manually enter values for state.user, the app works properly, however when I actually login from the site, the user object is stored in localStorage but is not being read properly by the app. I noticed I had to remove new ObjectId from the object and also convert the createdAt and lastUpdated dates into strings in order for my static values to work. How can I get around this? Thanks!
Screenshot of localStorage object
context.js
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
isFetching: false,
error: false,
};
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
JSON.stringify(localStorage.setItem("user", state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
isFetching: state.isFetching,
error: state.error,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
reducer.js
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
isFetching: true,
error: false,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
isFetching: false,
error: false,
};
case "LOGIN_FAILURE":
return {
user: null,
isFetching: false,
error: true,
};
case "FOLLOW":
return {
...state,
user: {
...state.user,
following: [...state.user.following, action.payload],
},
};
case "UNFOLLOW":
return {
...state,
user: {
...state.user,
following: state.user.following.filter(
(following) => following !== action.payload
),
},
};
default:
return state;
}
};
export default AuthReducer;
actions.js
export const LoginStart = (userCredentials) => ({
type: "LOGIN_START",
});
export const LoginSuccess = (user) => ({
type: "LOGIN_SUCCESS",
payload: user,
});
export const LoginFailure = (error) => ({
type: "LOGIN_FAILURE",
payload: error,
});
export const Follow = (userId) => ({
type: "FOLLOW",
payload: userId,
});
export const Unfollow = (userId) => ({
type: "UNFOLLOW",
payload: userId,
});
utils/api.js
import axios from "axios";
export const loginCall = async (userCredentials, dispatch) => {
dispatch({ type: "LOGIN_START" });
try {
const response = await axios.post("/api/auth/login", userCredentials);
dispatch({ type: "LOGIN_SUCCESS", payload: response.data });
} catch (error) {
dispatch({ type: "LOGIN_FAILURE", payload: error });
}
};

React-Redux UI bug. Lag in Image update

I'm building the MERN eCommerce app, and I'm facing a weird UI bug with pulling data from Redux Store.
After switching pages from one product page to another, there is the old product image shown and then updated.
As you can see: https://imgur.com/iU9TxJr
There you can see code for reducer:
export const productDetailsReducer = (
state = { product: { reviews: [] } },
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;
}
};
And also code for action:
export const listProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`/api/products/${id}`);
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,
});
}
};
And lastly, there is component's code where I bringing redux store into the page
const ProductPage = ({ match }) => {
const dispatch = useDispatch();
const productDetails = useSelector((state) => state.productDetails);
const { loading, error, product } = productDetails;
useEffect(() => {
dispatch(listProductDetails(match.params.id));
}, [dispatch, match]);
console.log(product);
return (
<>
{loading ? (
<Loader />
) : error ? (
<Message variant="danger">{error}</Message>
) : (
<Row>
<Col md={8}>
<Image src={product.image} alt={product.name} fluid />
</Col>
...rest of component's code....
I know probably would be the best to not even use redux for a single product, but I'm using this project for practice, and I'm kinda stuck at this one.
Thanks to everyone!
What you need to do is clear the selected product when you leave the detail page. You can do that using the return function of useEffect. So probably something like:
useEffect(() => {
dispatch(listProductDetails(match.params.id));
return () => {
dispatch(clearSelectedProduct());
}
}, [dispatch, match]);
And add corresponding action and reducer changes..
case CLEAR_SELECTED_PRODUCT:
return { loading: false, product: { reviews: [] } };
This way, when you arrive on the product detail page, the previous product is always cleared.

How to show loader image in react?

I want to show a loading image when the user hits that particular URL and hide when API is successfully fetched the data.
I am not getting in react to show image initially where I should add code and how to hide when API response in success.
This is the action creator for fetching data.
export const fetchEvents = (requestData) => ({
type: FETCH_EVENTS,
payload: axios.get(requestData.url, fetchEventsConfig)
})
And this is the reducer for fetching data.
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_EVENTS_FULFILLED:
const oldState = Object.assign({}, state)
const response = Object.assign({}, action.payload.data)
const allResults = oldState.results.concat(response.results)
return {
...state,
...response,
results: allResults
}
}
}
I am new to this react redux so any help would be great
You can add a boolean property in your Redux state to indicate loading state. Let's call it isLoading. Set isLoading true when you dispatch FETCH_EVENTS. Set it false, when you dispatch FETCH_EVENTS_FULFILLED.
Pass value of isLoading to your React component using #connect HOF. Render a loading indicator when value is true, and content when it's false.
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_EVENTS: {
return {...state, isLoading: true}
}
case FETCH_EVENTS_FULFILLED: {
const response = action.payload.data
const results = [...state.results, ...response.results]
return {
...state,
...response,
results,
isLoading: false
}
}
case FETCH_EVENTS_FAILURE: {
return {...state, isLoading: false}
}
}
}
P.S.
Containing code inside braces after each switch/case limits scope of your const inside that block.
Write a loader file (loader.js)
import React from 'react'
import {ClipLoader} from "react-spinners";
const Loader = ({ loading, message }) => {
return loading ? (
<div className='wrapper'>
<ClipLoader
size={40}
color={"#123abc"}
loading={loading}
/>
<span className='message'>
{message}
</span>
</div>
) : null
};
export default Loader;
In Actions:
export const GET_SERVICE = '[SERVICE REQUEST] GET LIST';
export const GET_SERVICE_SUCCESS = '[SERVICE REQUEST] GET LIST SUCCESS';
export const GET_SERVICE_FAILURE = '[SERVICE REQUEST] GET LIST FAILURE';
export function getService(options) {
const request = axios.post('api/services', {options});
return (dispatch) => {
dispatch({
type: GET_SERVICE
});
try {
request.then((response) =>
dispatch({
type: GET_SERVICE_SUCCESS,
payload: {data: response.data.data, meta: response.data.meta}
})
);
} catch (e) {
dispatch({
type: GET_SERVICE_FAILURE
});
}
}
}
In Reducer:
const initialState = {
services: [],
loading: false,
hasErrors: false,
};
const serviceReducer = function (state = initialState, action) {
switch (action.type) {
case Actions.GET_SERVICE:
return {...state, loading: true};
case Actions.GET_SERVICE_SUCCESS:
return {
...state,
loading: false,
services: [...action.payload.data],
page: action.payload.meta.page,
total: action.payload.meta.total
};
case Actions.GET_SERVICE_FAILURE:
return {...state, loading: false, hasErrors: true};
default:
return state;
}
};
export default serviceReducer;
Write following code in your component:
<div className="flex flex-1 w-full">
<Loader loading={loading} />
</div>

Categories

Resources