2

Im trying to update my Redux state in a component using a variable passed up to that component from a child, following a form submital callback. The form submits a users comment, which i want to store in my redux state. I'm unsure how to send this variable into the redux chain so i can use it in my action creator. I want to pass the newComment variable inside handleCommentSubmit into the this.props.getVideoComments() action creator. Here is the code:

CommentSection.js (where i want to update my state)

 //redux stuff
import {connect} from 'react-redux'
import {getVideoComments} from '../actions'

class CommentsSection extends React.Component{

    constructor(props){
        super(props)
        //this.state={comments:[], loading:false}

    }

    componentDidMount(){
        this.props.getVideoComments()

    }


    handleCommentSubmit = (newComment) =>{
        // call action creator to dist action to all reducers and update relevant states
        this.props.getVideoComments()
        //this.setState({comments: [...this.state.comments, newComment]})
        //this.setState({comments: newComments},console.log('The current state is now',this.state.comments));
        //comment is object with author and message. Add new comment to old comments
        //this.setState({comments:[...this.state.comments,newComment]},console.log(this.state, 'state updated'))

    }
    //Comments are create in comment form, passed up then sent down through commentList to individual comment rendering inside comment.js
// comment form oncommentsubmit running everytime it renders, not only on submital
    render(){
        const {comments} = this.props
        console.log({comments})
        return(
            <div>
                <span><h4> Comments </h4></span>
                <div className="ui grid"> 


                    <div className = "right floated eight wide column" >
                        <CommentList comments={comments}/> 
                    </div>
                    <div className="left floated eight wide column">

                        <CommentForm onCommentSubmit={this.handleCommentSubmit}/>

                    </div>
                 </div>
             </div>

        )

    }
}

//redux stuff
//called following state update
const mapStateToProps = (state) => {

    return {comments:state.videoComments}
}
export default connect(mapStateToProps,{getVideoComments:getVideoComments})(CommentsSection)

index.js (for action creators)

import React from 'react'

export const getVideoComments= ()=> {

    return (dispatch, getState)=> {

        const videoComments = getState().videoComments

        return ({
            type: 'GET_VIDEO_COMMENTS',
            payload: videoComments
        })
    }
}

videoCommentsReducer.js

import React from 'react'

 const videoCommentsReducer=function(state= [], action){ // reads in previous state
    switch (action.type){
        case 'GET_VIDEO_COMMENTS':
            return action.payload //reducer will update state to be payload.videoComments. Action only describes what happened
                                         // reducer describes how what happened effects state. Could also use previous state and action to create new data
        default: 
            return state
    }
}

export default videoCommentsReducer

index.js (in reducer folder where they are combined)

import React from 'react'
import {combineReducers} from 'redux'
import videoCommentsReducer from './videoCommentsReducer'

export default combineReducers({
    videoComments:videoCommentsReducer
})
Sean
  • 587
  • 4
  • 20

3 Answers3

1

From your action creator file, it seems that you are using the redux-thunk middleware, so make sure to import this library and apply it in the store. This codesandbox shows a complete example based on yours.

When using this thunk, make sure to always use the dispatch that it provides in order to send the action to the store. Don't return an object from the bound action creator:

export const getVideoComments = () => {
  return (dispatch, getState) => {
    const videoComments = getRandomComments();

    dispatch({
      type: "GET_VIDEO_COMMENTS",
      payload: videoComments
    });
  };
};

Also, it doesn't make sense to use getState here to get the video comments. You would simply update the store with the same state over and over again. getState is useful when you want to interact with a different part of the state, that is outside the reducer that captures your action type.

