我有以下React组件,该组件通过“ renderPosts”方法显示所有用户的帖子。在其下方,有一个喜欢/不同按钮,用于显示当前登录用户是否喜欢该帖子。
但是,当我单击“ like”按钮时,该组件不会重新渲染,以便“ renderPosts”方法创建一个“ like”按钮,并且按预期方式修改了“ like字符串”。只有当我转到另一个组件然后再回到该组件时,才会显示不同的按钮,反之亦然。
无论如何,我可以使用Redux在我的应用程序中解决此问题吗?我在onClick事件之后尝试了this.forceUpdate,但仍然无法正常工作...
根据robinsax的说法,我还尝试创建一个新的Reducer,称为“ likers”,它基本上得到了喜欢特定帖子的用户数组,并将其作为道具导入组件中,但得到了
"this.props.likers.includes(currentUser)" is not a function
应用程序首次进入主页(PostIndex)时,可能是因为this.props.likers仍然是reducer返回的空对象
这是我的动作创建者的代码:
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
}
}
这是我的减速器:
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;
}
}
由于我是初学者,我将非常感谢您的帮助
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
};
}
}
答案 0 :(得分:2)
您需要使用redux-thunk中间件才能使用异步操作。 首先,在创建像这样的商店时添加redux-thunk
import thunk from 'redux-thunk';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
然后像这样更改您的方法
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
});
});
}
}
,现在在mapStateToProps之后的组件中,定义mapDispatchToProps,
const mapDispatchToProps = dispatch => {
return {
likePost: (currentUser,postId) => dispatch(likePost(currentUser, postId)),
// same goes for "unlike" function
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
答案 1 :(得分:1)
好像“喜欢/不喜欢”帖子功能不会引起您的state
或props
中的任何更改,因此该组件不会重新渲染。
您应该更改要存储的数据结构,以使post.likedBy.includes(currentUser)
的值包含在其中之一,或者forceUpdate()
和{{1}之后的组件中}。
请先做一下,这样我就可以晚上入睡。使组件的likePost
受其unlikePost
或render()
中未包含的事物的影响会破坏使用props
的目的。
答案 2 :(得分:1)
问题出在您的动作创建者上。
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
}
}
因为您的状态可能永远不会更新,并保持在初始值。
您需要使用redux-thunk
或redux-saga
来执行异步操作。
答案 3 :(得分:1)
如其他答案所述,您需要使用redux-thunk
或redux-saga
进行异步调用以更新减速器。我个人更喜欢redux-saga
。这是React,Redux和Redux-Saga的基本实现。
Redux-Saga使用JavaScript generator functions和yield完成了处理异步调用的目标。
下面您将看到很多熟悉的React-Redux代码,Redux-Saga的关键部分如下:
watchRequest
-生成器函数,可将调度动作映射到生成器函数loadTodo
-从watchRequest
到yield
的生成器函数中调用异步调用中的值,并为reducer调度动作getTodoAPI
-发出fetch
请求的常规函数applyMiddleware
-来自Redux的用于将Redux-Saga与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>
答案 4 :(得分:0)
正如他们所说,使用redux-thunk或redux-saga。如果您是redux的新手,我更喜欢redux-thunk,因为它比redux-saga更易于学习。您可以像这样重写代码
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
});
}
}