I am building a react app and implementing redux for data. When I navigate to a particular route, I want to dispatch the action to fetch the data from an external API and then once the data comes back, display the data for the user.
Store :
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import { syncHistoryWithStore } from 'react-router-redux';
import { browserHistory } from 'react-router';
import rootReducer from '../reducers/index';
const initialState = {
marvel :{
characters: []
}
};
const store = createStore(rootReducer, initialState, applyMiddleware(thunkMiddleware, createLogger()));
export const history = syncHistoryWithStore(browserHistory, store);
if (module.hot) {
module.hot.accept('../reducers/', () => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}
export default store;
reducer :
import * as constants from '../constants/constants';
const initialState = {
characters: [],
isFetching: false,
errorMessage: null
};
const marvelReducer = (state = initialState, action) => {
switch (action.type) {
case constants.FETCH_MARVEL :
return Object.assign({},state,{isFetching: true});
case constants.FETCH_MARVEL_SUCCESS :
return Object.assign({}. state,{
characters: [...action.response],
isFetching: false
});
case constants.FETCH_MARVEL_ERROR :
return Object.assign({}, state,{
isFetching: false,
errorMessage: action.message
});
default :
return state;
}
};
export default marvelReducer;
actions:
import 'whatwg-fetch';
import * as constants from '../constants/constants';
export const fetchMarvel = (dispatch) => {
const MARVEL_API = 'http://gateway.marvel.com:80/v1/public/characters?apikey=e542b1d89f93ed41b132eda89b9efb2c';
dispatch({
type: constants.FETCH_MARVEL
});
return fetch(MARVEL_API).then(
response => {
dispatch({
type: constants.FETCH_MARVEL_SUCCESS,
response
});
},
error => {
dispatch({
type: constants.FETCH_MARVEL_ERROR,
message: error.message || 'Something went wrong with fetchMarvel'
});
});
};
component :
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/actions';
import '../styles/homeStyles.css';
class Home extends Component {
render() {
const { characters, isFetching, errorMessage } = this.props;
return (
<div>
<h1>React Starter App</h1>
<h2>This is the home page</h2>
</div>
);
}
}
function mapStateToProps(state) {
return {
characters: state.characters,
isFetching: state.isFetching,
errorMessage: state.errorMessage
};
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actions, dispatch) };
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
I know I'm not currently displaying the props anywhere in the application, but first I am just trying to get them populated.
What step am I missing to dispatch the action so that I can populate the props from state?
You are not dispatching the action anywhere. So nothing happens.
You probably want to do this in a React lifecycle hook, for example:
class Home extends Component {
componentDidMount() {
this.props.actions.fetchMarvel();
}
render() {
const { characters, isFetching, errorMessage } = this.props;
return (
<div>
<h1>React Starter App</h1>
<h2>This is the home page</h2>
</div>
);
}
}
Related
I am simply trying to connect() my LoginPage (component) to my Redux Store and dispatch in action via a onClick (event). When I console.log(this.props) my dispatch handler login() isn't in the component's props.
GitHub Repo -- https://github.com/jdavis-software/demo.git
Question: Why isn't my Redux Store either connection or dispatching the actions?
LoginPage:
import React, { Component} from 'react';
import { connect } from 'react-redux';
export class LoginPage extends Component<any> {
render(){
console.log('props doesnt have contain - login(): ', this.props)
return (<button onClick={ () => '' }>Login</button>)
}
}
const mapProps = state => ({ user: state.user })
const dispatchProps = (dispatch) => {
return {
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
}
}
export default connect(mapProps,dispatchProps)(LoginPage)
Redux Configuration:
import { IStore, IUser } from '#interfaces';
import { createStore, combineReducers } from 'redux';
import ReduxPromise from 'redux-promise';
// reducers
import userReducer from './user.reducer';
// define the intial global store state
const initialState:IStore = {
user: {
isAuthenticated: false
}
}
const appReducer = combineReducers({user: userReducer})
export default createStore(appReducer,initialState);
User Reducer:
// initial state
const initalState:IUser = {
isAuthenticated: false
}
// reducer
const userReducer = (state:IUser = initalState, { type, payload}: IPayload): IUser => {
console.log('user reducer start', state)
switch (type) {
case 'USER_LOGGED_IN':
state = { ...state, isAuthenticated: payload }
break;
default:
return state;
}
return state;
};
export default userReducer;
Root Page:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
// styles
import './index.scss';
// pages
import { App } from '#pages';
// store
import store from './core/store/store';
render(
<Provider store={store}>
<App/>
</Provider>, document.getElementById('app')
);
I checked your code on git repository. I found out that you're exporting the named export
export class LoginPage
and the default export,
export default connect(mapProps,dispatchProps)(LoginPage)
But when you're accessing it, you're accessing it as
import { /*Other components*/ , LoginPage } from '#pages'
So it is actually taking the named exported component which is not connected to store.
I suggest you to import as
import LoginPage , { /*Other components*/ } from '#pages'
This might solve your problem.
Return statements are missing in the properties of connect.
const mapProps = state => { return {user: state.user} }
const dispatchProps = (dispatch) => {
return {
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
}
}
export default connect(mapProps,dispatchProps)(LoginPage)
Updated:
Please check Redux-dispatch
try:
import React, { Component} from 'react';
import { connect } from 'react-redux';
export class LoginPage extends Component<any> {
render(){
console.log('props doesnt contain - login(): ', this.props)
return (
<button onClick={ this.props.login }>Login</button>
)
}
}
const mapProps = state => ({ user: state.user })
const dispatchProps = (dispatch) => ({
login: () => dispatch({ type: 'USER_LOGGED_IN', payload: true})
})
export default connect(mapProps,dispatchProps)(LoginPage)
to return an object with Arrow Functions you need to wrap your {} with ()
I am new to react and redux, this is my first attempt at using a redux action to call an API with it returning a list of products which I can then add to the redux store for me to use in any component. So far the API call is working, and returns a list of products when I add a console.log in the then response, however when I use dispatch to call the next action which sets the type I receive the error "Unhandled Rejection (TypeError): dispatch is not a function".
Here is my Fetch.js file:
import axios from "axios";
import * as React from "react";
export function loadProducts() {
return dispatch => {
return axios.get(getApiHost() + 'rest/productComposite/loadProductsWithImages/' + getId() + '/ENV_ID').then((response) => {
dispatch(getProducts(response.data.productDtos));
})
}
}
export function getProducts(productDtos) {
return {
type: 'product',
productDtos: productDtos
}
}
function getId() {
return params().get('id');
}
function getEnv() {
let env = params().get('env');
if (!env) return 'prod';
return env;
}
function getApiHost() {
}
function params() {
return new URLSearchParams(window.location.search);
}
and my reducer.js file:
const initialState = {
loaded: 'false',
productDtos: []
}
const productReducer = (state = initialState, action) => {
switch (action.type) {
case 'product':
return {
...state,
loaded: 'true',
productDtos: action.productDtos
}
default:
return {
...state,
loaded: 'false',
productDtos: action.productDtos
};
}
}
export default productReducer;
and my index.js file:
(there is lots of messy code in this file which is why i am trying to convert the project into redux)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import * as serviceWorker from './serviceWorker';
import {createStore, compose, applyMiddleware } from 'redux';
import allReducers from './components/reducers'
import {Provider} from 'react-redux';
import thunk from "redux-thunk";
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
allReducers,
composeEnhancer(applyMiddleware(thunk)),
);
ReactDOM.render(
<React.StrictMode>
<Provider store = {store}>
<App/>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
and my app.js file:
import React from 'react';
import './App.css';
import Header from './Header';
import Footer from './Footer';
import PageHero from './PageHero';
import Products from './Products';
import ProductDetail from "./ProductDetail";
import Cart from "./Cart";
import ErrorBoundary from "./ErrorBoundary";
import PrivacyPolicy from "./PrivacyPolicy";
import LicenceAgreement from "./LicenceAgreement";
import {loadProducts} from "./Fetch";
import {connect} from "react-redux";
class App extends React.Component {
constructor(props) {
super(props);
this.cartProductsRef = React.createRef();
this.state = {
loadedShopConfig: false,
loadedLogo: false,
productView: false,
products: null,
logoUrl: null,
lAView: false,
pPView: false
};
this.onChange = this.onChange.bind(this)
this.changeBack = this.changeBack.bind(this)
this.addToCart = this.addToCart.bind(this)
this.lAViewChange = this.lAViewChange.bind(this)
this.pPViewChange = this.pPViewChange.bind(this)
}
params() {
return new URLSearchParams(window.location.search);
}
getId() {
return this.params().get('id');
}
getEnv() {
let env = this.params().get('env');
if (!env) return 'prod';
return env;
}
getApiHost() {
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({hasError: true});
// You can also log the error to an error reporting service
console.log('error', error);
console.log('info', info);
}
changeBack() {
this.setState({
productView: false,
lAView: false,
pPView: false
});
}
onChange(event) {
this.setState({productView: true});
this.setState({selectedProduct: event})
}
addToCart(product, quantity) {
console.log('cartProductsRef', this.cartProductsRef);
if (this.cartProductsRef.current) {
this.cartProductsRef.current.addToCart(product.entityInstanceId.id, quantity, product);
}
this.setState({})
}
lAViewChange() {
this.setState({lAView: true});
}
pPViewChange() {
this.setState({pPView: true});
}
loadShopDetails() {
this.setState({...this.state, isFetching: true});
fetch(this.getApiHost() + 'rest/shopComposite/' + this.getId() + '/ENV_ID')
.then((response) => response.json())
.then((responseJson) => {
console.log('Shop Details Function Results ', responseJson);
this.setState({
shopConfig: responseJson.shopConfig,
logoUrl: responseJson.logoUrl,
currencyCode: responseJson.currencyCode,
website: responseJson.website,
twitter: responseJson.twitter,
facebook: responseJson.facebook,
instagram: responseJson.instagram,
linkedIn: responseJson.linkedIn,
youTube: responseJson.youTube,
loadedShopConfig: true,
loadedLogo: true
});
})
this.setState({...this.state, isFetching: false});
}
componentDidMount() {
this.loadShopDetails();
this.props.dispatch(loadProducts());
}
render() {
let displayProduct;
const {lAView} = this.state;
const {pPView} = this.state;
const {productView} = this.state;
const {shopConfig} = this.state;
const {logoUrl} = this.state;
const {selectedProduct} = this.state;
if (productView && !lAView && !pPView) {
displayProduct = <ProductDetail
product={selectedProduct}
addToCart={this.addToCart}
/>;
} else if (!lAView && !pPView) {
displayProduct =
<Products
shopConfig={this.state.shopConfig}
productSelectedHandler={this.onChange}
/>;
}
return (
<div id="page">
<ErrorBoundary>
<p>{this.state.productView}</p>
<Header
logoUrl={this.state.logoUrl}
itemsInCart={this.state.itemsInCart}
changeBack={this.changeBack}
currencyCode={this.state.currencyCode}
/>
{!productView && !lAView && !pPView && this.state.loadedShopConfig ?
<PageHero shopConfig={this.state.shopConfig}/> : null}
{displayProduct}
<Cart id={this.getId()}
apiHost={this.getApiHost()}
ref={this.cartProductsRef}
currencyCode={this.state.currencyCode}
/>
{lAView ? <LicenceAgreement
shopConfig={this.state.shopConfig}
/> : null }
{pPView ? <PrivacyPolicy
shopConfig={this.state.shopConfig}
/> : null }
{this.state.loadedLogo ? <Footer
logoUrl={this.state.logoUrl}
lAChange={this.lAViewChange}
pPChange={this.pPViewChange}
twitter={this.state.twitter}
facebook={this.state.facebook}
instagram={this.state.instagram}
linkedIn={this.state.linkedIn}
youTube={this.state.youTube}
website={this.state.website}
/> : null}
</ErrorBoundary>
</div>
);
}
}
function mapDispatchToProps() {
return loadProducts()
}
export default connect(mapDispatchToProps)(App);
Thank you in advance for anyone who helps me, its probably a quick fix of something that I doing wrong, although I have read many articles and watched many videos and cannot find an immediate problem.
This is the way you dispatch an action in connected component. Notice that mapStateToProps is the first argument you pass into connect function, even if it returns an empty object:
import { connect } from 'react-redux'
import { loadProducts } from './path/to/actions'
class App extends React.Component {
componentDidMount() {
this.props.loadProducts();
}
render() { ... }
}
const mapStateToProps = () => ({});
const mapDispatchToProps = { loadProducts };
export default connect(mapStateToProps, mapDispatchToProps)(App);
Probably, you didn’t connect loadProducts
i used react 16+ and redux get jsonplaceholder fake data to assign posts state but not working. can't assign the state. how can i assign json values into state using concat method. i check lifecycle methods also but can't get the answer.
Reducer
import * as actiontypes from './actions';
import axios from 'axios';
const initalstate = {
counter: 0,
posts: []
};
const reducer = (state = initalstate, action ) => {
switch (action.type) {
case actiontypes.actionFetchPost:
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
return {
...state,
posts: state.posts.concat(res.data)
}
});
break;
default :
return state;
}
};
export default reducer;
Redux reducers must be pure functions, it means they should not contain any side effects like calling api.
You need to call api in action creators using redux-thunk package.
Codesandbox
An example action creator:
import {
FETCH_POSTS_STARTED,
FETCH_POSTS_FAILURE,
FETCH_POSTS_SUCCESS
} from "./actionTypes";
import axios from "axios";
export const fetchPosts = () => {
return dispatch => {
dispatch(fetchPostsStarted());
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then(res => {
dispatch(fetchPostsSuccess(res.data));
})
.catch(err => {
dispatch(fetchPostsFailed(err.message));
});
};
};
const fetchPostsStarted = () => {
return {
type: FETCH_POSTS_STARTED,
payload: {
isLoading: true
}
};
};
const fetchPostsSuccess = posts => {
return {
type: FETCH_POSTS_SUCCESS,
payload: {
posts
}
};
};
const fetchPostsFailed = error => {
return {
type: FETCH_POSTS_FAILURE,
payload: {
error
}
};
};
And reducer file:
import {
FETCH_POSTS_STARTED,
FETCH_POSTS_SUCCESS,
FETCH_POSTS_FAILURE
} from "../actions/actionTypes";
const initialState = {
posts: [],
loading: false,
error: null
};
export default function(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS_STARTED:
return {
...state,
loading: true
};
case FETCH_POSTS_SUCCESS:
return {
...state,
loading: false,
error: null,
posts: action.payload.posts
};
case FETCH_POSTS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error
};
default:
return state;
}
}
In store we use redux-thunk like this:
import { createStore, compose, applyMiddleware, combineReducers } from "redux";
import reduxThunk from "redux-thunk";
import postsReducers from "./reducers/postsReducers";
const rootReducer = combineReducers({
posts: postsReducers
});
const store = createStore(rootReducer, compose(applyMiddleware(reduxThunk)));
export default store;
Posts component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchPosts } from "./store/actions/postsActions";
class Posts extends Component {
componentDidMount() {
this.props.fetchPosts();
}
render() {
const { posts, loading, error } = this.props;
return (
<div>
{loading && <div>LOADING...</div>}
{error && <div>{error}</div>}
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
}
const mapStateToProps = state => {
const { posts, loading, error } = state.posts;
return {
posts,
loading,
error
};
};
export default connect(
mapStateToProps,
{
fetchPosts
}
)(Posts);
Index.js
import ReactDOM from "react-dom";
import store from "./store/store";
import { Provider } from "react-redux";
import Posts from "./Posts";
function App() {
return (
<div className="App">
<Posts />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
Do api call in action and return promise, use redux-thunk and redux-promise-middleware:
export const myApiCall = (args1, arg2) => async (dispatch, getState) => {
const payload = fetch({ ...config });
return dispatch({ type: 'MY_API_CALL', payload });
}
Then in reducer will have to handle two results:
MY_API_CALL_FULFILLED and MY_API_CALL_REJECTED
I want to get a list of repositories from GitHub's API using redux in reactjs, but I get this error:
./src/components/layouts/page/index.js Line 14: Parsing error:
Unexpected token
function mapDispatchToProps(dispatch) {
^
return {
fetchRepos: function() {
dispatch(fetchRepos());
These are my files:
actions.js
export function fetchRepos() {
return function(dispatch) {
dispatch({
type: 'FETCH_REPOS_REQUEST'
});
return fetch('curl https://api.github.com/search/repositories?q=sort=stars&order=desc')
.then(response => response.json().then(body => ({ response, body })))
.then(({ response, body }) => {
if (!response.ok) {
dispatch({
type: 'FETCH_REPOS_FAILURE',
error: body.error
});
} else {
dispatch({
type: 'FETCH_REPOS_SUCCESS',
repos: body.repos
});
}
}
);
}
}
app.js
import React from 'react';
import './App.css';
import Page from './components/layouts/page/index';
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
function App() {
return (
<Provider store={store}>
<Page />
</Provider>
);
}
export default App;
reducers/index.js
import { combineReducers } from 'redux';
import repos from './reducers';
const rootReducer = combineReducers({
repos,
});
export default rootReducer;
reducers/reducers.js
import { combineReducers } from 'redux';
const INITIAL_STATE = {
items: [],
isFetching: false,
error: undefined
};
function reposReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case 'FETCH_REPOS_REQUEST':
return Object.assign({}, state, {
isFetching: true
});
case 'FETCH_REPOS_SUCCESS':
return Object.assign({}, state, {
isFetching: false,
repos: action.repos
});
case 'FETCH_REPOS_FAILURE':
return Object.assign({}, state, {
isFetching: false,
error: action.error
});
default:
return state;
}
}
export default combineReducers({
repos: reposReducer
});
Page.js
import React, { Component } from 'react';
import List from '../list/index.js';
import { connect } from 'react-redux';
import { fetchRepos } from '../../../actions/actions';
import './page.scss';
class Page extends Component {
componentDidMount() {
this.props.fetchRepos();
}
function mapDispatchToProps(dispatch) {
return {
fetchRepos: function() {
dispatch(fetchRepos());
}
};
}
function mapStateToProps(state) {
return {
repos: state.repos
};
}
render() {
return <List items={this.props.repos}/>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Page);
Your mapDispatchToProps and mapStateToProps need to be defined outside of your component in order to be used by connect:
class Page extends Component {
componentDidMount() {
this.props.fetchRepos();
}
render() {
const { repos } = this.props;
return <List items={repos} />;
}
}
const mapDispatchToProps = dispatch => ({
fetchRepos: () => dispatch(fetchRepos())
});
const mapStateToProps = state => ({
repos: state.repos
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Page);
For more information refer to this.
I want my firebase UI auth to be checked in multiple containers and also redirect to the main page after the authentication is done.
When authentication is done I can see the actions updating and the reducer updating - but then mapstatetoprops does not do anything with the new reducer state
Loginpage that received props and based on that renders
import React from 'react';
import { bindActionCreators } from 'redux'
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';
import { Redirect } from 'react-router-dom'
import firebase from 'firebase';
import { connect } from 'react-redux';
import { signIn, signOut } from '../reducer/actions'
import { auth } from '../firebase'
class LoginPage extends React.PureComponent {
// Configure FirebaseUI.
uiConfig = {'FirebaseUI Config'}
componentDidMount = () => {
auth.onAuthStateChanged((user) => { // gets user object on authentication
console.log('OnAuthStateChanged', user)
console.log('Check If Props Change in AuthChange', this.props.isauthed)
if (user) {
this.props.signIn(user)
} else {
this.props.signOut(user)
}
});
}
render() {
console.log('Check If Props Change in Render', this.props.isauthed)
if (!this.props.isauthed) {
return (
<div>
<h1>My App</h1>
<p>Please sign-in:</p>
<StyledFirebaseAuth uiConfig={this.uiConfig} firebaseAuth={firebase.auth()} />
</div>
);
}
return (
<Redirect to='/' />
)
}
}
export default (LoginPage);
JS that should dispatch and update the props
import { connect } from 'react-redux';
import { signIn, signOut } from '../reducer/actions'
import { bindActionCreators } from 'redux'
import LoginPage from './LoginPage';
const mapStateToProps = (state) => {
console.log('LOGINmapstatetoprops', state.Authed)
return {
isauthed: state.Authed.isauthed,
}
}
const mapDispatchToProps = (dispatch) => {
console.log('LOGINmapDISPATCHoprops')
return bindActionCreators({signIn,signOut},dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(LoginPage);
The reducer
import LoginPage from '../components/LoginPage';
import firebase from 'firebase';
const initialState = {
isauthed: false,
error: ''
}
const AuthReducer = (state = initialState, action) => {
console.log('this is an action',action)
switch (action.type) {
case 'IsSignedIn':
return state = [
...state,
{
isauthed: action.payload
}
]
break;
case 'IsNotSignedIn':
return state= [
...state,{
isauthed: action.payload
}]
break;
default: return state
}
}
export default AuthReducer;
This is the actions file
export const signIn = (user) => {
console.log('this is from actions',user)
return {
type: 'isSignedIn',
payload: true
}
}
export const signOut = (user) => {
console.log(user)
return {
type: 'isNotSignedIn',
payload: false
}
}
Any reason why Mapstatetoprops is idle?
I do not think a simple scenario like this needs componentwillreceiveprops method
there is a problem in the reducer, you should return an object and not an array, change to :
case 'IsSignedIn':
return {
...state,
isauthed: action.payload
}