useSelector property returns undefined - javascript

I'm trying to fetch 'all posts' using Redux. I should be getting an empty array but instead, I'm getting undefined. Here's my reducer:
export default (posts = [], action) => {
switch ((action.type)) {
case "FETCH_ALL":
return action.payload;
case "CREATE":
return posts;
default:
return posts;
}
};
Action
export const getPosts = () => async (dispatch) => {
try {
const { data } = await api.fetchPosts();
dispatch({ type: "FETCH_ALL", payload: data });
} catch (error) {
console.log(error.message)
}
};
Posts.js component
import { useSelector } from "react-redux";
import Post from "./Post/Post";
import useStyles from "./styles";
const Posts = () => {
const posts = useSelector((state)=>state.posts)
console.log(posts)
const classes = useStyles();
return (
<>
<h1>Posts</h1>
<Post />
</>
);
};
export default Posts;

According to your reducer, your entire state is a posts array instead of a state object like { posts: [ ] }. So in your selector, you can simply return the state as it is in the Posts component.
const posts = useSelector((state)=>state);

I believe that you need to change a line in your reducer file. You need to assign the action.payload to the posts and then you can access it
export default (posts = [], action) => {
switch ((action.type)) {
case "FETCH_ALL":
return {posts: action.payload};
case "CREATE":
return posts;
default:
return posts;
}
};

Firstly, switch ((action.type)) should be switch (action.type)
Then, you should check the data from api whether it's returned
correctly or not
Finally, check your redux state object and your selector of posts

Related

React js useReducer hook in forms controlled inputs

I'm having issues with using useReducer with input. I'm trying to use controlled input here but I keep getting errors;
Controlled input is uncontrolled
import React, {useReducer, useEffect} from "react";
import axios from "axios";
const initialState = {
post: {},
user: ""
}
const reducer = (state, action) => {
switch(action.type){
case "Fetch_data":
return {
post: action.payload
}
case "On_change":
return {
user: action.payload
}
case "Fetch_error":
return {
post: {}
}
default:
return state
}
}
const ReducerFetchdata = () => {
const [info, dispatch] = useReducer(reducer, initialState)
useEffect(()=>{
axios
.get(`https://jsonplaceholder.typicode.com/posts/${info.user}`)
.then (res => {
console.log(res)
dispatch({type: "Fetch_data", payload: res.data})
})
.catch(err => {
console.log(err)
dispatch({type: "Fetch_error"})
})
}, [info.user])
const handleChange = (event) =>{
dispatch({type: "On_change", payload: event.target.value})
}
return(
<div>
<input type="text" onChange={handleChange} value={info.user}/>
<p>{info.post.title}</p>
</div>
)
}
export default ReducerFetchdata
You need to spread whole state and then update the necessary key when returning from the reducer. In this case onChange called On_change whose action did not return post from reducer state and thus causing error.
Refer : Sandbox for the fix
Just a guess: you are removing your user key after you fetch your post, so info.user becomes undefined.
Your reducer should be:
switch(action.type) {
case 'Fetch_data':
return {
...state,
post: action.payload
}
...
}
Always return
{
...state
[someKey]: someValue
}
in your On_Change and Fetch_error as well.

Redux useSelect doesn't get updated as expected