Rodrigo Amaral
  • 1,324
  • 1
  • 13
  • 17
  • BTW, if you want to debug your application, use a logging middleware like [redux-logger](https://github.com/LogRocket/redux-logger) – Rodrigo Amaral Sep 26 '19 at 02:23
1

Use mapDispatchToProps in your CommentSection.js and there's no need to use getState in your action creator.

Action Creator

const getVideoComments = (comments) => ({
   type: 'GET_VIDEO_COMMENTS',
   payload: comments,
});

CommentSection.js

// handleCommentSubmit
handleCommentSubmit = (newComment) => {
   this.props.getVideoComments(newComment); //pass comment to action then access newComment in reducer then add it to your state
}

mapDispatchToProps = (state) => {
   getVideoComments: (newComment) => dispatch(getVideoComments(newComment)),
}

export default connect(mapStateToProps, mapDispatchToProps)(CommentsSection);


Reducer.js

case 'GET_VIDEO_COMMENTS':
   return [...state, action.payload];
fctmolina
  • 196
  • 2
  • 10
  • ah right. So you can pass normal JS objects you define yourself into action creators too? – Sean Sep 26 '19 at 10:02
  • i am quite confused about what is going on here. The redux docs say that the only arguments `mapDispatchToProps` accepts is `dispatch` and `ownProps`. I'm also unsure why you are dispatching an action creator inside `mapDispatchToProps` and not an action object? Finally, wouldn't this currently just replace my state with the newest comment, rather than adding it on to the others? – Sean Sep 26 '19 at 10:19
  • Action creator just returns an object so it's just like your passing an object to your dispatch. ```mapDispatchToProps = (state) => { getVideoComments: (newComment) => dispatch(getVideoComments(newComment)), }``` ```getVideoComments:``` is the props. Then you tell it what action to dispatch hence ```(newComment) => dispatch(getVideoComments(newComment))``` – fctmolina Sep 26 '19 at 11:12
  • @Sean regarding replacing your current state in reducer, I edited my answer above to show how you can edit your reducer so that it won't replace your current state and just add the new comment. – fctmolina Sep 26 '19 at 11:25
  • thanks for the response. I already implemented that in my answer following your initial advice. I would mark yours as the solution but when i copied the mapDispatchToProps i got a few errors – Sean Sep 26 '19 at 11:28
  • @Sean what errors did you get? Maybe I experienced them and can help in debugging it. Reference: https://react-redux.js.org/using-react-redux/connect-mapdispatch#arguments – fctmolina Sep 26 '19 at 11:30
0

Thought id post my solution as an answer as it combined parts of both answers, but needed bits of both to fully solve the issue.

By combining parts of both the above answers I was able to fully solve the problem. I removed getState() as both fctmolina and Rodrigo Amaral suggested. I also simplified the action creator to returning a javascript object, rather than a function, and so no longer needed to include a dispatch function, or use redux-thunk. I passed the newComment variable into my action creator, and then combined it with my old state inside the reducer. The solution only required a simply definition of the mapDispatchToProps as a JS object containing the action creator getVideoComments, which made it available as a prop to commentSection, and resulted in the action creator being dispatched when the this.props.getVideoComments() function call was made. Here is the altered code:

CommentSection.js

import React from 'react'
import CommentList from './CommentList'
import CommentForm from './CommentForm'


//redux stuff
import {connect} from 'react-redux'
import {getVideoComments} from '../actions'

class CommentsSection extends React.Component{

    constructor(props){
        super(props)
        //this.state={comments:[], loading:false}

    }

    componentDidMount(){
        console.log(this.props.comments)
    }


    handleCommentSubmit = (newComment) =>{
        // call action creator to dist action to all reducers and update relevant states
        this.props.getVideoComments(newComment)

    }
    //Comments are create in comment form, passed up then sent down through commentList to individual comment rendering inside comment.js
// comment form oncommentsubmit running everytime it renders, not only on submital
    render(){
        const {comments} = this.props
        console.log({comments})
        return(
            <div>
                <span><h4> Comments </h4></span>
                <div className="ui grid"> 


                    <div className = "right floated eight wide column" >
                        <CommentList comments={comments}/> 
                    </div>
                    <div className="left floated eight wide column">

                        <CommentForm onCommentSubmit={this.handleCommentSubmit}/>

                    </div>
                 </div>
             </div>

        )

    }
}

//redux stuff
//called following state update
const mapStateToProps = (state) => {

    return {comments:state.videoComments}
}

export default connect(mapStateToProps,{getVideoComments:getVideoComments})(CommentsSection)

videoCommentsReducer.js

import React from 'react'

 const videoCommentsReducer=function(state= [], action){ // reads in previous state
    switch (action.type){
        case 'GET_VIDEO_COMMENTS':
            return [...state, action.payload] //reducer will update state to be payload.videoComments. Action only describes what happened
                                         // reducer describes how what happened effects state. Could also use previous state and action to create new data
        default: 
            return state
    }
}

export default videoCommentsReducer

index.js (for action creator)

import React from 'react'

export const getVideoComments = (newComment) => {
    return({
        type: 'GET_VIDEO_COMMENTS',
        payload: newComment
    })
};

Sean
  • 587
  • 4
  • 20
  • No need for the return in your action cause javascript implents implicit return in arrow functions. Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#Function_body – fctmolina Sep 27 '19 at 02:25