I have the following React component that shows all the users posts through the "renderPosts" method. Below it there's a like/unlike button on whether the currently logged in user has liked the post.
However, when I click on the like button, the component does not re-render in order for the "renderPosts" method to create an unlike button and the "like string" is modified as expected. Only when I go to another component and then come back to this component does the unlike button display and vice versa.
Is there anyway that I could fix this with Redux in my app? I tried this.forceUpdate after the onClick event but still does not work...
Also I tried creating a new Reducer called "likers", according to robinsax which basically get the array of users who like a particular post and imported it as props into the component but got
"this.props.likers.includes(currentUser)" is not a function
When the app first gets to the main page (PostIndex), probably because this.props.likers is still an empty object returned from reducer
Here is the code for my action creator:
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/likePost`,request);
return{
type: LIKE_POST,
payload: post
}
}
export function unlikePost(username,postId){
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/unlikePost`,request);
return{
type: UNLIKE_POST,
payload: post
}
}
And this is my reducer:
import {LIKE_POST,UNLIKE_POST} from '../actions/index.js';
export default function(state = {},action){
switch(action.type){
case LIKE_POST:
const likers = action.payload.data.likedBy;
console.log(likers);
return likers;
case UNLIKE_POST:
const unlikers = action.payload.data.likedBy;
console.log(unlikers);
return unlikers;
default:
return state;
}
}
I would really appreciate any help since I'm a beginner
import { fetchPosts } from "../actions/";
import { likePost } from "../actions/";
import { unlikePost } from "../actions/";
class PostsIndex extends Component {
componentDidMount() {
this.props.fetchPosts();
}
renderPost() {
const currentUser = Object.values(this.props.users)[0].username;
return _.map(this.props.posts, post => {
return (
<li className="list-group-item">
<Link to={`/user/${post.username}`}>
Poster: {post.username}
</Link>
<br />
Created At: {post.createdAt}, near {post.location}
<br />
<Link to={`/posts/${post._id}`}>{post.title}</Link>
<br />
//error here, with this.props.likers being an
//array
{!this.props.likers.includes(currentUser) ? (
<Button
onClick={() => this.props.likePost(currentUser,post._id)}
bsStyle="success"
>
Like
</Button>
) : (
<Button
onClick={() => this.props.unlikePost(currentUser,post._id)}
bsStyle="warning"
>
Unlike
</Button>
)}{" "}
{post.likedBy.length === 1
? `${post.likedBy[0]} likes this`
: `${post.likedBy.length} people like this`}
</li>
);
});
}
function mapStateToProps(state) {
return {
posts: state.posts,
users: state.users,
likers: state.likers
};
}
}
You need to use redux-thunk middleware in order to use async actions. First, add redux-thunk while creating store like
import thunk from 'redux-thunk';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
then change your method like this
export function likePost(username,postId) {
return function(dispatch) {
// body...
const request = {
username,
postId
}
axios.post(`${ROOT_URL}/likePost`,request)
.then(res => {
dispatch({
type: LIKE_POST,
payload: res
});
});
}
}
and now in your component after mapStateToProps, define mapDispatchToProps,
const mapDispatchToProps = dispatch => {
return {
likePost: (currentUser,postId) => dispatch(likePost(currentUser, postId)),
// same goes for "unlike" function
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
Seems like the like/unlike post functionality isn't causing anything in your state
or props
to change, so the component doesn't re-render.
You should change the data structure you're storing so that the value of post.likedBy.includes(currentUser)
is included in one of those, or forceUpdate()
the component after the likePost
and unlikePost
calls.
Please do it the first way so I can sleep at night. Having a component's render()
be affected by things not in its props
or state
defeats the purpose of using React
.
The problem is in your action creator.
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
// this is an async call
const post = axios.post(`${ROOT_URL}/likePost`,request);
// next line will execute before the above async call is returned
return{
type: LIKE_POST,
payload: post
}
}
Because of that your state is likely never updated and stays in the initial value.
You would need to use either redux-thunk
or redux-saga
to work with async actions.
As noted in other answers, you need to use redux-thunk
or redux-saga
to make async calls that update you reducer. I personally prefer redux-saga
. Here's is a basic implementation of React, Redux, and Redux-Saga.
Redux-Saga uses JavaScript generator functions and yield to accomplish the goal of handling async calls.
Below you'll see a lot of familiar React-Redux code, the key parts of Redux-Saga are as follows:
watchRequest
- A generator function that maps dispatch actions to generator functions loadTodo
- A generator function called from watchRequest
to yield
a value from an async call and dispatch an action for the reducer getTodoAPI
- A regular function that makes a fetch
request applyMiddleware
- from Redux is used to connect Redux-Saga with createStore
const { applyMiddleware, createStore } = Redux; const createSagaMiddleware = ReduxSaga.default; const { put, call } = ReduxSaga.effects; const { takeLatest } = ReduxSaga; const { connect, Provider } = ReactRedux; // API Call const getTodoAPI = () => { return fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => { return response.json() .then(response => response); }) .catch(error => { throw error; }) }; // Reducer const userReducer = (state = {}, action) => { switch (action.type) { case 'LOAD_TODO_SUCCESS': return action.todo; default: return state; } }; // Sagas, which are generator functions // Note: the asterix function* loadTodo() { try { const todo = yield call(getTodoAPI); yield put({type: 'LOAD_TODO_SUCCESS', todo}); } catch (error) { throw error; } } // Redux-Saga uses generator functions, // which are basically watchers to wait for an action function* watchRequest() { yield* takeLatest('LOAD_TODO_REQUEST', loadTodo); } class App extends React.Component { render() { const { data } = this.props; return ( <div> <button onClick={() => this.props.getTodo()}>Load Data</button> {data ? <p>data: {JSON.stringify(data)}</p> : null } </div> ) } } // Setup React-Redux and Connect Redux-Saga const sagaMiddleware = createSagaMiddleware(); const store = createStore(userReducer, applyMiddleware(sagaMiddleware)); sagaMiddleware.run(watchRequest); // Your regular React-Redux stuff const mapStateToProps = (state) => ({ data: state }); // Map the store's state to component's props const mapDispatchToProps = (dispatch) => ({ getTodo: () => dispatch({type: 'LOAD_TODO_REQUEST'}) }) // wrap action creator with dispatch method const RootComponent = connect(mapStateToProps, mapDispatchToProps)(App); ReactDOM.render( <Provider store={store}> <RootComponent /> </Provider>, document.getElementById('root') );
<script src="https://npmcdn.com/babel-regenerator-runtime@6.3.13/runtime.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.0/react-redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/0.16.2/redux-saga.min.js"></script> <div id="root"></div>
As they say use redux-thunk or redux-saga. If your new to redux I prefer redux-thunk because it's easy to learn than redux-saga. You can rewrite your code like this
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/likePost`,request);
return dispatch => {
post.then(res => {
dispatch(anotherAction) //it can be the action to update state
});
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.