I’m working on a little proof of concept project, using React and Redux, and useSelector and useDispatch hooks. I’m trying to fetch some data asynchronously and I use thunks for that. I think I'm conceptually missing something. Even though my state works as expected, I can not get my data from api using useSelector.
Here is the code. My action:
import axios from "axios";
export const API_FETCH_POSTS = 'API_FETCH_POSTS';
export const fetchPosts = (postId) => { // to simulate post request
return (dispatch) => {
let baseUrl = 'https://jsonplaceholder.typicode.com/';
let postFix = 'comments?postId=';
let url = `${baseUrl}${postFix}${postId}`;
axios.get(url)
.then(response => {
const data = response.data;
console.log(JSON.stringify(data)); // work!
dispatch(fetchPostsSuccess(data));
});
}
};
const fetchPostsSuccess = posts => {
return {
type: API_FETCH_POSTS,
payload: posts
}
};
My reducer:
import {API_FETCH_POSTS} from "./apiActions";
const initialState = {
getPostsReq : {
posts: [],
}
};
const apiReducer = (state = initialState, action) => {
let getPostsReq;
switch (action.type) {
case API_FETCH_POSTS:
getPostsReq = {
posts: [...state.getPostsReq.posts]
};
return {
...state,
getPostsReq
};
default: return state;
}
};
export default apiReducer;
And rootReducer:
import {combineReducers} from 'redux';
import apiReducer from "./api/apiReducer";
export default combineReducers({
api: apiReducer
})
And store:
const initialState = {};
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(
applyMiddleware(thunk)
)
);
export default store;
I have a problem with my React component:
function PostContainer(props) {
const posts = useSelector(state => state.api.getPostsReq.posts);
const dispatch = useDispatch();
const logPosts = () => {
{/*why doesn't this line work???*/}
console.log(JSON.stringify(posts));
}
return (
<div>
<button onClick={() => {
dispatch(fetchPosts(1));
logPosts();
}}>Fetch Posts</button>
<div>
{/*why doesn't this line work???*/}
{posts.map(post => <p>{post.body}</p>)}
</div>
</div>
);
}
export default PostContainer;
I expect that after I press the button, the function fetchPosts gets dispatched and because I use thunk I shouldn’t have any problems with asynchronicity. But by some reason I can’t get my state, using useSelector() hook. I can neither render the state, nor log it in the console.
What am I missing here?
Here is the whole code if it is more convenient - https://github.com/JavavaJ/use-select-problem
Problem: Not Storing Posts
Your selector is fine, it's your reducer that's the problem! You dispatch an action which has an array of posts in the payload:
const fetchPostsSuccess = posts => {
return {
type: API_FETCH_POSTS,
payload: posts
}
};
But when you respond to this action in the reducer, you completely ignore the payload and instead just return the same posts that you already had:
const apiReducer = (state = initialState, action) => {
let getPostsReq;
switch (action.type) {
case API_FETCH_POSTS:
getPostsReq = {
posts: [...state.getPostsReq.posts]
};
return {
...state,
getPostsReq
};
default: return state;
}
};
Solution: Add Posts from Action
You can rewrite your reducer like this to append the posts using Redux immutable update patterns.
const apiReducer = (state = initialState, action) => {
switch (action.type) {
case API_FETCH_POSTS:
return {
...state,
getPostsReq: {
...state.getPostsReq,
posts: [...state.getPostsReq.posts, ...action.payload]
}
};
default:
return state;
}
};
It's a lot easier if you use Redux Toolkit! With the toolkit you can "mutate" the draft state in your reducers, so we don't need to copy everything.
const apiReducer = createReducer(initialState, {
[API_FETCH_POSTS]: (state, action) => {
// use ... to push individual items separately
state.getPostsReq.posts.push(...action.payload);
}
});

State returning undefined from Fetch action to Rails backend

