I'm doing an API call to my server using Redux, but I'm unsure what the "best practices" are for doing so.
When simply using React, one common way of doing API calls is that you create a folder called for example hooks, where you create your own custom hooks to make the API calls that you want.
However, when it comes to using Redux with React, it is not really clear to me what the best method of structuring your client and API calls are, in order to make it more readable and easy to scale in the future.
Questions
So, what are the best practises for making API calls with Redux in React to increase readability and scaleability?
Second question, what are the best practises to use API calls in a functional component in React with React Redux?
Here below are the current structure of a project, where I make a simple GET request using Redux to the server, which respons with the JSON Placeholder /post.
Starting off with the file structure, to give a better understanding of where everything is located.
Files related to the API call
index.js (root)
import App from "./App";
import reducers from "./reducers";
ReactDOM.render(
<Provider store={createStore(reducers, applyMiddleware(thunk))}>
<App />
</Provider>,
document.querySelector("#root")
);
index.js (reducers)
import postReducer from "./postReducer";
export default combineReducers({
posts: postReducer,
});
postReducer.js (reducers)
import _ from "lodash";
import { GET_POSTS } from "../actions/types";
function postReducer(state = {}, action) {
switch (action.type) {
case GET_POSTS:
return { ...state, ..._.mapKeys(action.payload, "id") };
default:
return state;
}
}
export default postReducer;
requestPosts.js (apis)
import axios from "axios";
const API_URL = "http://localhost:8000";
async function httpGetAllPosts() {
const response = await axios.get(`${API_URL}/posts`);
return response.data;
}
export { httpGetAllPosts};
types.js (actions)
export const GET_POSTS = "GET_POSTS";
index.js (actions)
import { httpGetAllPosts } from "../apis/requestPosts";
import { GET_POSTS } from "./types";
const getAllPosts = () => async (dispatch) => {
const response = await httpGetAllPosts();
dispatch({ type: GET_POSTS, payload: response });
};
export { getAllPosts };
Moving on to the second question. What are the best practises to use API calls in a functional component in React with React Redux?
Here is how I used it in a functional component. Is this the ideal way of doing it? I think the useEffect looks a bit weird, when all it does is call a function and has a dependancy related to that function.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { getAllPosts } from "../actions";
import { Card } from "react-bootstrap";
const Posts = ({ getAllPosts, posts }) => {
useEffect(() => {
getAllPosts();
}, [getAllPosts]);
return (
<div>
{posts.map((post) => {
return (
<Card style={{ width: "45rem", marginBottom: "1rem" }} key={post.id}>
<Card.Body>
<Card.Title>{post.title}</Card.Title>
<Card.Text>{post.body}</Card.Text>
</Card.Body>
</Card>
);
})}
</div>
);
};
const mapStateToProps = (state, ownProps) => {
return { posts: Object.values(state.posts) };
};
export default connect(mapStateToProps, { getAllPosts })(Posts);
Related
I am using redux with redux-thunk middleware. The function in question makes a GET request to an API and upon response (.then()) dispatches the res to my redux store via an action.
For some reason when I pass dispatch to the parent function the function never runs. When I remove dispatch the parent function does run...(???) I have multiple other components within the same app that follow this exact same pattern successfully. For some reason this particular component is behaving in this strange way although i've triple checked and the boilerplate is all the same.
Here is my store.jsx:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from '../reducers/root_reducer'
const configureStore = (preloadedState = {}) =>
createStore(
rootReducer,
preloadedState,
applyMiddleware(thunk, logger)
);
export default configureStore;
my actions my_team_actions.js:
import * as APIUtil from '../util/api/my_team_api_util';
export const RECEIVE_ORG_SURVEY = "RECEIVE_ORG_SURVEY"
export const receiveOrgSurvey = survey => ({
type: RECEIVE_ORG_SURVEY,
survey
});
export const getOrganizationSurvey = () => dispatch => {
debugger
APIUtil.getOrgSurvey()
.then((res) => {
debugger
dispatch(receiveOrgSurvey(res))
})
.catch(err => console.log(err))
}
my API call my_team_api_util.js:
import axios from 'axios';
export const getOrgSurvey = () => {
return axios.get(`/api/mongo/organizations/test`)
}
component container my_team_container.jsx:
import { connect } from 'react-redux';
import MyTeam from './my_team';
import { getOrganizationSurvey } from '../../actions/my_team_actions';
const mSTP = state => {
return {
user: state.session.user,
};
};
const mDTP = dispatch => {
return {
getSurvey: () => getOrganizationSurvey(),
};
};
export default connect(mSTP, mDTP)(MyTeam);
component my_team.jsx:
import React from 'react';
class MyTeam extends React.Component {
constructor(props) {
super(props)
this.createTeam = this.createTeam.bind(this);
}
createTeam() {
this.props.getSurvey();
}
render() {
return (
<div className="my-team-frame frame">
<div className="my-team-container">
<div className="contact-data-container">
<div className="contact-data-header header">Contact a Data Scientist</div>
</div>
<div className="myteam" onClick={this.createTeam}>BUTTON</div>
</div>
</div>
)
}
}
export default MyTeam;
On the client side the my_team component renders fine and when I click the button which calls the function which will eventually dispatch my action it only seems to run when dispatch is NOT included in getOrganizationSurvey() in my_team_actions.js i.e. I hit both debuggers (and the second one with a correct res object). When dispatch is included (as shown in the snippet above) I don't hit either debuggers nor are any errors thrown.
I'm really scratching my head on this, any input is appreciated!
Thanks,
Ara
God I am a moron... XD
I said I triple checked... I should have checked 4 times! The issue was in my components container my_team_container.jsx I simply forgot to pass dispatch in the map dispatch to props object!
I fixed it by adding dispatch to the getSurvey callback...
my_team_container.jsx
import { connect } from 'react-redux';
import MyTeam from './my_team';
import { getOrganizationSurvey } from '../../actions/my_team_actions';
const mSTP = state => {
return {
user: state.session.user,
};
};
const mDTP = dispatch => {
return {
getSurvey: () => dispatch(getOrganizationSurvey()),
};
};
export default connect(mSTP, mDTP)(MyTeam);
it's funny how you can spend 2 hours on a problem, think it's hopeless and then as soon as you ask for help take another look at it and the solution just stares right back at you 😂
I'm a bit confused and would love an answer that will help me to clear my thoughts.
Let's say I have a backend (nodejs, express etc..) where I store my users and their data, and sometimes I wanna fetch data from the backend, such as the user info after he logs in, or a list of products and save them in the state.
My approach so far and what I've seen, I fetch the data before the component loads and dispatch an action with the data from the response.
But I recently started digging a bit about this and I saw react-thunk library which I knew earlier and started to wonder if what is the best practice of fetching from backend/API?
Has React Hooks change anything about this topic? Is it important to know this?
I feel a bit dumb but couldn't find an article or video that talks exactly about this topic :)
To do this best practice, use the following method:
I used some packages and patterns for best practice:
redux-logger for log actions and states in console of browser.
reselect Selectors can compute derived data, allowing Redux to
store the minimal possible state and etc.
redux-thunk Thunks are the recommended middleware for basic
Redux side effects logic, including complex synchronous logic that
needs access to the store, and simple async logic like AJAX requests
and etc.
axios for work with api (Promise based HTTP client for the
browser and node.js)
create a directory by name redux or any name of you like in src folder and
then create two files store.js and rootReducer.js in redux directory. We assume fetch products from API.
To do this:
Create a new directory by name product in redux directory and then
create four files by names product.types.js, product.actions.js,
product.reducer.js, product.selector.js in redux/product directory
The structure of the project should be as follows
...
src
App.js
redux
product
product.types.js
product.actions.js
product.reducer.js
rootReducer.js
store.js
Index.js
package.json
...
store.js
In this file we do the redux configuration
// redux/store.js:
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import rootReducer from "./root-reducer";
const middlewares = [logger, thunk];
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
rootReducer.js
The combineReducers helper function turns an object whose values are
different reducing functions into a single reducing function you can
pass to createStore.
// redux/rootReducer.js
import { combineReducers } from "redux";
import productReducer from "./product/product.reducer";
const rootReducer = combineReducers({
shop: productReducer,
});
export default rootReducer;
product.types.js
In this file we define constants for manage types of actions.
export const ShopActionTypes = {
FETCH_PRODUCTS_START: "FETCH_PRODUCTS_START",
FETCH_PRODUCTS_SUCCESS: "FETCH_PRODUCTS_SUCCESS",
FETCH_PRODUCTS_FAILURE: "FETCH_PRODUCTS_FAILURE"
};
product.actions.js
In this file we create action creators for handle actions.
// redux/product/product.actions.js
import { ShopActionTypes } from "./product.types";
import axios from "axios";
export const fetchProductsStart = () => ({
type: ShopActionTypes.FETCH_PRODUCTS_START
});
export const fetchProductsSuccess = products => ({
type: ShopActionTypes.FETCH_PRODUCTS_SUCCESS,
payload: products
});
export const fetchProductsFailure = error => ({
type: ShopActionTypes.FETCH_PRODUCTS_FAILURE,
payload: error
});
export const fetchProductsStartAsync = () => {
return dispatch => {
dispatch(fetchProductsStart());
axios
.get(url)
.then(response => dispatch(fetchProductsSuccess(response.data.data)))
.catch(error => dispatch(fetchProductsFailure(error)));
};
};
product.reducer.js
In this file we create productReducer function for handle actions.
import { ShopActionTypes } from "./product.types";
const INITIAL_STATE = {
products: [],
isFetching: false,
errorMessage: undefined,
};
const productReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ShopActionTypes.FETCH_PRODUCTS_START:
return {
...state,
isFetching: true
};
case ShopActionTypes.FETCH_PRODUCTS_SUCCESS:
return {
...state,
products: action.payload,
isFetching: false
};
case ShopActionTypes.FETCH_PRODUCTS_FAILURE:
return {
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return state;
}
};
export default productReducer;
product.selector.js
In this file we select products and isFetching from shop state.
import { createSelector } from "reselect";
const selectShop = state => state.shop;
export const selectProducts = createSelector(
[selectShop],
shop => shop.products
);
export const selectIsProductsFetching = createSelector(
[selectShop],
shop => shop.isFetching
);
Index.js
In this file wrapped whole app and components with Provider for access child components to the store and states.
// src/Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js class component
In this file we do connect to the store and states with class component
// src/App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
class App extends Component {
componentDidMount() {
const { fetchProductsStartAsync } = this.props;
fetchProductsStartAsync();
}
render() {
const { products, isProductsFetching } = this.props;
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
or App.js with functional component ( useEffect hook )
In this file we do connect to the store and states with functional component
// src/App.js
import React, { Component, useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectIsProductsFetching,
selectProducts
} from "./redux/product/product.selectors";
import { fetchProductsStartAsync } from "./redux/product/product.actions";
const App = ({ fetchProductsStartAsync, products, isProductsFetching}) => {
useEffect(() => {
fetchProductsStartAsync();
},[]);
console.log('products', products);
console.log('isProductsFetching', isProductsFetching);
return (
<div className="App">Please see console in browser</div>
);
}
const mapStateToProps = createStructuredSelector({
products: selectProducts,
isProductsFetching: selectIsProductsFetching,
});
const mapDispatchToProps = dispatch => ({
fetchProductsStartAsync: () => dispatch(fetchProductsStartAsync())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Okay, caveat is that I'm very very new to redux. I'm doing a course on it atm and I'm trying to step outside the box a little and generate a fairly standard website using the wordpress API and Redux. I appreciate that redux is generally meant for larger things but this seems like a useful first step in the learning process.
I have a series of components which list out posts, pages and different types of custom posts taken from the wordpress API and I navigate between these using react-router-dom.
The problem is that every time I go back to a component/view the list of posts or pages is rendered again so, for example, the first time I go there the list might be: test post 1, test post 2, the second time it would be: test post 1, test post 2, test post 1, test post 2, the third time: test post 1, test post 2, test post 1, test post 2, test post 1, test post 2 etc etc etc.
The reason for this is obvious, each time the component is rendered the data gets pulled from the store and rendered, however, as the entire app doesn't rerender as it would be with plain old reactjs, it doesn't cleared.
My question, of course is what's the best way of going about fixing this. I've read some kind of related posts which advise attaching some kind of condition to the component to check whether the data is already present but I've no idea how to do this and can't find out how. My attempts haven't worked because it seems that any var returned from componentDidMount is not seen in the render method.
Thanks in advance.
Code is below:
src/index.js
import React from "react";
import { BrowserRouter as Router } from 'react-router-dom';
import { render } from "react-dom";
import { Provider } from "react-redux";
import store from "./js/store/index";
import App from "./js/components/App";
render(
<Router>
<Provider store={store}>
<App />
</Provider>
</Router>,
document.getElementById("root")
);
src/js/index.js
import store from "../js/store/index";
window.store = store;
src/js/store/index.js
import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "../reducers/index";
import thunk from "redux-thunk";
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
storeEnhancers(applyMiddleware(thunk))
);
export default store;
src/js/reducers/index.js
import { POSTS_LOADED } from "../constants/action-types";
import { PAGES_LOADED } from "../constants/action-types";
const initialState = {
posts: [],
pages: [],
banner_slides: [],
portfolio_items: []
};
function rootReducer(state = initialState, action) {
switch (action.type) {
case 'POSTS_LOADED':
return Object.assign({}, state, {
posts: state.posts.concat(action.payload)
});
case 'PAGES_LOADED':
return Object.assign({}, state, {
pages: state.pages.concat(action.payload)
});
default:
return state;
}
}
export default rootReducer;
src/js/actions/index.js
export function getWordpress(endpoint) {
return function(dispatch) {
return fetch("http://localhost/all_projects/react-wpapi/my_portfolio_site/wordpress/wp-json/wp/v2/" + endpoint )
.then(response => response.json())
.then(json => {
dispatch({ type: endpoint.toUpperCase() + "_LOADED", payload: json });
});
};
}
src/js/constants/action-types.js
export const ADD_ARTICLE = "ADD_ARTICLE";
export const POSTS_LOADED = "POSTS_LOADED";
export const PAGES_LOADED = "PAGES_LOADED";
src/js/components/app.js
import React from "react";
import { Route, Switch, Redirect } from 'react-router-dom';
import Header from "./Header/Header";
import Posts from "./Posts";
import Pages from "./Pages";
import BannerSlides from "./BannerSlides";
import PortfolioItems from "./PortfolioItems";
const App = () => (
<div>
<Header />
<Route render = {({ location }) => (
<Switch location={location}>
<Route
exact path="/posts"
component={Posts}
/>
<Route
exact path="/pages"
component={Pages}
/>
</Switch>
)} />
</div>
);
export default App;
src/js/components/Posts.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { getWordpress } from "../actions/index";
export class Posts extends Component {
componentDidMount() {
this.props.getWordpress('posts');
let test = 1;
return test;
}
render() {
console.log("test: ", test); // not defined
if (test !== 1) {
return (
<ul>
{this.props.posts.map(item => (
<li key={item.id}>{item.title.rendered}</li>
))}
</ul>
);
}
}
}
function mapStateToProps(state) {
return {
posts: state.posts.slice(0, 10)
};
}
export default connect(
mapStateToProps,
{ getWordpress }
)(Posts);
The problem was that, every time you were fetching data, you were adding it to previous data in the array. That's why it was duplicating over time. Just assign instead of adding it in your reducer
function rootReducer(state = initialState, action) {
switch (action.type) {
case 'POSTS_LOADED':
return {
...state,
posts: action.payload
};
case 'PAGES_LOADED':
return {
...state,
pages: action.payload
};
default:
return state;
}
}
Hope it helps :)
If I'm understanding, you want to only fetch initial posts on first mount instead of every time the component is mounted?
In src/js/components/Posts.js you can check if any posts are stored in Redux before fetching inside the CDM lifecycle method. Eg.
componentDidMount() {
// access props.posts which you set inside mapDispatchToProps
if (this.props.posts.length === 0) {
this.props.getWordpress('posts');
}
}
If you are okay with duplicate API calls on every mount, and you are ok with fetching all the posts at once, you can adjust your reducer to overwrite the posts array instead of concat. But overwriting it assumes you want to load all the posts in 1 API call, instead of loading say 25 posts per page or having a 'Load more posts' button.
You need to check your state before calling fetch. I like to put mst of my logic in the redux part of the application (fat action creators) and use my react components only for rendering the current state. I would recommend something like this:
export function getWordpress(endpoint) {
return function(dispatch, getState) {
const currentState = getState();
if (currentState.posts && currentState.posts.length) {
// already initialized, can just return current state
return currentState.posts;
}
return fetch("http://localhost/all_projects/react-wpapi/my_portfolio_site/wordpress/wp-json/wp/v2/" + endpoint )
.then(response => response.json())
.then(json => {
dispatch({ type: endpoint.toUpperCase() + "_LOADED", payload: json });
});
};
}
Later you could separate the logic if posts are initialized into a selector and add some additional layers (like if posts are stale). This way your 'business' logic is easily testabale and separate from your UI.
Hope this helps :)
It is my first application using react context with hooks instead of react-redux and would like to get help of the structure of the application.
(I'm NOT using react-redux or redux-saga libraries.)
Context
const AppContext = createContext({
client,
user,
});
One of actions example
export const userActions = (state, dispatch) => {
function getUsers() {
dispatch({ type: types.GET_USERS });
axios
.get("api address")
.then(function(response) {
dispatch({ type: types.GOT_USERS, payload: response.data });
})
.catch(function(error) {
// handle error
});
}
return {
getUsers,
};
};
Reducer (index.js): I used combineReducer function code from the redux library
const AppReducer = combineReducers({
client: clientReducer,
user: userReducer,
});
Root.js
import React, { useContext, useReducer } from "react";
import AppContext from "./context";
import AppReducer from "./reducers";
import { clientActions } from "./actions/clientActions";
import { userActions } from "./actions/userActions";
import App from "./App";
const Root = () => {
const initialState = useContext(AppContext);
const [state, dispatch] = useReducer(AppReducer, initialState);
const clientDispatch = clientActions(state, dispatch);
const userDispatch = userActions(state, dispatch);
return (
<AppContext.Provider
value={{
clientState: state.client,
userState: state.user,
clientDispatch,
userDispatch,
}}
>
<App />
</AppContext.Provider>
);
};
export default Root;
So, whenever the component wants to access the context store or dispatch an action, this is how I do from the component :
import React, { useContext } from "react";
import ListMenu from "../common/ListMenu";
import List from "./List";
import AppContext from "../../context";
import Frame from "../common/Frame";
const Example = props => {
const { match, history } = props;
const { userState, userDispatch } = useContext(AppContext);
// Push to user detail route /user/userId
const selectUserList = userId => {
history.push(`/user/${userId}`);
userDispatch.clearTabValue(true);
};
return (
<Frame>
<ListMenu
dataList={userState.users}
selectDataList={selectUserList}
/>
<List />
</Frame>
);
};
export default Example;
The problem I faced now is that whenever I dispatch an action or try to access to the context store, the all components are re-rendered since the context provider is wrapping entire app.
I was wondering how to fix this entire re-rendering issue (if it is possible to still use my action/reducer folder structure).
Also, I'm fetching data from the action, but I would like to separate this from the action file as well like how we do on redux-saga structure. I was wondering if anybody know how to separate this without using redux/redux-saga.
Thanks and please let me know if you need any code/file to check.
I once had this re-rendering issue and I found this info on the official website:
https://reactjs.org/docs/context.html#caveats
May it will help you too
This effect (updating components on context update) is described in official documentation.
A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization
Possible solutions to this also described
I see universal solution is to useMemo
For example
const Example = props => {
const { match, history } = props;
const { userState, userDispatch } = useContext(AppContext);
// Push to user detail route /user/userId
const selectUserList = userId => {
history.push(`/user/${userId}`);
userDispatch.clearTabValue(true);
};
const users = userState.users;
return useMemo(() => {
return <Frame>
<ListMenu
dataList={users}
selectDataList={selectUserList}
/>
<List />
</Frame>
}, [users, selectUserList]); // Here are variables that component depends on
};
I also may recommend you to completly switch to Redux. You're almost there with using combineReducers and dispatch. React-redux now exposes useDispatch and useSelector hooks, so you can make your code very close to what you're doing now (replace useContext with useSelector and useReducer with useDispatch. It will require minor changes to arguments)
So I'm completely confused on how to integrate the Container and Component Pattern. I've been reviewing examples all morning and nothing seems to be clicking. How I have been worked with React previously on my first project was fetch the data within my view components and then pass that data down as props using the #connect which works, but in an "automagically" way to me at this time.
import React;
...
import {action} from 'path/to/action.js';
#connect((store) => {return{ key: store.property}});
export class Component{
componentWillMount(){
this.props.dispatch(action());
}
}
As I'm working more with React I want to learn the more "correct" way of building out with Redux and understand on a deeper level what is happening.
What I have setup is
index.jsx (This renders all of my HOCs)
|
App.jsx (Container)
|
Auth.jsx (Component)
|
Layout.jsx (Component) - Contains app content
--or--
AuthError.jsx (Component) - 401 unauthenticated error page
Authentication is handled through an outside resource so this app will not control anything with Logging in or out. There will be no log in/out states simply receiving an object from an API that identifies the User Role & Authenticated Boolean.
What I would like to happen is when the App loads, it will fetch data from a mock API, JSON Server. From there it will render the Auth component. The Auth component will take in props from App.jsx and either render the Layout.jsx or AuthError.jsx.
Where I'm running into issues is how this should be integrated. I'm going to omit lines of code I don't think absolutely pertain to the question.
store.js
import { applyMiddleware, combineReducers, createStore } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import promise from 'redux-promise-middleware';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducer from './reducers';
const middleware = applyMiddleware(promise(), thunk, createLogger());
export default createStore(reducer, composeWithDevTools(middleware));
index.jsx
import React from 'react';
import store from './store.js';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './containers/App.jsx';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
App.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { authenticateUser } from '../actions/authActions.js';
import Auth from '../components/Auth.jsx';
class App extends Component {
constructor(props) {
super(props);
this.state = {
authenticated: false // this needs to be set
};
}
componentWillMount() {
console.log('APP PROPS', this.props);
// this.props.actions.authenticateUser();
authenticateUser(); // this runs but doesn't run the dispatch function
// What I think needs to happen here Dispatch an Action and then setState referring back to how I would previous build with React Redux.
}
render() {
return (
<Auth app_name={ApplicationName} authenticated={this.state.authenticated} {...this.props} />
);
}
}
const mapStateToProps = state => {
console.log('redux store auth state', state);
return {
auth: state.auth
};
};
const mapDispatchToProps = dispatch => {
return { actions: bindActionCreators(authenticateUser, dispatch) };
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Auth.jsx
import React from 'react';
import { Route } from 'react-router-dom';
import AuthError from './AuthError.jsx';
import Layout from './Layout.jsx';
export default function Auth(props) {
console.log('AUTH PROPS', props);
const renderLayout = () => {
if (props.authenticated == true) {
return <Layout app_name={props.app_name} />;
} else {
return <AuthError />;
}
};
return <Route path="/" render={renderLayout} />;
}
authReducer.js
export default function reducer(
state = {
authenticated: null
},
action
) {
switch (action.type) {
case 'AUTH_SUCCESSFUL': {
return {
...state,
authenticated: action.payload.authenticated
};
break;
}
case 'AUTH_REJECTED': {
return {
...state,
authenticated: false
};
}
}
return state;
}
authActions.js
import axios from 'axios';
export function authenticateUser() {
console.log('authenticate user action has been called');
return function(dispatch) {
// nothing runs within this block so it's leading me to believe nothing is being `dispatch`ed
console.log('dispatch', dispatch);
axios
.get('localhost:3004/auth')
.then(response => {
dispatch({ type: 'AUTH_SUCCESSFUL', payload: response.data });
console.log('response', response);
})
.catch(err => {
dispatch({ type: 'AUTH_REJECTED', payload: err });
console.log('error', err);
});
};
}
Right now inside of App.jsx I can console the state of the authReducer and I can call authenticateUser() in my actions. But when I call authenticateUser() the return dispatch function doesn't run. Should I be dispatching the auth action in App.jsx? Or should I be dispatching the auth in Auth.jsx as a prop to then have App.jsx fetch the data? Just a bit lost on breaking this apart and what piece should be doing what work.
I'll do a brief explanation about it to help you to understand those patterns and don't get in confusion anymore (I hope).
So, let's forget reducers for a moment to focus on container, action creator and component pattern.
Component
A lot of people implement components by wrong way when using it with redux application.
A better component approach for redux is, implement it with stateless pattern (see Functional Components). Let's see in practice:
// components/Subscribe.js
import React from 'react'
import PropTypes from 'prop-types'
const Subscribe = ({text, confirmSubscription}) =>
<div>
<p>{text}</p>
<button onClick={confirmSubscription}>Confirm</button>
</div>
Subscribe.propTypes = {
subtitle: PropTypes.string.isRequired
}
Subscribe.defaultProps = {
subtitle: ''
}
export default Subtitle
This allows you to optimize component footprint because they have less features than stateful components (or class components), so you will win some performance and keep focused on component objective.
Container
In other hand, Container is a kind of component with some logical implementation. Container is a pattern created to bind React and Redux, because both should't interact directly. This means, a Container render the component, handle some component events (for example, form onSubmit) and feed components with application state. So, the Container is the best place to interact with Redux. (react-redux)[https://github.com/reactjs/react-redux] and Redux make this task a bit easier. So a simple Container to feed and capture interactions on Subscribe component could be like this:
// containers/SubscribeContainer.js
import React from 'react'
import PropTypes from 'prop-types'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { confirmSubscription } from 'actions/subscription'
import Subscribe from 'components/Subscribe'
const mapStateToProps = state => ({
text: state.subscription.text
})
const mapDispatchToProps = dispatch =>
bindActionCreators({
confirmSubscription
}, dispatch)
const Container = connect(mapStateToProps, mapDispatchToProps)
export default Container(Subscribe)
Action Creator
An action creator (or action creators), is just a collection of or a function where return an action. Simple like that:
// actions/subscription
export const CONFIRM_SUBSCRIPTION = 'actions.confirmSubscription'
export function confirmSubscription() {
return {
type: CONFIRM_SUBSCRIPTION
}
}
For now, we have the triad pattern, Component, Container and Action Creator implemented, from here, you just need two more things to make this working with Redux.
Create a subscription store.
Handle CONFIRM_SUBSCRIPTION (in case to update app's state)
Return a new state
The magic will happen when you return a new state from any reducer, the mapStateToProps will be called and you will receive the new state as argument and from there, React will update your components when necessary, in case of those components are stateless, PureComponent (works only with single level states and props) or custom shouldComponentUpdate.
Another thing to keep on mind is to not do fetch or async execution inside Components, Containers and Action Creators, instead, you can use middleware like redux-thunk to compose a custom middeware to capture actions and handle that before be sent to reducers.
your authenticateUser returns a function, you need to literally run the function. The right way to do that is to add a property in your mapDispatchToProps
const mapDispatchToProps = dispatch => {
return { authenticateUser: () => dispatch(authenticateUser()) };
};
Then, in your componentWillMount function, call
this.props.authenticateUer()
Check this