I'm trying to pass an array of items(state) in mapStateToProps. However i get an empty array or it shows undefined.
App.js
import React, { Component } from 'react';
import PostList from './PostList';
import Axios from '../Axios';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {DeletePost, GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
loading: true,
}
getPosts = () => {
Axios.get(process.env.REACT_APP_GET_POSTS)
.then( (res) => {
this.setState({
posts: res.data,
loading: false
})
})
// console.log(this.state.posts);
}
componentWillMount(){
this.getPosts();
}
componentDidMount(){
// doesn't show posts in here
console.log(this.props.posts)
this.props.GetPosts(this.state.posts);
}
onDelete = (id) => {
Axios.post(`/api/posts/delete/${id}`);
this.setState({
posts: this.state.posts.filter(post => post.id !== id)
})
}
render() {
const {loading, posts} = this.state;
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList DeletePost={this.onDelete} posts={posts}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
posts: state.user.posts
})
const mapDispatchToProps = (dispatch, state) => ({
// newPost: (post) => dispatch(newPost(post)),
// DeletePost: (id) => dispatch( DeletePost(id))
GetPosts: (posts) => dispatch( GetPosts(posts))
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Posts));
Reducer.js
import { GET_POSTS} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[]
}
export default (state = initialState, action) => {
switch (action.type) {
// doesn't get posts
case GET_POSTS:
return({
...state,
posts: action.posts
})
default:
return state
}
actions
export const GetPosts = (posts) => {
return (dispatch, getState) => {
dispatch({type: GET_POSTS, posts })
console.log('this works i guess', posts);
}
}
I would advice you not to save posts in two places. That somewhat defeats the purpose of using redux. You actually don't need post as a state variable in Posts class. Whenever there is a new state in redux store associated Class will fall into updation cycle.
Also, you can have a look at redux-thunk if you are making api calls.
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
It will help you move api fetching logic to actions and reducers and thus rendering your views clean.
Change this
export const GetPosts = (posts) => {
return (dispatch, getState) => {
dispatch({type: GET_POSTS, posts })
console.log('this works i guess', posts);
}
}
to
export const GetPosts = (posts) => {
return (dispatch, getState) => {
Axios.get(process.env.REACT_APP_GET_POSTS)
.then( (res) => {
dispatch({type: GET_POSTS, res.data })
})
})
}
}
Change this
componentWillMount(){
this.getPosts();
}
to
componentWillMount(){
this.props.GetPosts();
}
Now you wont be needing a componentDidUpdate.
Also, if you are wondering how to show Loading... till the api call is not completed, you can add a key isFetching to your store.
const initialState = {
post: [],
postError: null,
posts:[],
isFecthing: false
}
and can add an action something like ChangeFetchStats
export const GetPosts = (posts) => {
return (dispatch, getState) => {
dispatch({type: CHANGE_STATE, false});
Axios.get(process.env.REACT_APP_GET_POSTS)
.then( (res) => {
dispatch({type: CHANGE_STATUS, true);
dispatch({type: GET_POSTS, res.data })
})
})
}
}
Sometimes, it may take time over a network to get a POST response. In such case, if ur component gets mounted, it will make a call to the action, but because it takes time, you will get response empty/ undifined posts array.
To prevent this from happening, you can go with following :
componentDidMount(){
this.props.GetPosts(this.state.posts);
if(!this.props.posts){
console.log(this.props.posts);
}
}
A little tweek in the render method may help too:
render() {
const {loading, posts} = this.props;
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
{ posts &&(
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList DeletePost={this.onDelete} posts={posts}/>
</div>
);)}
}
So i finally found a purpose for componentDidupdate
The app took a little bit long to load posts maybe half a second.
So by calling componentDidUpdate, i get the posts after its finished rendering.
componentDidUpdate(){
this.props.GetPosts(this.state.posts);
}
Along with another solution by #stevek
change this
case GET_POSTS:
return({
...state,
posts: state.posts
})
to this
import { GET_POSTS} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[]
}
export default (state = initialState, action) => {
switch (action.type) {
// doesn't get posts
case GET_POSTS:
return{...state, posts: action.posts}
default:
return state
}
}
And i can see it after its rendered
Related
I'm on my Home Component where I need to show the article feed and for that, I have to have the articleList array. But for some reason when I look into the store, articleList is null. Also, the console.log that I have placed after fetching the data is also not working. It all seems strange.
Home.js
import React, { Component } from "react"
import { connect } from "react-redux"
import { listAllArticles } from "../actions/articles"
class Home extends Component {
componentDidMount() {
this.props.dispatch(listAllArticles)
}
render() {
console.log(this.props)
return (
<div style={{ textAlign: "center" }}>
<h1>Conduit</h1>
<h5>A place to share your knowledge</h5>
</div>
)
}
}
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps)(Home)
listAllArticles
export const listAllArticles = () => {
console.log("inside listAllArticles action creator")
return dispatch => {
fetch("https://conduit.productionready.io/api/articles")
.then(res => res.json())
.then(data => {
console.log(data.articles)
dispatch({
type: "LIST_ALL_ARTICLES",
data: data.articles
})
})
}
}
articleReducer
const initState = {
articleList: null
}
export const articleReducer = (state=initState, action) => {
console.log("inside article reducer")
switch(action.type) {
case "LIST_ALL_ARTICLES":
return {...state, articleList: action.data}
default:
return state
}
}
My problem is that mapStateToProps returns undefined. Maybe I have some problems with dispatching in the state or maybe app rendering before data comes from the server? I can't understand. So app works right without redux with just componentDidMount, but I have some problems with redux
So I have a top-level component App:
const App = () => {
return (
<Provider store={store}>
<Screen />
</Provider>
)
}
I have store with thunk meddleware:
const store = createStore(reducer, applyMiddleware(ReduxThunk));
Two types of action:
export const fetchData = (newPhotos) => async (dispatch) => {
function onSuccess(success) {
dispatch({
type: FETCH_DATA,
payload: success})
return success
}
function onError(error) {
dispatch({type: FETCH_FAILED, error})
}
try {
const URL = 'https://api.unsplash.com/photos/?client_id=cf49c08b444ff4cb9e4d126b7e9f7513ba1ee58de7906e4360afc1a33d1bf4c0';
const res = await fetch(URL);
const success = await res.json();
console.log(success);
return onSuccess(success);
} catch (error) {
return onError(error)
}
};
reducer:
const initialState = {
data: []
}
export default dataReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case FETCH_DATA:
return {
data: action.payload
}
case FETCH_FAILED:
return {
state
}
default: return state;
}
}
combine reducers:
export default combineReducers({
fetchedData: dataReducer
});
and my rendering component:
class HomeScreen extends Component {
render() {
console.log(this.props)
const {navigation, data} = this.props;
return (
<ScrollView>
<Header />
<ImageList navigation={navigation} data={data}/>
</ScrollView>
)
}
}
const mapStateToProps = state => {
return {
data: state.fetchedData.data
}
}
export default connect(mapStateToProps, {fetchData})(HomeScreen);
fetchData action will not be called on its own. You need to call that explicitly(in componentDidMount and, probably, componentDidUpdate) like
class HomeScreen extends Component {
componentDidMount() {
this.props.fetchData(/* I don't know where you are going to take newPhotos argument */);
}
render() {
//...
}
}
I'm retrieving posts from my actions, and its not showing up in
console.log(this.props.posts)
however it does show up under the actions console.log
Instead i get this
ideally in the posts:Array It should show Array(2) So it appears that redux is not updating the initialState posts array making it impossible to retrieve posts. And changing action.data.data to action.data won't change anything.
actions.js
export const GetPosts = () => {
return (dispatch, getState) => {
return Axios.get('/api/posts/myPosts')
.then( (res) => {
const data = {
data: res.data
}
console.log(data); // logs data and i can see an array
dispatch({type: GET_POSTS, data })
})
}
}
So i update the state so i can be able to retrieve it in a component.
posts.js
import { POST_FAIL, GET_POSTS, POST_SUCC, DELETE_POST} from '../actions/';
const initialState = {
post: [],
postError: null,
posts:[]
}
export default (state = initialState, action) => {
switch (action.type) {
case POST_SUCC:
return ({
...state,
post:action.post
});
case POST_FAIL:
return({
...state,
postError: action.err.response.data
})
case GET_POSTS:
// console.log(action.data.data)
return {...state, posts: action.data.data}
// case DELETE_POST:
// return ({
// ...state,
// posts: action.posts.filter(post => post.id !== action.posts[0].id)
// })
default:
return state
}
}
Posts.js
import React, { Component } from 'react';
import PostList from './PostList';
import Axios from '../Axios';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {DeletePost, GetPosts} from '../actions/';
const Styles = {
myPaper:{
margin: '20px 0px',
padding:'20px'
}
,
wrapper:{
padding:'0px 60px'
}
}
class Posts extends Component {
state = {
posts: [],
loading: true,
}
componentWillMount(){
this.props.GetPosts();
// renders an empty posts array
console.log(this.props.posts);
}
onDelete = (id) => {
Axios.post(`/api/posts/delete/${id}`);
this.setState({
posts: this.state.posts.filter(post => post.id !== id)
})
}
render() {
const {loading, posts} = this.state;
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
{/* <PostList posts={this.props.posts}/> */}
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
// i know i have to use state.post.posts but i wanted to get an idea if the
// initialize state updated at all
posts: state.post
})
const mapDispatchToProps = (dispatch, state) => ({
// newPost: (post) => dispatch(newPost(post)),
// DeletePost: (id) => dispatch( DeletePost(id))
GetPosts: () => dispatch( GetPosts())
});
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Posts));
Your reducer is setting the posts state property:
case GET_POSTS:
// console.log(action.data.data)
return {...state, posts: action.data.data}
Your component is mapping the post state property to your component's posts property:
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
// i know i have to use state.post.posts but i wanted to get an idea if the
// initialize state updated at all
posts: state.post
})
Have a component to display user information. However, when the user logouts out, and shouldn't be in the store anymore ( I have set a dispatch up for this as well). Also, I am able to reload the entire page and then the user information displays. I have been having a go with componentDidUpdate and componentDidMount but I can't seem to figure it out.
Here is the view component:
// import React from "react";
// import { connect } from "react-redux";
// import { getUser } from "../store/actions/userActions";
// import { withRouter } from 'react-router-dom';
import React from 'react';
import { connect } from 'react-redux';
import * as actions from '../store/actions/auth';
class UserDetailView extends React.Component {
componentDidMount() {}
shouldComponentUpdate(nextProps, props) {
console.log(nextProps);
const username = this.props.user.username;
console.log(username);
if (username !== nextProps.username) {
console.log(username);
return true;
} else {
return false;
}
}
render() {
const user = this.props.user;
return (
<div>
{this.props.user ? (
<div>
<h3>{user.username}</h3>
{this.props.user.email}
</div>
) : (
<h3>Not Logged In</h3>
)}
</div>
);
}
}
const mapStateToProps = state => ({
username: state.username,
user: state.user
});
const mapStateToDispatch = dispatch => ({
onTryAutoSignup: () => dispatch(actions.authCheckState()),
getfetchUser: id => dispatch(actions.fetchUser(id))
});
export default connect(
mapStateToProps,
mapStateToDispatch
)(UserDetailView);
// class UserDetailView extends React.Component {
// componentDidMount() {
// const { getUser, userID } = this.props
// getUser(userID) //fixed
// }
// render() {
// console.log(this.props.userID)
// console.log(this.props.user)
// return (
// <ul>
// {this.props.user.map(user =>
// <li key={user.id}>{user.username}</li>
// )}
// </ul>
// );
// }
// }
// const mapStateToProps = (state, ownProps) => ({
// user: state.user,
// userID: ownProps.match.params.userID,
// });
// const mapDispatchToProps = dispatch => ({ //added
// getUser: (userID) => dispatch(getUser(userID))
// })
// export default withRouter(connect(mapStateToProps, {getUser})(UserDetailView)); //fixed
Reducer:
const getUserInformation = (state, action) => {
return Object.assign({}, state, {
user: action.payload.user
});
};
Action Generator and Action
export const authSuccess = (token, username) => {
return {
type: actionTypes.AUTH_SUCCESS,
token: token,
username: username
};
};
export const fetchUser = username => {
return dispatch => {
return axios
.get(`http://127.0.0.1:8000/api/user/${username}/`)
.then(res => {
const user = res.data;
dispatch(getUserInformation(user));
});
};
};
I see no reason to override shouldComponentUpdate, just inherit from React.PureComponent.
You have some mix-ups in action creators and reducers. It should be something like this:
dispatch(setUserInformation(user)); // dispatch action
const setUserInformation = ({ type: 'SET_USER_INFORMATION', user }); // this is the action creator, returns an object with the type and the payload
const reducer = (state, action) { // this is the reducer
switch (action.type) {
case 'SET_USER_INFORMATION':
return {
...state,
user: action.user
}
}
}
I have read through 100's of these threads on here, and I can't seem to understand why my component isn't updating. I am pretty sure it has something to do with the Immutability, but I can't figure it out.
The call is being made, and is returning from the server. The state is changing (based on the redux-Dev-Tools that I have installed).I have made sure to not mutate the state in any instance, but the symptoms seem to point that direction.
Code Sandbox of whole app https://codesandbox.io/s/rl7n2pmpj4
Here is the component.
class RetailLocationSelector extends Component {
componentWillMount() {
this.getData();
}
getData = () => {
this.props.getRetailLocations()
}
render() {
const {data, loading} = this.props;
return (
<div>
{loading
? <LinearProgress/>
: null}
<DefaultSelector
options={data}
placeholder="Retail Location"/>
</div>
);
}
}
function mapStateToProps(state) {
return {
loading: state.retaillocations.loading,
data: state.retaillocations.data,
osv: state.osv};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
getRetailLocations,
selectRetailLocation,
nextStep
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(RetailLocationSelector);
And here is my reducer :
import {REQUEST_RETAIL_LOCATIONS, SUCCESS_RETAIL_LOCATIONS,
ERR_RETAIL_LOCATIONS, SELECT_RETAIL_LOCATION} from
'../actions/RetailLocationsAction'
const initialState = {
data: [],
loading: false,
success: true,
selectedRetailLocation: undefined
}
function retailLocation(state = initialState, action) {
switch (action.type) {
case REQUEST_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: true
}, {success: true})
case SUCCESS_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: false
}, {
success: true
}, {
data: Object.assign([], action.payload.data)
})
case ERR_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: false
}, {
success: false
}, {errorMsg: action.payload.message})
case SELECT_RETAIL_LOCATION:
return Object.assign({}, state, {
selectedRetailLocation: state
.data
.find((rec) => {
return rec.id === action.payload.id
})
})
default:
return state;
}
}
export default retailLocation
And finally, my Action file:
import axios from 'axios';
//import {api} from './APIURL'
export const REQUEST_RETAIL_LOCATIONS = 'REQUEST_RETAIL_LOCATIONS'
export const SUCCESS_RETAIL_LOCATIONS = 'SUCCESS_RETAIL_LOCATIONS'
export const ERR_RETAIL_LOCATIONS = 'ERR_RETAIL_LOCATIONS'
export const SELECT_RETAIL_LOCATION = 'SELECT_RETAIL_LOCATION'
const URL = 'localhost/api/v1/retail/locations?BusStatus=O&LocType=C'
export const getRetailLocations = () => (dispatch) => {
dispatch({ type: 'REQUEST_RETAIL_LOCATIONS' });
return axios.get(URL)
.then(data => dispatch({ type: 'SUCCESS_RETAIL_LOCATIONS', payload: data }))
.catch(error => dispatch({type : 'ERR_RETAIL_LOCATIONS', payload: error}));
}
Combined Reducer
import { combineReducers } from "redux";
import retailLocations from './RetailLocationsReducer'
import vendors from './VendorsReducer'
import receiptInformation from './ReceiptInfoReducer'
import osv from './OSVReducer'
import receiptDetail from './ReceiptDetailReducer'
const allReducers = combineReducers({
retaillocations: retailLocations,
vendors: vendors,
receiptInformation: receiptInformation,
receiptDetail: receiptDetail,
osv: osv
});
export default allReducers;
This answer doesn't solve your issue totally but provides some hints about what is not working. The broken part is your store definition. I don't have much experience with redux-devtools-extension or redux-batched-subscribe but if you define your store like that your thunk function works:
const store = createStore(reducer, applyMiddleware(thunk));
One of the configuration for the mentioned packages above brokes your thunk middleware.