Redux useSelect doesn't get updated as expected - javascript

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);
}
});

Related

How to create global filter search with redux toolkit, react and javascripr?

I need to develop a global search filter to search the products based on the product title.
To solve the problem I'm using redux to manage the global formal state. I created a reducer to filter the titles, however, I don't know how to get the array of API objects and play in the initialState data: []; To call the API I'm using createAPI from the redux toolkit.
Some part of code:
import { createSlice } from "#reduxjs/toolkit";
const searchSlice = createSlice({
name: "search",
initialState: {
data: [], // array of objects from API
filteredTitle: [],
isLoading: true
},
reducers: {
getData: (state, action) => {
state.data = action.payload;
},
searchByName: (state, action) => {
const filteredResult = state.data.filter((product) =>
product.title.toLowerCase().includes(action.payload.toLowerCase())
);
return {
...state,
filteredTitle:
action.payload.length > 0 ? filteredResult : [...state.data]
};
}
}
});
export const { searchByName, getData } = searchSlice.actions;
export default searchSlice.reducer;
In App.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { searchByName, getData } from "./features/slice";
import { useGetAllProductsQuery } from "./services/api";
export default function App() {
const dispatch = useDispatch();
const filteredTitle = useSelector((state) => state.search.filteredTitle);
const [searchTerm, setSearch] = useState("");
const { data } = useGetAllProductsQuery();
console.log("data", data);
const changeSearchTerm = (e) => {
setSearch(e.target.value);
};
useEffect(() => {
dispatch(searchByName(searchTerm));
dispatch(getData(data));
}, [searchTerm, dispatch]);
return (
<div>
<input onChange={changeSearchTerm} type="text" value={searchTerm} />
<div>
{filteredTitle.map((user) => (
<div>{user.name}</div>
))}
</div>
</div>
);
}
CodeSandbox
Redux Toolkit Query is made, so you won't interact with Redux store with thunk functions directly (that's what they say in their documentation).
You can only use transformResponse once creating the query. I haven't come across any ways, to be able to add an extra reducer to the slices that are made with RTK Query.
The first way, is to store the data that you are getting from the query, inside another slice, and then use a reducer to filter your data.
The second way is to create a slice, and using createAsyncThunk, fetch your data, then treat your data as you want it. I can leave an example for you:
export const getName = createAsyncThunk('getNameFromBE',
async name => {
await fetch('https://example.com/studentID)
}
)
export const studnetSlice = createSlice({
name: 'studentSlice',
initialState,
reducers: {
aReducer : (state, action) => {state.name = action.payload}
},
extraReducers: {
[getName .pending]: state => {
state.loading = true
state.failed = false
},
[getName .fulfilled]: (state, action) => {
state.name= action.payload
state.loading = false
},
[getName .rejected]: state => {
state.loading = false
state.rejected = true
},
}
})
Here you have a slice, with thunk and normal reducers, with a state that is accessible, but the downside is that you have to manage fetch completely manually (can be an advantage too), and also the call state (pending, fulfilled, rejected) must be done manually, which takes a lot of boilerplate code writing.

useSelector property returns undefined

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

Access the state of my redux app using redux hooks

I am migrating my component from a class component to a functional component using hooks. I need to access the states with useSelector by triggering an action when the state mounts. Below is what I have thus far. What am I doing wrong? Also when I log users to the console I get the whole initial state ie { isUpdated: false, users: {}}; instead of just users
reducers.js
const initialState = {
isUpdated: false,
users: {},
};
const generateUsersObject = array => array.reduce((obj, item) => {
const { id } = item;
obj[id] = item;
return obj;
}, {});
export default (state = { ...initialState }, action) => {
switch (action.type) {
case UPDATE_USERS_LIST: {
return {
...state,
users: generateUsersObject(dataSource),
};
}
//...
default:
return state;
}
};
action.js
export const updateUsersList = () => ({
type: UPDATE_USERS_LIST,
});
the component hooks I am using
const users = useSelector(state => state.users);
const isUpdated = useSelector(state => state.isUpdated);
const dispatch = useDispatch();
useEffect(() => {
const { updateUsersList } = actions;
dispatch(updateUsersList());
}, []);
first, it will be easier to help if the index/store etc will be copied as well. (did u used thunk?)
second, your action miss "dispatch" magic word -
export const updateUsersList = () =>
return (dispatch, getState) => dispatch({
type: UPDATE_USERS_LIST
});
it is highly suggested to wrap this code with { try } syntax and be able to catch an error if happened
third, and it might help with the console.log(users) error -
there is no need in { ... } at the reducer,
state = intialState
should be enough. this line it is just for the first run of the store.
and I don't understand where { dataSource } comes from.

Action.type undefined error in Redux Reducer

I'm not sure why I'm forced to do a check if actions exists in my reducer. Could it be because we are using async await in our actions / API methods?
Reducer
export const partyReducer = (state = initState, action) => {
if (action) { // <-- should not need this
switch (action.type) {
case Actions.SET_ROLES: {
const roles = formatRoles(action.roles);
return {
...state,
roles
};
}
default:
return state;
}
}
return state;
};
export default partyReducer;
Actions
import {getRoles} from '../shared/services/api';
export const Actions = {
SET_ROLES: 'SET_ROLES'
};
export const fetchRoles = () => async dispatch => {
try {
const response = await getRoles();
const roles = response.data;
dispatch({
type: Actions.SET_ROLES,
roles
});
} catch (error) {
dispatch({
type: Actions.SET_ROLES,
roles: []
});
}
};
Component that dispatches the action:
componentDidMount() {
this.props.fetchRoles();
this.onSubmit = this.onSubmit.bind(this);
}
...
export const mapDispatchToProps = dispatch => {
return {
fetchRoles: () => {
dispatch(fetchRoles());
}
};
};
The Store
import {createStore, combineReducers, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import {reducer as formReducer} from 'redux-form';
// Reducers
import partyReducer from '../reducers/party-reducer';
export default function configureStore(initialState) {
let reducer = combineReducers({
form: formReducer,
party: partyReducer
});
let enhancements = [applyMiddleware(thunk)];
if (process.env.PROD_ENV !== 'production' && typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
enhancements.push(window.__REDUX_DEVTOOLS_EXTENSION__());
}
return createStore(reducer, initialState, compose(...enhancements));
}
What I've tried
I noticed my mapDispatchToProps was written kinda strange so I fixed that, but I still get the error actions is undefined if I remove the if statement :'(
import {fetchRoles as fetchRolesAction} from '../../../actions/party-actions';
...
export const mapDispatchToProps = dispatch => ({
fetchRoles: () => dispatch(fetchRolesAction())
});
Figured it out! Was my test!
it('returns expected initState', () => {
let expected = {roles: []};
let actual = partyReducer();
expect(actual).toEqual(expected);
});
^ test above is suppose to see if the initial state is return if no state is passed in. However Actions should Always be passed in.
Fix:
it('returns expected initState', () => {
let expected = {roles: []};
let actual = partyReducer(undefined, {}); // <-- undefined state, + action
expect(actual).toEqual(expected);
});

React - Redux - this.props return always undefined

I have the following code
store/index.js
const DEFAULT_STATE = {
auth: { isAuthenticated: false },
error: { message: null },
tracks: [],
uploadedTrack: {}
};
store/reducers/index.js
import auth from './auth';
import error from './error';
import {tracks, uploadedTrack} from './tracks';
export default combineReducers({
auth,
tracks,
uploadedTrack,
error
});
store/reducers/tracks.js
import {UPLOADED_TRACK, SET_CURRENT_USER_TRACK} from '../actionTypes';
export const tracks = (state = [], action) => {
switch(action.type) {
case SET_CURRENT_USER_TRACK:
return action.tracks;
default:
return state;
}
}
export const uploadedTrack = (state = {}, action) => {
switch(action.type) {
case UPLOADED_TRACK:
return action.track;
default:
return state;
}
};
store/actions/tracks.js
export const setTrack = tracks => ({
type: SET_CURRENT_USER_TRACK,
tracks
});
export const setUploadedTrack = track => ({
type: UPLOADED_TRACK,
track
});
export const getUserTrack = () => {
return async dispatch => {
try {
const {token, ...tracks} = await api.call('get', 'tracks/user');
dispatch(setTrack(tracks));
dispatch(removeError());
} catch (err) {
const {error} = err.response.data;
dispatch(addError(error.message));
}
};
};
components/trackList.jsx
componentDidMount() {
const {getUserTrack} = this.props;
getUserTrack();
}
render() {
var {authType} = this.props;
const {auth} = this.props;
const {tracks} = this.props;
console.log("Track: ", tracks)
All seems works because my "tracks" on Redux store contains my list of six tracks, but when i try to print this information from the "tracks" variable on the console this print "undefined".
The strange things is that my "call" on the console contains my six tracks...
Can you help me?
I don't know where is my errors, i try to apply the solutions find on the web but nothing working.
Can you show how you map your redux state to your component ? If your redux store store contains your six tracks but you can't display them in your react component, the problem is probably how you bind your store to your component (When you call your connect() in your components/trackList.jsx).

Categories

Resources