I get my action called in Redux Dev Tools and even the new state, but in the actual Component props is undefined.
The component:
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getPromos } from '../../actions/promo';
import PropTypes from 'prop-types';
const Landing = ({ getPromos, data }) => {
useEffect(() => {
getPromos();
console.log(data) // ==>> "UNDEFINED"
}, []);
return (
<div>
<section className='landing'>
<div className='dark-overlay'>
<div className='landing-inner'>
<h1 className='x-large'> Developer Connector </h1>
<p className='lead'>
Create a developer profile/portfolio, share posts and get help
from other developers
</p>
<div className='buttons'>
<Link to='/register' className='btn btn-primary'>
Sign Up
</Link>
<Link to='/login' className='btn btn-light'>
Login
</Link>
</div>
</div>
</div>
</section>
</div>
);
};
Landing.propTypes = {
getPromos: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
data: state.data
});
export default connect(
mapStateToProps,
{ getPromos }
)(Landing);
Actions:
import axios from 'axios';
import { setAlert } from './alert';
import { GET_PROMOS, REGISTER_FAIL } from './types';
export const getPromos = () => async dispatch => {
try {
const res = await axios.get('/api/promo');
dispatch({
type: GET_PROMOS,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({ type: REGISTER_FAIL });
}
};
And reducer:
import { GET_PROMOS } from '../actions/types';
const initialState = {
data: null,
title: ''
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PROMOS:
return { ...state, data: payload };
default:
return state;
}
}
Like I said, in Redux Dev Tools I get my desired output. But for some reason I cant get to echo this state in the component. What im getting wrong? Can it be something about the hooks?
Thanks !
First thing that jumps at me is that you have a naming conflict with the getPromos in your component, it's defined in the imports as getPromos then it's destructured in the component as { getPromos } as well. I'm surprised you didn't get an error there for naming conflicts.
You will want to NOT destructure getPromos in the component and instead call it as (props) => { props.getPromos } to actually call the connected action creator instead of the unconnected one.
Second, Is that reducer the main root reducer? or is it nested in the root reducer? if the latter is true then in your mapStateToProps the data prop should be one level deeper, as in state: state.rootLevelState.data
(sorry can't ask questions in the comments due to reputation < 50)
enter image description here
Here's a screenshot of the redux dev tools
Related
I need a help to solve this error:
"useDispatch is called in function that is neither a React function component nor a custom React Hook function".
Explanation:
store.js and userSlice.js hold the definition of my Redux related things (rtk).
Auth.js is meant to hold functions to authenticate/logout and keep redux "user" storage updated. By now I have just the google auth, that is authenticated when I call redirectToGoogleSSO.
The authentication part is working flawless and i'm retrieving the user info correctly, but I'm having a hard time making it update the user store.
The dispatch(fetchAuthUser()) is where I get the error.
Sidebar.js is a navigation sidebar that will hold a menu to sign in/sign out and to access the profile.js (not implemented yet).
If I bring all the code from Auth to inside my Sidebar component, the authentication work and the redux store is filled, but I would like to keep things in the Auth.js so I can use that in other components and not just in the Sidebar.
//store.js:
import { configureStore } from '#reduxjs/toolkit';
import userReducer from './userSlice';
export default configureStore({
reducer: {
user: userReducer
}
});
//userSlice.js
import { createSlice } from '#reduxjs/toolkit';
import axios from "axios";
export const userSlice = createSlice({
name: 'user',
initialState: {
email: 'teste#123',
name: 'teste name',
picture: 'teste pic',
isAuthenticated: false
},
reducers: {
setUser (state, actions) {
return {...state,
email: actions.payload.email,
name: actions.payload.name,
picture: actions.payload.picture,
isAuthenticated: true
}
},
removeUser (state) {
return {...state, email: '', name: '', picture: '', isAuthenticated: false}
}
}
});
export function fetchAuthUser() {
return async dispatch => {
const response = await axios.get("/api/auth/user", {withCredentials: true}).catch((err) => {
console.log("Not properly authenticated");
dispatch(removeUser());
});
if (response && response.data) {
console.log("User: ", response.data);
dispatch(setUser(response.data));
}
}
};
export const { setUser, removeUser } = userSlice.actions;
export const selectUser = state => state.user;
export default userSlice.reducer;
//Auth.js
import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { fetchAuthUser } from '../../redux/userSlice';
export const AuthSuccess = () => {
useEffect(() => {
setTimeout(() => {
window.close();
},1000);
});
return <div>Thanks for loggin in!</div>
}
export const AuthFailure = () => {
useEffect(() => {
setTimeout(() => {
window.close();
},1000);
});
return <div>Failed to log in. Try again later.</div>
}
export const redirectToGoogleSSO = async() => {
const dispatch = useDispatch();
let timer = null;
const googleAuthURL = "http://localhost:5000/api/auth/google";
const newWindow = window.open(
googleAuthURL,
"_blank",
"toolbar=yes,scrollbars=yes,resizable=yes,top=200,left=500,width=400,height=600"
);
if (newWindow) {
timer = setInterval(() => {
if(newWindow.closed) {
console.log("You're authenticated");
dispatch(fetchAuthUser()); //<----- ERROR HERE ---->
if (timer) clearInterval(timer);
}
}, 500);
}
}
//Sidebar.js
import React from 'react';
import { Link } from 'react-router-dom';
import { redirectToGoogleSSO } from '../auth/Auth';
import { useSelector } from 'react-redux';
export const Sidebar = () => {
const handleSignIn = async() => {
redirectToGoogleSSO();
};
const {name,picture, isAuthenticated} = useSelector(state => state.user);
return (
<div id="sidenav" className="sidenav">
<div className="nav-menu">
<ul>
{
isAuthenticated
? <li>
<img className="avatar" alt="" src={picture} height="40" width="40"></img>
<Link to="/" className="user">{name}</Link>
<ul>
<li><Link to="/"><i className="pw-icon-export"/> logout</Link></li>
</ul>
</li>
: <li>
<Link to="/" className="login" onClick={handleSignIn}>
<i className="pw-icon-gplus"/>
Sign In / Sign Up
</Link>
</li>
}
</ul>
</div>
</div>
)
}
You only can use the useDispatch hook from a react component or from a custom hook, in your case, you should use store.dispatch(), try to do the following:
import { configureStore } from '#reduxjs/toolkit';
import userReducer from './userSlice';
// following the docs, they assign configureStore to a const
const store = configureStore({
reducer: {
user: userReducer
}
});
export default store;
Edit: i also noticed that you are trying to dispatch a function that is not an action, redux doesn't work like that, you should only dispatch the actions that you have defined in your reducer, otherwise your state will be inconsistent.
So first of all, move the fetchAuthUser to another file, like apiCalls.ts or anything else, it's just to avoid circular import from the store.js.
after this, call the store.dispatch on the fetchAuthUser:
// File with the fetch function
// Don't forget to change the path
import store from 'path/to/store.js'
export function fetchAuthUser() {
const response = await axios.get("/api/auth/user", {withCredentials: true}).catch((err) => {
console.log("Not properly authenticated");
store.dispatch(removeUser());
});
if (response && response.data) {
console.log("User: ", response.data);
store.dispatch(setUser(response.data));
}
};
In the Auth.js you don't have to call the dispatch, because you have already called it within your function.
export const redirectToGoogleSSO = async() => {
let timer = null;
const googleAuthURL = "http://localhost:5000/api/auth/google";
const newWindow = window.open(
googleAuthURL,
"_blank",
"toolbar=yes,scrollbars=yes,resizable=yes,top=200,left=500,width=400,height=600"
);
if (newWindow) {
timer = setInterval(() => {
if(newWindow.closed) {
console.log("You're authenticated");
// Just call the fetchAuthUser, you are already dispatching the state inside this function
await fetchAuthUser();
if (timer) clearInterval(timer);
}
}, 500);
}
}
So keep in mind that ever you need to use dispatch outside a react component or a custom hook, you must use the store.dispatch, otherwise it will not work, and don't forget to only dispatch actions to keep the state consistent. I suggest you to read the core concepts about redux, and also see this video to understand better how it works under the hoods. Hope i helped a bit!
Just as the error states, you are calling useDispatch in Auth.js-> redirectToGoogleSSO. This is neither a React Component nor a React Hook function. You need to call useDispatch in either of those. So you can:
Handle the redux part of the user information and the Google SSO part in a component by calling both useDispatch and redirectToGoogleSSO in handleSignIn itself (this is probably easier to implement right now, you just need to move the dispatch code from redirectToGoogleSSO to handleSignIn), or
turn redirectToGoogleSSO into a Hook you can call from within components.
I'm learning redux and I struggle to understand it.
I have a list of article and each articles contain title and url.
I would like to get the url of the current article and pass it to an other component for show the preview url
I'm trying to pass the url of current article when I click to the button to put on other component in Article.js but it's not working. it's only display all the url of all the article.
If someone can help me or explain me what I'm wrong it's could be very nice.
in actions.js
export const articleClick = url => ({
type: SHOW_ARTCILE,
payload: { url }
});
in redux, article.js
article: null
}
const articleReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SHOW_ARTCILE':
return [
...state,
{
url: action.url,
}
]
;
default:
return state;
}
}
export default articleReducer;
in Article.js
import { articleClick } from '../../redux/actions';
import { connect } from "react-redux";
class Article extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>{this.props.article.title}</p>
<div>
<p>Posted by : {this.props.article.by}</Posted>
<button onClick={() => this.props.articleClick(this.props.article.url), console.log(this.props.article.url)}>Visit website</button>
</div>
</div>
)
}
}
export default connect(
null,
{ articleClick }
)(Article);
in Preview.js
import { connect } from "react-redux";
import { articleClick } from '../../redux/actions';
class PreviewArticle extends Component {
render() {
return (
<p>
{this.url}
</p>
);
}
}
export default connect(
null,
{ articleClick }
)(PreviewArticle);
Let's try to optimize your react-redux flow here so we can explain things a bit easier.
First let's try to make your App component look like this:
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, combineReducers } from "redux";
import articles from "./reducers/articles";
import Articles from "./components/Articles";
import Preview from "./components/Preview";
const store = createStore(
combineReducers({
articles: articles
})
);
function App() {
return (
<Provider store={store}>
<div className="App">
<Articles />
<Preview />
</div>
</Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The main component displays the Articles component and Preview component. Additionally, we feed the redux-store to the Main component.
Let's take a look at all the aspects that comprise our redux-store.
reducers.js
const initialState = {
articles: [
{
title: "Corgi",
url: "https://www.akc.org/dog-breeds/cardigan-welsh-corgi/"
},
{
title: "Leopard Dog",
url: "https://www.akc.org/dog-breeds/catahoula-leopard-dog/"
}
],
article: {}
};
const articlesReducer = (state = initialState, action) => {
switch (action.type) {
case "SHOW_URL":
return {
...state,
article: action.payload
};
default:
return state;
}
};
export default articlesReducer;
In the reducer, we have a default state that holds an array of objects (articles) and a current article. We'll use both to populate the display of our Articles and Preview components. The only time our reducer will get updated is when an action with a type of "SHOW_URL" is dispatched, and thus we will update the current article.
See action-creator in actions.js:
export const showUrl = article => {
return {
type: "SHOW_URL",
payload: article
};
};
Now for our components, Articles, Article and Preview.
In Articles, we need to use mapStateToProps to get the list of articles available in our Redux-state. Then, we iterate over each article and render an individual Article component. Passing, the iterated article as a prop.
import React from "react";
import { connect } from "react-redux";
import Article from "./Article";
const Articles = ({ articles }) => {
return (
<div>
{articles.articles.map(article => {
return <Article article={article} />;
})}
</div>
);
};
const mapStateToProps = state => {
return {
articles: state.articles
};
};
export default connect(mapStateToProps)(Articles);
Now in each unique Article component, we use our action-creator, passing in the article we received as a prop to update the redux-state.
import React from "react";
import { connect } from "react-redux";
import { showUrl } from "../actions/articleActions";
const Article = ({ article, showUrl }) => {
return (
<div>
<button onClick={() => showUrl(article)}>{article.title}</button>
</div>
);
};
const mapDispatchToProps = dispatch => {
return {
showUrl: article => {
dispatch(showUrl(article));
}
};
};
export default connect(
null,
mapDispatchToProps
)(Article);
When the action completes, our reducer gets an updated state, which causes our Preview component to re-render and reflect that updated data.
import React from "react";
import { connect } from "react-redux";
const Preview = ({ articles }) => {
const thisArticle = articles.article;
return (
<div>
<h4>{thisArticle.title}</h4>
<a href={thisArticle.url}>Go To Link</a>
</div>
);
};
const mapStateToProps = state => {
return {
articles: state.articles
};
};
export default connect(mapStateToProps)(Preview);
I've created a sample codesandbox for you here for reference: https://codesandbox.io/s/simple-redux-with-dogs-s1v6h
I am fairly new to React and Redux and I have an issue with my component not updating on the final dispatch that updates a redux store. I am using a thunk to preload some data to drive various pieces of my site. I can see the thunk working and the state updating seemingly correctly but when the data fetch success dispatch happens, the component is not seeing a change in state and subsequently not re rendering. the interesting part is that the first dispatch which sets a loading flag is being seen by the component and it is reacting correctly. Here is my code:
actions
import { programsConstants } from '../constants';
import axios from 'axios'
export const programsActions = {
begin,
success,
error,
};
export const loadPrograms = () => dispatch => {
dispatch(programsActions.begin());
axios
.get('/programs/data')
.then((res) => {
dispatch(programsActions.success(res.data.results));
})
.catch((err) => {
dispatch(programsActions.error(err.message));
});
};
function begin() {
return {type:programsConstants.BEGIN};
}
function success(data) {
return {type:programsConstants.SUCCESS, payload: data};
}
function error(message) {
return {type:programsConstants.ERROR, payload:message};
}
reducers
import {programsConstants} from '../constants';
import React from "react";
const initialState = {
data: [],
loading: false,
error: null
};
export function programs(state = initialState, action) {
switch (action.type) {
case programsConstants.BEGIN:
return fetchPrograms(state);
case programsConstants.SUCCESS:
return populatePrograms(state, action);
case programsConstants.ERROR:
return fetchError(state, action);
case programsConstants.EXPANDED:
return programsExpanded(state, action);
default:
return state
}
}
function fetchPrograms(state = {}) {
return { ...state, data: [], loading: true, error: null };
}
function populatePrograms(state = {}, action) {
return { ...state, data: action.payload, loading: false, error: null };
}
function fetchError(state = {}, action) {
return { ...state, data: [], loading: false, error: action.payload };
}
component
import React from "react";
import { connect } from 'react-redux';
import { Route, Switch, Redirect } from "react-router-dom";
import { Header, Footer, Sidebar } from "../../components";
import dashboardRoutes from "../../routes/dashboard.jsx";
import Loading from "../../components/Loading/Loading";
import {loadPrograms} from "../../actions/programs.actions";
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(loadPrograms());
}
render() {
const { error, loading } = this.props;
if (loading) {
return <div><Loading loading={true} /></div>
}
if (error) {
return <div style={{ color: 'red' }}>ERROR: {error}</div>
}
return (
<div className="wrapper">
<Sidebar {...this.props} routes={dashboardRoutes} />
<div className="main-panel" ref="mainPanel">
<Header {...this.props} />
<Switch>
{dashboardRoutes.map((prop, key) => {
let Component = prop.component;
return (
<Route path={prop.path} component={props => <Component {...props} />} key={key} />
);
})}
</Switch>
<Footer fluid />
</div>
</div>
);
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error
});
export default connect(mapStateToProps)(Dashboard);
The component should receive updated props from the success dispatch and re render with the updated data. Currently the component only re renders on the begin dispatch and shows the loading component correctly but doesn't re render with the data is retrieved and updated to the state by the thunk.
I've researched this for a couple days and the generally accepted cause for the component not getting a state refresh is inadvertent state mutation rather than returning a new state. I don't think I'm mutating the state but perhaps I am.
Any help would much appreciated!
Update 1
As requested here's the code for creating the store and combining the reducers
store:
const loggerMiddleware = createLogger();
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(
thunk,
loggerMiddleware)
);
export const store = createStore(rootReducer, enhancer);
reducer combine:
import { combineReducers } from 'redux';
import { alert } from './alert.reducer';
import { programs } from './programs.reducer';
import { sidenav } from './sidenav.reducer';
const rootReducer = combineReducers({
programs,
sidenav,
alert
});
export default rootReducer;
The 2nd param is expected to be [preloadedState]:
export const store = createStore(rootReducer, {} , enhancer);
axios.get return a promise that you need to await for to get your data:
Try this:
export const loadPrograms = () => async (dispatch) => {
dispatch(programsActions.begin());
try {
const res = await axios.get('/programs/data');
const data = await res.data;
console.log('data recieved', data)
dispatch(programsActions.success(data.results));
} catch (error) {
dispatch(programsActions.error(error));
}
};
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
data: state.programs.data,
});
Action Call
import React from 'react';
import { connect } from 'react-redux';
import { loadPrograms } from '../../actions/programs.actions';
class Dashboard extends React.Component {
componentDidMount() {
// Try to call you action this way:
this.props.loadProgramsAction(); // <== Look at this
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
});
export default connect(
mapStateToProps,
{
loadProgramsAction: loadPrograms,
},
)(Dashboard);
After three days of research and refactoring, I finally figured out the problem and got it working. Turns out that the version of react-redux is was using (6.0.1) was the issue. Rolled back to 5.1.1 and everything worked flawlessly. Not sure if something is broken in 6.0.1 or if I was just using wrong.
I have a component that connects to a store and displays a child component like below:
render() {
return <div>
<div className="userBox">
<ProfilePhoto userid={this.props.id} />
</div>
<div className="nameTitleBox">
<div className="firstLastTitle">
<h1>{this.props.firstName} {this.props.lastName}</h1>
</div>
<IDBox userid={this.props.id} />
</div>
<div className="childcomponent">
<childComponent />
</div>
<div className="profileBox">
<EditInterests interestsList={this.props.interest} />
</div>
</div>
}
}
export default connect(
(state) => state.user,
UserState.actionCreators
)(User);
I want the child component to be a smart component that loads it's own data and controls everything itself. The code for it is pretty simple.
import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import { ApplicationState } from '../../store';
import { connect } from 'react-redux';
import * as ChildState from '../../store/childStore';
export class ChildComponent extends React.Component {
componentWillMount() {
this.props;
}
render() {
return (<div>
<div className="textCenter"><h2 id="sss">{this.props.text}</h2></div>
<div className="textRight">
<input type="button" className="button" value="Yes" /> <b className="textColor">No</b>
</div>
</div>
</div>
</div>)
}
}
const mapDispatchToProps = (dispatch) => {
return {
action: dispatch(ChildState.actionCreators.requestChildren())
}
}
export default connect(
mapDispatchToProps,
ChildState.actionCreators
)(ChildComponent);
this.props in the child component is always an empty object. Nothing from the child state is in there, the initial state, the actions, dispatch...anything. I've tried a few different things. ChildState loads fine if I actually load it in the parent. Don't know why it's not loading in the child and connecting the props.
Adding the store below:
import { Action, Reducer } from 'redux';
import { fetch, addTask } from 'domain-task';
import { AppThunkAction } from './';
export const actionCreators = {
requestChildren: () => (dispatch, getState) => {
let url = 'random';
var myheaders = new Headers();
myheaders.append("X-Requested-With", "XMLHttpRequest");
let fetchTask = fetch(url, {
headers: myheaders,
credentials: "same-origin"
})
.then(response => response.json())
.then(data => {
dispatch({ type: 'POST_ACTION', children: data });
});
addTask(fetchTask);
}
}
export const initialState = { ... };
export const reducer = (state = initialState, incomingAction) => {
const action = incomingAction;
switch (action.type) {
case 'REQUEST_ACTION':
return {
...
};
case 'POST_ACTION':
return {
...
};
default:
}
return state || initialState;
};
I believe the problem is in mapDispatchtoProps have you tried using bindActionCreators
bindActionCreators make sure action (ChildState.actionCreators.requestChildren) flows through the middleware if there is any and then to the reducers
import { bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
ChildState.actionCreators.requestChildren}, dispatch); }
export default connect(
ChildState.actionCreators,
mapDispatchToProps
)(ChildComponent);
This was happening because I was exporting both the child component and the connect function. I removed the export on the child component and its working now as expected.
Im not 100% sure if it is working correct, but it does noet give the result of the video course that I followed.
The renderPosts is just suppose to render the list, but instead it get a blank array the first time round. and when mapStateToProps is called the second time, the array is filled with the expected values.
it is as if the first time mapStateToProps is invoked, it did not pass through the action creator first or something.
COMPONENT
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
// console.log("renderPosts - this.props.posts",this.props.posts);
if(this.props.posts){
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
ACTION
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export const CREATE_POST = 'CREATE_POST';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=qwerty123';
export function fetchPosts(){
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
export function createPost(props) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return{
type: CREATE_POST,
payload: request
}
}
REDUCER
import { FETCH_POSTS } from '../actions/index';
const INITIAL_STATE = { postsList:[], post:null };
export default function(state = INITIAL_STATE, action){
console.log("action.type",action.type);
switch (action.type) {
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
default:
return state;
}
}
mapStateToProps is called twice. on the initial call the array is empty. on the second call I have my ten posts inside the array.
Problem is that it seems to want to render the first array and ignores the second
I have put an consol.log in the
renderPosts
and
mapStateToProps
and it renders as follows.
Console
any Ideas?
I think the error is coming from the way you handle the Promise. The first time you see the mapStateToProps in the console you can see you have no data so this is PENDING, the second is when it's FULFILLED. You need to find a way to handle this.
Example but not the best, I think you can just change you if statement.
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.props.posts !== [] this.renderPosts() : <h1>Loading...</h1>}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
The second one should be by changing the way you do the promise. A good library is redux-promise-middleware
This is a example of my app what I did.
Actions
export const reqAllGames = games => {
const promise = new Promise((resolve, reject) => {
request
.get(`${config.ROOT_URL}/${config.API_KEY}`)
.end((err, res) => {
if (err) {
reject(err);
} else {
resolve(res.body.top);
}
});
});
return {
type: types.RECEIVE_ALL_GAMES,
payload: promise
};
};
Reducer
import * as types from "../constants/";
const gameReducer = (games = { isFetched: false }, action) => {
switch (action.type) {
case `${types.RECEIVE_ALL_GAMES}_PENDING`:
return {};
case `${types.RECEIVE_ALL_GAMES}_FULFILLED`:
return {
games: action.payload,
err: null,
isFetched: true
};
case `${types.RECEIVE_ALL_GAMES}_REJECTED`:
return {
games: null,
err: action.payload,
isFetched: true
};
default:
return games;
}
};
export default gameReducer;
Component
const Games = ({ games, err, isFetched }) => {
if (!isFetched) {
return <LoadingCircular />;
}
else if (err === null) {
return (
<div>
<GamesList games={games} />
</div>
);
} else {
return <h1>Games not find!</h1>;
}
};
const mapStateToProps = (state) => state.games;
export default connect(mapStateToProps)(Games);
If you using react-router you can use the onEnter api and do the actions right here. With that you know your component gonna get the post. A good tutorial is this one from RallyCoding https://www.youtube.com/watch?v=JicUNpwLzLY
Hope that can help you
https://www.udemy.com/react-redux/learn/v4/questions/1693796
In your reducer you're assigning the list of posts to the key postsList.
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
We can confirm that they are properly being assumed to postsList by looking at the mapStateToProps console log you have in your screenshot.
Your mapStateToProps, however, is looking at the property state.posts.all
return {posts: state.posts.all}
The list of posts are not assigned to the all property, they are assigned to the postsList property. This is why you don't see the updated list of posts in your component. You'll need to update either the property the reducer is placing the list of posts on or update your mapStateToProps to pull the list of posts from the correct property.
-Stephen Grider