I have a Rails backend set up (for an array of ingredients), and it was working previously, however now the fetch action in my Dispatch Action Creator function is returning an undefined state (not retrieving ingredients).
The API endpoints are working perfectly fine (checked through server), however the fetch action is not retrieving the ingredients, and is returning a response.json that is undefined.
I put breakpoints everywhere, to check the state. I have tried to change the contents of the mapStateToProps for the component as well, but the state was undefined before going into the mapState function.
IngredientList component
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { getIngredients, hideIngredients } from "../actions";
class IngredientList extends React.Component {
render() {
const { ingredients } = this.props;
const ingredientsList = ingredients.map(ingredient => {
return (
<li key={ingredient.id}>
{ingredient.id}. {ingredient.name}
</li>
);
});
return (
<React.Fragment>
<div>
<button
className="button"
onClick={() => this.props.getIngredients()}
>
Get Ingredients
</button>
<button
className="button"
onClick={() => this.props.hideIngredients()}
>
Hide Ingredients
</button>
</div>
<ul>{ingredientsList}</ul>
</React.Fragment>
);
}
}
const structuredSelector = createStructuredSelector({
ingredients: state => state.ingredients
});
const mapDispatchToProps = { getIngredients, hideIngredients };
export default connect(
structuredSelector,
mapDispatchToProps
)(IngredientList);
Actions
export function getIngredientsRequest() {
return {
type: GET_INGREDIENTS_REQUEST
};
}
export function getIngredientsSuccess(json) {
return {
type: GET_INGREDIENTS_SUCCESS,
json
};
}
export function hideIngredients() {
return dispatch => {
dispatch({ type: HIDE_INGREDIENTS });
};
}
export function getIngredients() {
return dispatch => {
dispatch(getIngredientsRequest());
return fetch(`v1/ingredients`)
.then(response => response.json())
.then(json => dispatch(getIngredientsSuccess(json)))
.catch(error => console.log(error));
};
}
Reducers
const initialState = {
ingredients: []
};
function rootReducer(state = initialState, action) {
console.log(action.type);
switch (action.type) {
case "GET_INGREDIENTS_SUCCESS":
console.log(action.json);
return { ...state, ingredients: action.json.ingredients }
case "GET_INGREDIENTS_REQUEST":
console.log('Ingredients request received')
return
case "HIDE_INGREDIENTS":
console.log('Ingredients are being hidden')
return { ...state, ingredients: [] }
case "GET_INGREDIENT_REQUEST":
console.log('One Ingredient request received:', "id:", action.id)
return
case "GET_INGREDIENT_SUCCESS":
console.log('GET_INGREDIENT_SUCCESS')
const ingredients = action.json.ingredients;
const id = action.id;
return {
...state,
ingredients: ingredients.filter(ingredient => ingredient.id.toString() === id)
}
default:
return state
}
}
export default rootReducer;
GET_INGREDIENTS_REQUEST reducers.js:6
Ingredients request received reducers.js:12
This is in the structuredSelector for IngredientList.js:42
Uncaught TypeError: Cannot read property 'ingredients' of undefined
I assume the issue is coming from the reducer below where you are trying to access the action.json.ingredients property here:
switch (action.type) {
case "GET_INGREDIENTS_SUCCESS":
console.log(action.json);
return { ...state, ingredients: action.json.ingredients } // here
In the following code snippet please check how the the response value - variable json - is passed to getIngredientsSuccess function:
export function getIngredients() {
return dispatch => {
dispatch(getIngredientsRequest());
return fetch(`v1/ingredients`)
.then(response => response.json())
.then(json => dispatch(getIngredientsSuccess(json))) // json value
.catch(error => console.log(error));
};
}
My assumption is the value is returned like the following from the API endpoint:
[
{id: 12, name: 'test12'},
{id: 13, name: 'test13'},
]
And not like this:
{
ingredients: [
{id: 12, name: 'test12'},
{id: 13, name: 'test13'},
]
}
So to resolve the issue, you might want to change the following line in the reducer:
switch (action.type) {
case "GET_INGREDIENTS_SUCCESS":
console.log(action.json);
// removed .ingredients
// original value: action.json.ingredients
return { ...state, ingredients: action.json }
I hope this resolves your problem, if not just let me know so we can investigate further.
Additionally it is worth to handle null and undefined values for your variable before calling map function on ingredients variable in rendering as below:
const { ingredients } = this.props;
const ingredientsList = ingredients != null ? ingredients.map(ingredient => {
return (
<li key={ingredient.id}>
{ingredient.id}. {ingredient.name}
</li>);
}) : null;
UPDATE:
Initially your code calls getIngredientsRequest function which goes to the following lines of code where the code does not return the state object:
case "GET_INGREDIENTS_REQUEST":
console.log('Ingredients request received')
return // missing state
So I guess the following correction will do the job here and most likely you won't get the error message further:
case "GET_INGREDIENTS_REQUEST":
console.log('Ingredients request received')
return state;
Let me highlight one important thing about reducer's return statement:
In Redux a reducer takes the starting state and an item to process, and return the new state.
Other important rule is the following:
Always return the state, even if you didn't change it, and even if it's just null. You may not return undefined.
So the reducer should have the new state in the return statement which in this case it was undefined and that caused the error message.
Please read further about reducers here: Actions and reducers: updating state.

loading status change after fetch data completely

I have action creator to get data from API and have another action creator for loading status and want change loading status when data completely fetched.
Now, I wrote following codes but not working good, Loading status changes to false before data fetched completely.
My ActionCreator:
export const loadingStatus = (bool) => {
return {
type: Constants.LOADING_STATUS,
isLoading: bool
};
}
const allFlashCards = (action) => {
return{
type: Constants.ALL_CARD,
...action
}
};
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and my Reducer:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
export const Loading = (status= false, action) => {
switch (action.type) {
case Constants.LOADING_STATUS:
return action.isLoading;
break;
default:
return status;
}
}
and in my component:
componentDidMount() {
this.props.fetchCards();
}
render() {
return(
<div>
{this.props.loading ?
<Loading/> :
Object.keys(this.props.cards.data).map(this.renderCard)
}
</div>
);
}
const mapStateToProps = (state) => ({
cards: state.main,
loading: state.loading
});
const mapDispatchToProps = (dispatch) => ({
fetchCards: bindActionCreators(fetchAllFlashCards, dispatch)
});
and combineReducer is:
import { combineReducers } from 'redux';
import FlashCard , { Loading } from './FlashCard.js';
import { routerReducer } from "react-router-redux";
export default combineReducers({
main: FlashCard,
loading: Loading,
routing: routerReducer
});
In my page, I have an error in console and it's:
Uncaught TypeError: Cannot read property 'data' of undefined and if put my codes in timeout fixed my bug :/
What should i do?
Your default state is wrong here:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
It should be an empty object {} instead of an empty array [], since you're returning objects.
This code
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
Looks completely fine. loadingStatus(false) should not be called before setting the flash cards. Your reducers and action creators are synchronous (as they should). So, nothing of note there.
I saw that you're using action.data on the Constants.ADD_CARD action case, but in your code you do not dispatch any actions with that type. Do you do it somewhere else? Maybe that's where the error is?
EDIT:
Another place that you're using .data is in your renderer: this.props.cards.data. What's the value of the state.main?
How are you creating your rootReducer? It should be something like this:
const rootReducer = combineReducers({
main: FlashCard,
loading: Loading,
});
Are you using main there? Or maybe cards?
Finally, I fixed my problem:
In my actionCreator change fetchAllFlashCards method to following:
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then(({data})=>{
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and in reducer change FlashCard reducer to following:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action.data};
break;
default:
return state;
}
};

ReactJS x Redux: Reducer not returning state values

Hi I'm new at React and Redux.
I'm met with a problem with the reducer while trying to fetch a user object from the database. But it seems like it is not returning the state to the correct place?
On my front end editProfile.js:
import { a_fetchUser } from '../../../actions/resident/actions_user';
class EditProfile extends Component {
componentDidMount() {
this.props.fetchProfile({ iduser: this.props.auth.user.iduser });
console.log(this.props.store.get('isProcessing')); // returns false
console.log(this.props.store.get('retrievedUser')); // returns empty object {} when it's supposed to return data
}
// code simplified...
const mapStateToProps = state => ({
store: state.r_fetch_user,
auth: state.authReducer
});
const mapDispatchToProps = (dispatch, store) => ({
fetchProfile: (user) => {
dispatch(a_fetchUser(user));
}
});
export const EditProfileContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(EditProfile);
}
Action actions_user.js:
import axios from 'axios';
const startFetchUser = () => ({
type: 'START_FETCH_USER',
});
const endFetchUser = response => ({
type: 'END_FETCH_USER',
response,
});
export const a_fetchUser = (user) => (dispatch) => {
dispatch(startFetchUser());
return axios.post('/rdb/getUser/', user)
.then((res) => {
console.log(res);
dispatch(endFetchUser(res));
})
.catch((err) => {
console.log(err);
dispatch(endFetchUser({ status: 'error' }));
});
};
Reducer userReducer.js:
import Immutable from 'immutable';
export const fetchUserState = Immutable.Map({
isProcessing: false,
feedbackType: null,
feedbackMsg: null,
retrievedUser: {},
});
export const r_fetch_user = (state = fetchUserState, action) => {
switch (action.type) {
case 'START_FETCH_USER':
console.log('start'); // printed
return state.set('isProcessing', true);
case 'END_FETCH_USER':
if (action.response.data.status === 'success') {
console.log(action.response.data.data[0]); // data retrieved from database successfully
return state.set('isProcessing', false).set('retrievedUser', action.response.data.data[0]);
} else {
return state.set('isProcessing', false).set('retrievedUser', {});
}
default:
return state;
}
};
My aim is to retrieve the object retrievedUser from the store. I've tried to console.log(this.props.store) on the front end and it did return a Map of the initial state, fetchUserState.
I've also tried to state.set (without returning) and it was successful so I came to a conclusion that there was something wrong with the return statement?
Additional details:
Using MERN stack.
this looks wrong:
const mapDispatchToProps = (dispatch, store) => ({
fetchProfile: (user) => {
dispatch(a_fetchUser(user));
}
});
What you need to do is to use bindActionCreators with, you can see example here and here:
function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch)
}
or you can also change the syntax to:
const mapDispatchToProps = (dispatch) => ({
fetchProfile: a_fetchUser(user);
});
I am not sure what exactly your state.set() method does (in reducer) but if its mutating the state, then your reducer will not remain PURE function since its changing the original state obj. So please update below reducer method to start returning new state obj which should not mutate existing state obj:
export const r_fetch_user = (state = fetchUserState, action) => {
switch (action.type) {
case 'START_FETCH_USER':
console.log('start'); // printed
return state.set('isProcessing', true);
case 'END_FETCH_USER':
if (action.response.data.status === 'success') {
console.log(action.response.data.data[0]); // data retrieved from database successfully
return state.set('isProcessing', false).set('retrievedUser', action.response.data.data[0]);
} else {
return state.set('isProcessing', false).set('retrievedUser', {});
}
default:
return state;
}
};

Categories

Resources