I have an action dispatcher that picks up comments from a blog post, straight from an API server first then dispatch an action.
export function fetchPostComments(postID) {
return (dispatch) => {
fetch(`${url}/posts/${postID}/comments`, { method: 'GET', headers})
.then((response) => response.json())
.then((comments) => {console.log("action ", comments); dispatch(getPostComments(comments))})
.catch((error)=>{console.log('fetch comments error',error)});
};}
I console.logit as you see above, and it gives the result I am looking for, as shown below.
And here is how I dispatch an action.
export function getPostComments (comments) {
return {
type: GET_POST_COMMENTS,
comments
}
}
And now here is my reducer:
function comments (state = {}, action) {
switch (action.type) {
case GET_POST_COMMENTS:
console.log("Reducer comments ", action.comments);
return action.comments;
default :
return state
}
}
If you observe again, I use a console.log to confirm the result and again I have the correct result that I desired as shown below:
Now, in one of my component, I display the blog post information and a link to view comments. Once the user click the view comments, it triggers the calling of the action dispatcher.
<div className="row">
<a onClick={this.handleFetchComments} href=""><span className="float-date"><strong style={{fontWeight:'bold'}}>View Comments</strong></span></a>
</div>
<div>
<div>
<span>Count : { this.state.comments.length }</span>
</div>
<textarea placeholder="Add your comments here"></textarea>
<input type="submit" value="+Add comment" />
</div>
This is the handler to fetch comments:
handleFetchComments = (e) => {
e.preventDefault();
const comments = this.props.getAllComments(this.props.postID);
console.log("Comments fetch ", comments);
setInterval(this.loadComments(comments),1000);
}
loadComments(comm) {
console.log("Before ", this.state.comments);
this.setState(()=>({comments:comm}));
console.log("After ", this.state.comments);
}
I put some console.log just to check the result and this is the result which I get, which is wrong and not compatible with the result produced by the reducer a moment ago. It gives me an undefined and empty result from a dispatch action, as shown in the result below:
Meanwhile, my Redux Dev tools every time I clicked the View Comments link, it shows up the correct data that I desired and triggers the dispatch action as shown below.
This is my state by the way on my Component, that has the link to View Comments:
state = {
post: [],
hasClickComments : false,
comments: []
}
This is my mapStateToProps in my App.js:
function mapStateToProps ({posts, comments, categories}) {
return {
posts: posts,
comments: comments,
categories: categories,
}
}
And this is my mapDispatchToProps:
function mapDispatchToProps (dispatch) {
return {
getAllComments: (postID) => dispatch(fetchPostComments(postID)),
}
}
I have other actions that I dispatch also mapDispatchtoProps and it's all working so far, except for this one.
I am new to React and Redux, and I am looking for the solution for this one almost two days but could not figure it out. What could have gone wrong?
You mix React Component's State and props.State And Props
after mapStateToProps all the data become the component's props.
and you should use this.props.comments to get your data!
function comments (state = {}, action) {
switch (action.type) {
case GET_POST_COMMENTS:
console.log("Reducer comments ", action.comments);
return {
...state,
comments: action.comments;
}
default :
return state
}
}
If your comment data is in redux then might be the problem is the way you are using mapStateToProps.
I suggest you to use debugger in function and find what you are getting in state.
function mapStateToProps (state) {
debugger
//return {
// posts: posts,
// comments: comments,
// categories: categories,
//}
}
now look at the data in state on the console.
then assign the data to required variables.
Related
I'm new in Redux and have a problem with rerendering after the store changed. I have found many similar problems here on SO but still can't solve my issue.
I have a monthly task(event) calendar with multiple tasks. The Calendar is the main component and some level deeper there are multiple TaskItem components. At the first render, the calendar and the tasks are rendered fine (In this case without employee names). In the Calendar component I trigger loading employees with a useEffect hook. I can see the network request on my console. Besides this, the console logs in the action, and in the reducer also show the employee list. And the Redux devtool also shows the loaded employees. Still the mapStateToProps on TaskItem shows a completly empty state.
What I'm doing wrong?
Here is my related code:
Calendar:
const Calendar = ({startDay, tasks, loadEmployeesAction}) => {
useEffect(()=>{
loadEmployeesAction();
},[]);
...
}
export default connect(null, {loadEmployeesAction})(Calendar);
TaskItem:
const TaskItem = ({task, onTextEdit, onTaskView, saveTask, employees }) => {
...
}
const mapStateToProps = (state) => {
console.log('Actual state is: ', state);
return {
employees: state.employees
}
}
export default connect(mapStateToProps)(TaskItem);
Reducer:
export const employeeReducer = (state = [], action) => {
switch (action.type) {
case actionType.EMPLOYEES_LOADED:
console.log('Reducer - Employees loaded:', action );
return action.payload.employees;
default :
return state;
}
}
Actions:
const employeesLoaded = (employees) => {
return {type: actionType.EMPLOYEES_LOADED, payload: {
employees
}
}
}
export const loadEmployeesAction = () => {
return (dispatch) => {
return employeeApi.getAllEmployees().then(emps => {
console.log('Action - Employees loaded: ', emps);
dispatch(employeesLoaded(emps));
})
}
}
Root reducer:
export const rootReduxReducer = combineReducers({
employees: employeeReducer
});
I found the error. It was a very clumsy mistake.
All of my posted code was fine, but I put the store creation in a component that was rerendered again and again so my store was recreated again and again.
The reducer code seems to be not as the redux pattern. So usually the state object is not directly replaced with a different object. Instead only the part of the state that needs to be changed is only with some non-mutating operation like spread operator.
So I think the reducer code should be changed like
export const employeeReducer = (state = [], action) => {
switch (action.type) {
case actionType.EMPLOYEES_LOADED:
return {...state,employees:action.payload.employees}
default :
return state;
}
}
if the response from the API is in the form
[{"employee_name":"name","employee_age":24},.....]
I have simple flow in my app. After request is finished i'm calling an action with a payload:
export const setVoteFlagCurrentPoll = () => dispatch => {
dispatch({
type: SET_VOTE_FLAG_CURRENT_POLL,
payload: true
})
}
Then in reducer im changing one variable in a poll object which looks like this:
{
idsurvey: 10,
topic: "random poll question",
answers: Array(4),
voted: false
}
Reducer itself:
case SET_VOTE_FLAG_CURRENT_POLL:
return {...state, pollData: {...state.pollData, voted: action.payload}};
My issue is that variable 'voted' is not chaning its value. Its still the same which is 'false'. Interesting thing is I can just log that recuder as: console.log({...state, pollData: {...state.pollData, voted: action.payload}}); and it works.. its logging with voted as true. Why is this happening?
Ok, I figured it out. It seems like mapStateToProps function was badly written..
before (not working):
const = ({user}) => ({user}); // its returning whole user reducer
after (working now):
const mapStateToProps = state => {
return {
user: state.user //same as above (I didnt want to change the code in other components..)
pollData: state.user.pollData //pulling pollData directly
}
}
Hope it helps.
In my react application, I have three parallel components. In my first component, I am doing an API call and based on the response I am routing the flow to Validated or Non-Validated Component.
Once the user is routed to validated component, there is a button on click of which user should be redirected to another component which should display the data in API response (first component) as key value pair. I am using Redux for state management.
The issue I am facing is the data is dispatched as an empty object from the store. I am not sure where I am going wrong but when I am debugging the app, I see the the action is not getting dispatched to the store and it's always returning me the initial state.
action.js-
export const setPoiData = (poiData) => dispatch => {
console.log('inside actions');
dispatch({
type: SET_POI_DATA,
payload: poiData
})
}
Reducer.js-
const initialState = {
poiData: {},
}
const reducerFunc = (state = initialState, action) => {
switch (action.type) {
case SET_POI_DATA:
console.log('inside poi reducers');
return {...state,poiData: action.payload}
default: return {...state}
}
}
Component 1-
//API call
Detail Component-
To get the data from store I am doing something like below-
componentDidMount() {
console.log(this.props.poiData)
}
function mapStateToProps(state) {
return {
poiData: state.poiData,
}
}
const mapDispatchToProps = dispatch => ({
setPoiData(data) {
dispatch(setPoiData(data));
}
})
I am not sure where I am going wrong. Can someone suggest me how to proceed ahead on this?
inside componentDidMount() you must call action like this this.props.setPoiData(<your data here>);
I have an icon that when clicked it triggers a function that calls an API and then dispatch an action to remove a blog post from the state. But the problem is that my UI does not re-render. However, if I refresh my browser the post that I deleted is no longer there and my store matches my state.
Here is my function that calls an API and then dispatch an action:
export function deletePost(postID) {
return (dispatch) => {
fetch(`${url}/posts/${postID}`, { method: 'DELETE', headers})
.then((response) => response.json())
.then((postID) => dispatch(removePost(postID)))
.catch((error)=>{console.log('dispatch error',error)});
};
Here is my action:
export function removePost ( postID ) {
return {
type: REMOVE_POST,
postID,
}
}
And here is my reducer:
function posts (state = {}, action) {
switch (action.type) {
case REMOVE_POST:
return [
...state.filter((post)=>post.id!==action.postID)
];
default:
return state
}
}
Now when I simply dispatch an action without calling an API
export function deletePost(postID) {
return (dispatch) => {
dispatch(removePost(postID));
}
My state is correctly updated but of course my redux store is not. When I do the calling of API before dispatching an action as shown earlier, there is also no error coming from the console log.
What could be the problem here? I am very new to ReactJS and can't find a solution yet to this problem after many tries.
Also, as a note, I am using redux-thunk in this project.
I have a few questions, but first: I think the problem is here:
[
...state.filter((post)=>post.id!==action.postID)
]
What is the state's shape? Is it state = [post1, post2, ...]? I can see the initial state is {}, so I find it weird to be calling state.filter and not state.posts.filter or whatever here.
The other might be problem, is with post.id !== action.postID, maybe the received ID is an number type, and maybe the local id is a string? Maybe the other way around?
I have a Reddit alike application. Where I'm trying to build out a voting function. I thought I had solved it because it workes great when on /
However, if I'm entering a different path /:category :/category/:id
I can see a dispatch being sent on click but here I'll have to "force update" (f5) to see a UI change.
API file
export const submitPostVote = (option, id) => {
return axios.post(`${API_URL}/posts/${id}`, {option}, { headers })
}
Action Creator (using redux-thunk)
export function postPostVote(option, id) {
const request = API.submitPostVote(option, id)
return (dispatch) => {
request.then(({data}) => {
dispatch({type: SUBMIT_POST_VOTE, payload: data})
});
};
}
Reducer
export default function(state = {}, action) {
const {payload} = action
switch (action.type){
case SUBMIT_POST_VOTE:
return {...state, [payload.id]: payload}
Component that use it
import { postPostVote } from '../actions';
<span
className='fa fa-angle-up voteArrow'
onClick={() => this.props.postPostVote('upVote', id)}
></span>
export default connect(null, {postPostVote})(PostComponent);
Imported as following in other components
import PostComponent from './Post_PostComponent';
<div>
<Container>
<PostComponent
key={id}
id={id}
title={title}
author={author}
voteScore={voteScore}
category={category}
timestamp={timestamp}
redirect={false}
/>
</Container>
</div>
Repo
Readable Repo
I think i see your problem, you are using the same reducer to both of the pages.
The page that holds a list of items, in this case the reducer shape
is an object that each key is an id of item and it's an object
as well that holds all the data of this item.
{
'6ni6ok3ym7mf1p33lnez': {
author: "thingone",
body: "Just kidding. It takes more than 10 minutes to learn technology.",
category: "redux",
deleted: false,
id: "6ni6ok3ym7mf1p33lnez",
timestamp: 1468479767190,
title: "Learn Redux in 10 minutes!",
voteScore: 6
}
}
The page that holds a single item, in this case the very same reducer
needs to deal with a different shape of object where all of the
item's properties are spread.
{
author: "thingone",
body: "Just kidding. It takes more than 10 minutes to learn technology.",
category: "redux",
deleted: false,
id: "6ni6ok3ym7mf1p33lnez",
timestamp: 1468479767190,
title: "Learn Redux in 10 minutes!",
voteScore: 6
}
Just for example, if you will change the shape of the object that your reducer_posts.js returns:
From this:
case SUBMIT_POST_VOTE:
return {...state, [payload.id]: payload}
To this:
case SUBMIT_POST_VOTE:
return {...state, ...payload}
You will notice that now the first page with the list not working well but the second page that shows the single item is working as expected.
So you should re-think the shape of your reducers or split this reducer into two.
EDIT
I was curious on what will be the best structure to handle this scenario so i took the liberty of changing some stuff for you.
So I've decided to split your reducer_posts.js into 2 reducers:
posts and post (plural and singular).
I've added another reducer reducer_post.js.
and this is the code:
import { POST_GET_POST, SUBMIT_POST_VOTE } from '../actions/action_constants';
export default function (state = {}, action) {
const { payload } = action
switch (action.type) {
case SUBMIT_POST_VOTE:
return { ...state, ...payload }
case POST_GET_POST:
return payload;
default:
return state;
}
}
And the old reducer reducer_posts.js now looks like this:
import _ from 'lodash';
import { POST_GET_POSTS, SUBMIT_POST_VOTE } from '../actions/action_constants';
export default function(state = {}, action) {
const {payload} = action
switch (action.type){
case SUBMIT_POST_VOTE:
return {...state, [payload.id]: payload}
case POST_GET_POSTS:
return _.mapKeys(payload, 'id');
default:
return state;
}
}
And of course don't forget to add it to the rootReducer:
export const rootReducer = combineReducers({
routing: routerReducer,
posts: PostsReducer,
post: PostReducer, // our new reducer
comments: CommentReducer,
categories: CategoriesReducer,
});
Basically one will handle multiple posts object shape and one will handle a single post object shape.
Now, the only thing you should change is the mapStateToProps in the Post_DetailedPost.js component.
Instead of using the posts reducer it will use the post reducer:
function mapStateToProps(state) {
return {post: state.post}
}
This should fix your problem.
I think the issue is this: The reducer logic for the vote action assumes that the state has some specific structure to it ( an object with posts, with their id as the key.), but then the reducer logic for the getPost action breaks this assumption.
Looked at the code in the repo, specifically for the "DetailedPost". This looked a bit off, but I may be wrong:
// in reducer_posts.js
case POST_GET_POST:**strong text**
return payload;
I would think that you get one specfic post from the API. If so, this return would basically get rid of everything else in the state. (All other posts). Maybe that's what you want, but I've never seen a reducer change the structure of the state in that way. I would have expected it to be something like
case POST_GET_POST:
return {...state, [payload.id]: payload}
(maybe without the ...state if you really want the rest of the posts to disappear.).
But then you'd have to change the mapStateToProps for the detailed view as well. You should be able to access the Router props from the second argument (Note: exact path on ownProps may be different. I don't remember exactly how the url params where stored)
// in Post_DetailedPost.js
function mapStateToProps(state, ownProps) {
const idFromURL = ownProps.id; // or maybe ownProps.match.params.id
return {post: state.posts[idFromURL]};
}
This could actually very well be the issue, because you upvote a post, you get the new (updated) post back, and you change the state like this.
case SUBMIT_POST_VOTE:
return {...state, [payload.id]: payload}
But that's not the state structure that your used to pass the post down down to your component. So you wouldn't get the updated props passed down to the details view. (But with the suggested edits to the mapStateToProps it might work.)