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