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
})
Related
I am facing an issue in my code base so I have made a sample code to demonstrate the issue.
link for the codesandbox code
App.js
import React, { Component } from 'react';
import './App.css';
import { connect } from 'react-redux';
import { handleDataInit, handlePageChange, handleDataAdded } from './appDataAction';
import First from './First';
import Second from './Second';
import { reduxStore } from "./store";
class App extends Component {
handleChange = (pageNumber, pageTitle) => {
let data = {
val1: "val1",
val2: "val2",
val3: "val3"
}
this.props.handleDataAdded(data);
console.log("app Data", this.props.appData);
console.log('app data in redux store ', reduxStore.getState().appData);
this.props.handlePageChange({ pageNumber, pageTitle });
}
render() {
return (
<div>
<button onClick={() => this.handleChange(1, "first_page")}>1</button>
<button onClick={() => this.handleChange(2, "second_page")}>2</button>
{
this.props.appData.pageNumber === 1 ?
<First />
:
<Second />
}
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('map state to props state value is ', state);
return ({
appData: state && state.appData
})
}
const mapDispatchToProps = (dispatch) => {
return ({
handleDataInit: (data) => dispatch(handleDataInit(data)),
handlePageChange: (newPage) => dispatch(handlePageChange(newPage)),
handleDataAdded: (data) => dispatch(handleDataAdded(data))
})
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
screenshot for the two console.log
browser console log:
appDataAction.js
export const handleDataInit = (data) => {
return ({
type: "data_init",
payload: data
});
}
export const handlePageChange = (newPage) => {
return ({
type: "page_change",
payload: newPage
});
}
export const handleDataAdded = (data) => {
return ({
type: "data_added",
payload: data
});
}
appDataReducer.js
const initialState = {
pageNumber: 1,
pageTitle: "first_page",
}
export const appDataReducer = (state = initialState, action) => {
switch (action.type) {
case "data_init":
if (Object.keys(state).length > 2) {
return state
}
else {
let newState = Object.assign({}, state, action.payload);
// console.log("new state in init ", newState);
return newState;
}
case "page_change":
// console.log('action.payload', action.payload);
let newState2 = {
...state,
pageNumber: action.payload.pageNumber,
pageTitle: action.payload.pageTitle
}
// console.log('new state is ', newState2);
return newState2;
case "data_added":
let newState3 = Object.assign({}, state, action.payload);
// console.log("new state in data added ", newState3);
return newState3;
default:
return state;
}
}
From react-redux documentation
The first argument to a mapStateToProps function is the entire Redux store state (the same value returned by a call to store.getState()).
can somebody explain why there is difference in the two console's.
I have debugged and found out that after return from reducer mapStateToProps is called and it gets the updated value of state
then why is this.props.appData is not up to date in the handleChange function.
I believe it could be something related to dirty state but if it is proper for getState() in the function it should be for this.props.appData too.
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
}
}
I am trying to create a higher order component that displays a spinner when data is fetching from a server and the component with the data when it's done. However with the implementation I have done below it goes straight to rendering the component and not the spinner giving an error the it can not read property length of null.
I think the problem might be related to the initial state I am giving to the reducer with the isFetching: false property. When debugging the application the HOC withSpinner gets an isLoading prop as false. To test withSpinner HOC I set the isFetching initial state as true. This way it only displays a spinner and doesn't continue updating the component. The selected value isFetching from the reducer never updates to false.
Redux actions file
import ProductActionTypes from "./product.types";
import { DB } from "../../api/DB";
const fetchProductsStart = () => ({
type: ProductActionTypes.FETCH_PRODUCTS_START
});
const fetchProductsSuccess = products => ({
type: ProductActionTypes.FETCH_PRODUCTS_SUCCESS,
payload: products
});
const fetchProductsFailure = errorMessage => ({
type: ProductActionTypes.FETCH_PRODUCTS_FAILURE,
payload: errorMessage
});
export const fetchProductsStartAsync = (series, queryStrings) => {
return dispatch => {
dispatch(fetchProductsStart());
DB.Product.getFilteredProducts(series, queryStrings)
.then(products => dispatch(fetchProductsSuccess(products)))
.catch(err => dispatch(fetchProductsFailure(err)));
};
};
Redux reducer
import ProductActionTypes from "./product.types";
const INITIAL_STATE = {
products: null,
errorMessage: null,
isFetching: false,
totalResultsFromQuery: 0
};
const productReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ProductActionTypes.FETCH_PRODUCTS_START:
return {
...state,
isFetching: true
};
case ProductActionTypes.FETCH_PRODUCTS_SUCCESS:
return {
...state,
isFetching: false,
products: action.payload[0],
totalResultsFromQuery: action.payload[1][0].totalResultsFromQuery
};
case ProductActionTypes.FETCH_PRODUCTS_FAILURE:
return {
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return state;
}
};
export default productReducer;
I am using reselect to get the isFetching value from my redux store:
export const selectIsFetching = createSelector(
[selectProducts],
products => products.isFetching
);
The React Component:
import React from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import { compose } from "redux";
import { fetchProductsStartAsync } from "../../redux/product/product.actions";
import {
selectProductItems,
selectIsFetching,
} from "../../redux/product/product.selectors";
import {
selectCurrentPage,
selectCapacity
} from "../../redux/filter/filter.selectors";
import withSpinner from "components/withSpinner/withSpinner";
import ProductItemCard from "components/ProductItemCard/ProductItemCard";
import "./ProductsWrapper.css";
import {
selectSearchInput,
selectColor,
selectFamily,
selectPriceFrom,
selectPriceTo
} from "redux/filter/filter.selectors";
import { resetDefault } from "../../redux/filter/filter.actions";
class ProductsWrapper extends React.Component {
componentDidMount() {
const {
fetchProducts,
match: { params },
color,
searchInput,
priceFrom,
priceTo,
family,
page,
capacity
} = this.props;
fetchProducts(params.seria, {
color,
searchInput,
priceFrom,
priceTo,
family,
page,
capacity
});
}
componentDidUpdate(prevProps) {
const {
fetchProducts,
match: { params },
color,
searchInput,
priceFrom,
priceTo,
family,
resetFilterToDefault,
page,
capacity
} = this.props;
if (params.seria !== prevProps.match.params.seria) {
resetFilterToDefault();
fetchProducts(params.seria, {
color,
searchInput,
priceFrom,
priceTo,
family,
page,
capacity
});
}
if (
color !== prevProps.color ||
searchInput !== prevProps.searchInput ||
priceFrom !== prevProps.priceFrom ||
priceTo !== prevProps.priceTo ||
family !== prevProps.family ||
page !== prevProps.page ||
capacity !== prevProps.capacity
) {
fetchProducts(params.seria, {
color,
searchInput,
priceFrom,
priceTo,
family,
page,
capacity
});
}
}
render() {
const { products } = this.props;
return (
<div className="products-outer-wrapper">
<div className="products-wrapper">
{products.length !== 0 ? (
products.map(({ ...productProps }, index) => (
<ProductItemCard {...productProps} key={index} />
))
) : (
<p>No products!</p>
)}
</div>
</div>
);
}
}
const mapStateToProps = state => ({
products: selectProductItems(state),
isLoading: selectIsFetching(state),
color: selectColor(state),
searchInput: selectSearchInput(state),
family: selectFamily(state),
priceFrom: selectPriceFrom(state),
priceTo: selectPriceTo(state),
page: selectCurrentPage(state),
capacity: selectCapacity(state)
});
const mapDispatchToProps = dispatch => ({
fetchProducts: (series, queryStrings) =>
dispatch(fetchProductsStartAsync(series, queryStrings)),
resetFilterToDefault: () => dispatch(resetDefault())
});
export default compose(
connect(
mapStateToProps,
mapDispatchToProps
),
withRouter,
withSpinner
)(ProductsWrapper);
And the higher order component withSpinner:
import React from "react";
import Spinner from "../Spinner/Spinner";
const withSpinner = WrappedComponent => {
const enhancedComponent = ({ isLoading, ...otherProps }) => {
return isLoading ? <Spinner /> : <WrappedComponent {...otherProps} />;
};
return enhancedComponent;
};
export default withSpinner;
As I have noticed, you assign products = null in your default state, which is not a very good practice (imho). Why not assign products = [] to get rid of the error in the first place? Also might be useful to use PropTypes to make sure products is always of correct type...
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
I want to list posts in PostList.js component from JSON file
I use react-redux for state managment and redux-saga to get json file
My components are Post.js and PostList.js:
Post.js
const Post = ({ post }) => {
<li>
{post}
</li>
}
export default Post
PostList.js
class PostList extends React.Component {
componentDidMount() {
console.log('did mount');
this.props.fetchPosts();
}
render() {
return (
<div>
<ul>
{this.state.posts(post => (
<Post key={post.id} {...post} />
))}
</ul>
</div>
)
}
}
export default PostList
Reducer
export default (state = [], action) => {
switch (action.type) {
case "FETCH_POSTS":
return {
...state,
loading: true,
posts: []
}
case "FETCH_FAILD":
return {
...state,
loading: false,
posts: []
}
case "FETCH_SUCCESS":
return Object.assign({}, state, {
posts: action.posts
})
default:
return state;
}
}
Actions.js
export const fetchPosts = () => {
return {
type: 'FETCH_POSTS'
}
}
export const fetchSuccess = data => ({
type: "FETCH_SUCCESS",
posts: data
})
export const fetchFaild = () => {
return {
type: 'FETCH_FAILD'
}
}
GetPosts.js (Container)
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import PostList from '../components/PostList'
import { fetchPosts } from '../actions'
const mapStateToProps = state => ({ posts: state.posts });
const mapDispatchToProps = dispatch => bindActionCreators({fetchPosts}, dispatch);
const GetPosts = connect(
mapStateToProps,
mapDispatchToProps
)(PostList)
export default GetPosts
Saga.js
export function* fetchProducts() {
try {
console.log('saga')
const posts = yield call(api_fetchPost);
yield put({ type: "FETCH_SUCCESS", posts});
} catch (e) {
yield put({ type: "FETCH_FAILD", e});
return;
}
}
export function* watchFetchProducts() {
yield takeEvery("FETCH_POSTS", fetchProducts)
}
You are fetching posts from the state of your postlist component. Redux mapStateToProps map the redux state to connected component's props and not state
class PostList extends React.Component {
componentDidMount() {
console.log('did mount');
this.props.fetchPosts();
}
render() {
return (
<div>
<ul>
{this.props.posts && this.props.posts.map(post => {
return ( <Post key={post.id} {...post} /> );
})}
</ul>
</div>
)
}
}
export default PostList
Change this.state.posts to this.props.posts