React authentication HoC - reactjs

I have a React-Router-Redux application that I built with an expressJS server. Part of this application is authentication using JWT. Aside from protecting Routes, I am trying to create a HoC that will protect it's wrapped component by reaching out to the server and authenticating before displaying the wrapped component. Here is the HoC I have built:
withAuth.js:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import * as actions from '../../store/actions';
export default function (ComposedComponent) {
class Authenticate extends Component {
componentWillMount() {
console.log('will mount');
this.props.authenticate();
}
render() {
const { loading, loaded } = this.props;
return !loading && loaded ? <ComposedComponent {...this.props} /> : null;
}
}
const mapStateToProps = state => {
return {
loading: state.auth.loading,
loaded: state.auth.loaded
};
};
const mapDispatchToProps = dispatch => {
return {
authenticate: () => dispatch(actions.authenticate())
};
};
return connect(mapStateToProps, mapDispatchToProps)(Authenticate)
}
I am using Redux Saga aswell. The authenticate action calls a saga that sets loading to true, loaded to false and reaches out to the server. When the server sends confirmation, loaded is set to true and loading is set to false, aside from a cookie and some data being saved.
It basically works, but the problem is that when I enter a route with this HoC, the authentication process is done twice (HoC's ComponentWillMount is called twice) and I cant figure out why. It happens with a wrapped component that doesnt even reach out to the server or change props on mount/update. What am I missing here?
This is one of the wrapped components that has this problem:
class SealantCustomer extends Component {
state = {
controls: {
...someControls
}
}
shouldComponentUpdate(nextProps) {
if (JSON.stringify(this.props.sealantCustomer) === JSON.stringify(nextProps.sealantCustomer)) return false;
else return true;
}
updateInput = (event, controlName) => {
let updatedControls = inputChangedHandler(event, controlName, this.state.controls);
this.setState({controls: updatedControls});
}
searchCustomer = async (event) => {
event.preventDefault();
this.props.fetchCustomer(this.state.controls.phone.value, this.state.controls.site.value, this.state.controls.name.value);
}
render () {
let sealantCustomer;
if (this.props.loading) {
sealantCustomer = <Loader />;
}
if (!this.props.loading) {
if (!this.props.sealantCustomer) this.props.error ? sealantCustomer = <h3 style={{color: 'salmon'}}>ERROR: {this.props.error}</h3> : sealantCustomer = <h3>Please search for a sealant customer</h3>
else if (this.props.sealantCustomer.length === 0) sealantCustomer = <h3>Found no sealant customers with these details!</h3>
else {
let data = [];
this.props.sealantCustomer.forEach(person => {
...filling data here
})
const columns = [{
...table columns
}]
const keysToSkip = [keys];
sealantCustomer = <ReactTable data={data} columns={columns} defaultPageSize={3} className={['-striped', '-highlight', 'tableDefaults'].join(" ")}
SubComponent={sub component} />
}
}
return (
<div className={classes.sealantCustomerPage}>
<SearchBox controls={this.state.controls} submit={this.searchCustomer} inputUpdate={this.updateInput} name="Sealant Customers" />
<div className={classes.sealantCustomer}>
{sealantCustomer}
</div>
</div>
)
}
};
const mapStateToProps = state => {
return {
loading: state.searches.loading,
error: state.searches.error,
sealantCustomer: state.searches.sealantCustomer
};
};
const mapDispatchToProps = dispatch => {
return {
fetchCustomer: (phone, site, name) => dispatch(actions.searchSealantCustomer(phone, site, name))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SealantCustomer);

Related

How to test onclick api call with react

I have 3 react components and when the user clicks on USER_CARD in header then an api is called and the response is displayed in TwitterList component. I have no experience with unit testing, so what are the unit test needs to be done and how? I have read about enzyme and jest but not sure about the implementation.
Fews things I understand here that I need to test the click and also check if the api is responding with any data or not.
Please help me understand how to do this?
import React ,{Component}from 'react'
// Import all contianers here
import Header from './containers/header'
import TweetList from './containers/tweetlist'
// Import all services here
import Http from './services/http'
import './App.css'
class App extends Component {
constructor() {
super()
this.state = {
data: [],
isTop: true,
userName: ''
}
}
_getUserTweets = (user) => {
console.log(user)
if (user !== undefined && user !== '') {
Http.get('/' + user)
.then(response => {
if (response.data.length > 0) {
this.setState((prevState) => {
return {
...prevState,
data: response.data,
userName: user
}
})
}
})
.catch(error => {
console.log(error)
})
} else {
console.log('No user found!!')
}
}
render() {
const {data, userName} = this.state
return (
<div className="app_container">
<Header getUserTweets={this._getUserTweets} />
<TweetList data={data} user={userName} />
</div>
);
}
}
export default App;
import React, {Component} from 'react'
class TweetList extends Component {
constructor() {
super()
this.state = {
tweets: []
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.data.length > 0) {
this.setState((prevState) => {
return {
...prevState,
tweets: nextProps.data
}
})
}
}
render() {
const {tweets} = this.state
return (
<div>
{
tweets.length > 0
&&
tweets.map((currentValue, index) => {
return (
<p>{currentValue.full_text}</p>
)
})
}
</div>
)
}
}
export default TweetList
import React, {Component} from 'react'
import './style.css'
const USER_CARD = ({userName, onClickHandler}) => {
return (
<p onClick={() => onClickHandler(userName)}>{userName}</p>
)
}
class Header extends Component {
componentWillMount() {
if (process.env.REACT_APP_USER_LIST !== undefined && process.env.REACT_APP_USER_LIST.split(',').length > 0) {
this.props.getUserTweets(process.env.REACT_APP_USER_LIST.split(',')[0])
}
}
_getUserTweets = (userName) => {
this.props.getUserTweets(userName)
}
render() {
return(
<div className="header_container">
{process.env.REACT_APP_USER_LIST !== undefined
&&
process.env.REACT_APP_USER_LIST.split(',').length > 0
&&
process.env.REACT_APP_USER_LIST.split(',')
.map((currentValue, index) => {
return (
<USER_CARD userName={currentValue} key={`user-card-${index}`}
onClickHandler={this._getUserTweets} />
)
})}
</div>
)
}
}
export default Header
If the user click on the USER_CARD in Header component then we call an api to get the results.
What are the different unit testing that I can do and how to do it?
wrote this code by heart (so not tested) but should give you the idea:
unit test the onClick:
shallow the USER_CARD with enzyme like this, pass mock function, trigger click and check if the function was called with expected arguments:
const handlerMock = jest.fn()
const wrapper = shallow(<USER_CARD userName="foo" onClickHandler={handlerMock}/>)
wrapper.find('p').simulate('click') // or wrapper.find('p').prop('onClick)()
expect(handlerMock).toHaveBeenCalledTimes(1)
expect(handlerMock).toHaveBeenCalledWith("foo")
unit test the API
a) either mock the whole Http and then use mock return value, shallow your component and trigger your _getUserTweets like in 1. where I showed you how to test your onClick and then find your TweetList if data was set accordingly, here the mocking part of API:
import Http from './services/http'
jest.mock('./services/http')
const mockResponse = foobar; // response expected from your call
Http.get.mockReturnValue(({
then: (succ) => {
succ(mockResponse)
return ({
catch: jest.fn()
})
}
}))
b) dont mock Http but spyOn + mockImplementation:
const getSpy = jest.spyOn(Http, 'get').mockImplementation(() => ...) // here your mock implementation
important! restore at end of test:
getSpy.mockRestore()

Firebase/React/Redux Component has weird updating behavior, state should be ok

I am having a chat web app which is connected to firebase.
When I refresh the page the lastMessage is loaded (as the gif shows), however, for some reason, if the component is otherwise mounted the lastMessage sometimes flickers and disappears afterwards like it is overridden. When I hover over it, and hence update the component, the lastMessage is there.
This is a weird behavior and I spent now days trying different things.
I would be very grateful if someone could take a look as I am really stuck here.
The db setup is that on firestore the chat collection has a sub-collection messages.
App.js
// render property doesn't re-mount the MainContainer on navigation
const MainRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<MainContainer>
<Component {...props} />
</MainContainer>
)}
/>
);
render() {
return (
...
<MainRoute
path="/chats/one_to_one"
exact
component={OneToOneChatContainer}
/>
// on refresh the firebase user info is retrieved again
class MainContainer extends Component {
componentDidMount() {
const { user, getUserInfo, firebaseAuthRefresh } = this.props;
const { isAuthenticated } = user;
if (isAuthenticated) {
getUserInfo(user.id);
firebaseAuthRefresh();
} else {
history.push("/sign_in");
}
}
render() {
return (
<div>
<Navigation {...this.props} />
<Main {...this.props} />
</div>
);
}
}
Action
// if I set a timeout around fetchResidentsForChat this delay will make the lastMessage appear...so I must have screwed up the state / updating somewhere.
const firebaseAuthRefresh = () => dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user) {
localStorage.setItem("firebaseUid", user.uid);
dispatch(setFirebaseAuthUser({uid: user.uid, email: user.email}))
dispatch(fetchAllFirebaseData(user.projectId));
}
});
};
export const fetchAllFirebaseData = projectId => dispatch => {
const userId = localStorage.getItem("firebaseId");
if (userId) {
dispatch(fetchOneToOneChat(userId));
}
if (projectId) {
// setTimeout(() => {
dispatch(fetchResidentsForChat(projectId));
// }, 100);
...
export const fetchOneToOneChat = userId => dispatch => {
dispatch(requestOneToOneChat());
database
.collection("chat")
.where("userId", "==", userId)
.orderBy("updated_at", "desc")
.onSnapshot(querySnapshot => {
let oneToOne = [];
querySnapshot.forEach(doc => {
let messages = [];
doc.ref
.collection("messages")
.orderBy("created_at")
.onSnapshot(snapshot => {
snapshot.forEach(message => {
messages.push({ id: message.id, ...message.data() });
});
});
oneToOne.push(Object.assign({}, doc.data(), { messages: messages }));
});
dispatch(fetchOneToOneSuccess(oneToOne));
});
};
Reducer
const initialState = {
residents: [],
oneToOne: []
};
function firebaseChat(state = initialState, action) {
switch (action.type) {
case FETCH_RESIDENT_SUCCESS:
return {
...state,
residents: action.payload,
isLoading: false
};
case FETCH_ONE_TO_ONE_CHAT_SUCCESS:
return {
...state,
oneToOne: action.payload,
isLoading: false
};
...
Main.js
// ...
render() {
return (...
<div>{React.cloneElement(children, this.props)}</div>
)
}
OneToOne Chat Container
// without firebaseAuthRefresh I don't get any chat displayed. Actually I thought having it inside MainContainer would be sufficient and subscribe here only to the chat data with fetchOneToOneChat.
// Maybe someone has a better idea or point me in another direction.
class OneToOneChatContainer extends Component {
componentDidMount() {
const { firebaseAuthRefresh, firebaseData, fetchOneToOneChat } = this.props;
const { user } = firebaseData;
firebaseAuthRefresh();
fetchOneToOneChat(user.id || localStorage.getItem("firebaseId"));
}
render() {
return (
<OneToOneChat {...this.props} />
);
}
}
export default class OneToOneChat extends Component {
render() {
<MessageNavigation
firebaseChat={firebaseChat}
firebaseData={firebaseData}
residents={firebaseChat.residents}
onClick={this.selectUser}
selectedUserId={selectedUser && selectedUser.residentId}
/>
}
}
export default class MessageNavigation extends Component {
render() {
const {
onClick,
selectedUserId,
firebaseChat,
firebaseData
} = this.props;
<RenderResidentsChatNavigation
searchChat={this.searchChat}
residents={residents}
onClick={onClick}
firebaseData={firebaseData}
firebaseChat={firebaseChat}
selectedUserId={selectedUserId}
/>
}
}
const RenderResidentsChatNavigation = ({
residents,
searchChat,
selectedUserId,
onClick,
firebaseData,
firebaseChat
}) => (
<div>
{firebaseChat.oneToOne.map(chat => {
const user = residents.find(
resident => chat.residentId === resident.residentId
);
const selected = selectedUserId == chat.residentId;
if (!!user) {
return (
<MessageNavigationItem
id={chat.residentId}
key={chat.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
{residents.map(user => {
const selected = selectedUserId == user.residentId;
const chat = firebaseChat.oneToOne.find(
chat => chat.residentId === user.residentId
);
if (_isEmpty(chat)) {
return (
<MessageNavigationItem
id={user.residentId}
key={user.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
</div>
}
}
And lastly the item where the lastMessage is actually displayed
export default class MessageNavigationItem extends Component {
render() {
const { hovered } = this.state;
const { user, selected, chat, isGroupChat, group, id } = this.props;
const { messages } = chat;
const item = isGroupChat ? group : user;
const lastMessage = _last(messages);
return (
<div>
{`${user.firstName} (${user.unit})`}
{lastMessage && lastMessage.content}
</div>
)
}
In the end it was an async setup issue.
In the action 'messages' are a sub-collection of the collection 'chats'.
To retrieve them it is an async operation.
When I returned a Promise for the messages of each chat and awaited for it before I run the success dispatch function, the messages are shown as expected.

API Request return page markup

I have already asked the question already [LINK], And as I did not get any answer I am re-asking, I hope it is not against the rule.
The issue is that when I trigger the request the result that I get is a page markup, However if I re-trigger then everything solves.
The issue appears to be only when I trigger the request as there are no problem when I do the requests at the loading of the page.
I am using react with redux, and redux-thunk as middleware.
This is an image of the response that I get
These are the code for the components:
Action
import { BEGIN_FETCH_MOVIES, FETCHED_MOVIES, FETCH_FAILED_MOVIES } from '../constants';
import axios from 'axios';
//fetch movie
const searchQuery = (url) => {
return dispatch => {
//dispatch begin fetching
dispatch({
type : BEGIN_FETCH_MOVIES,
})
//make a get request to get the movies
axios.get(url)
.then((res) => {
//dispatch data if fetched
dispatch({type : FETCHED_MOVIES, payload : res.data});
})
.catch((err) => {
//dispatch error if error
dispatch({type : FETCH_FAILED_MOVIES});
});
}
//return the result after the request
}
export default searchQuery;
Main component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actionSearchMovie, actionSearchSerie } from '../actions'
import DisplayItemMovie from '../components/DisplayItemMovie';
import DisplayItemSerie from '../components/DisplayItemSerie';
import DrPagination from "../components/DrPagination";
import { Layout, Divider, Icon, Spin, Row } from 'antd';
//Home component
class Home extends Component {
constructor(){
super();
this.state = {
moviePage : 1,
seriePage : 1,
urlMovie : '',
urlSerie : ''
}
}
//make request before the render method is invoked
componentWillMount(){
//url
const discoverUrlMovies = 'https://api.themoviedb.org/3/discover/movie?api_key=72049b7019c79f226fad8eec6e1ee889&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1';
//requests
this.fetchMovie(discoverUrlMovies);
}
fetchMovie = ( url ) => {
this.props.actionSearchMovie(url);
}
//handle pagination
handleChangePage = (page) =>{
let url = 'https://api.themoviedb.org/3/discover/movie?api_key=72049b7019c79f226fad8eec6e1ee889&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=' + page;
this.setState({
moviePage : page,
urlMovie : url
}, ()=> this.state);
this.fetchMovie(this.state.urlMovie);
}
//render
render() {
const movies = this.props.movies.results; //movies
let displayMovies; //display movies
const antIcon = <Icon type="loading" style={{ fontSize: 24 }} spin />; //spinner
//if movies and series is undefined, display a spinner
if(movies.results === undefined){
displayMovies = <Spin indicator={antIcon} />
}else {
//map through movies and series and then display the items
displayMovies = movies.results.map((movie) => {
return <DisplayItemMovie key = {movie.id} movie = {movie} />
});
}
return (
<div>
<div className='header'>
Home
</div>
<Divider />
<Layout style = {{paddingBottom : '1rem', margin : '0 auto' }}>
<h1 className = 'title'>Movie</h1>
<Row type = 'flex' style = {{flexWrap : 'wrap'}}>
{displayMovies}
</Row>
<DrPagination total = { movies.total_results } page = { this.handleChangePage } currentPage = { this.state.moviePage } /> </div>
)
}
};
const mapStateToProps = (state) => {
return{
movies : state.search_movies,
}
}
export default connect(mapStateToProps, { actionSearchMovie })(Home);
I am not including the code for the reducer but if needed I will post it.

React/Redux Why does specific component update, when its sibling’s child component updates, though its state doesn’t change

Update
The sidedrawers state is apparently different, but value does not change...
Details:
There is a layout component, which takes in routes from react router as children.
Inside the layout component code, two child components are rendered, Toolbar, and sidedrawer, and a main section that contains this.props.children.
One of the routes renders a component called page. Page renders another component called graphContainer, and passes it a click event, which is applied to the graphContainer’s button that it renders.
How it works is, I grab the first eight graphs and show 4 of them. When the button is clicked, it decides to either show the next 4 or grab the next eight.
This whole thing uses redux. There’s a page state, authentication state, navigation state, and a graph state. The only partial state changing when the button is clicked, is the graphs.
However, both the GraphContainer updates along with the sidedrawer component. As far as I can tell, nothing in the sidedrawer component is changing, so it should not trigger an update.
In the redux page for navigation state, the switch hits the default, which just returns state.
The graph redux portion works just fine, updates accordingly.
My workaround was to implement a dontUpdate prop in the navigation reducer state. And then use shouldComponentUpdate to check that prop, because the shallow check that was done by default, say with pureComponent, was seeing a different state or prop.
tl;dr: Any ideas why the sidedrawer component keeps updating, even though, as far as I can tell, there’s no prop or state change?
Reducers
const graphReducer = (state = initialState, action) => {
...
case SHOW_NEXTFOUR:
console.log('SHOW NEXT FOUR', state);
return {
...state,
ttlShown: action.ttlShown
};
default:
return state;
}
};
const navReducer = (state = initialState, action) => {
...
default:
return {...state, dontUpdate: true};
}
};
Layout Component
class Layout extends Component {
...
handleSideBarOpen = () => {
this.props.onSidebarToggle();
}
render () {
return (
<Aux>
<Toolbar
isAuth={this.props.isAuthenticated}
drawerToggleClicked={this.handleSideBarOpen}
/>
<SideDrawer
open={this.props.sidebarOpen}
closed={this.props.onSidebarToggle}
/>
<main className={classes.Content}>
{this.props.children}
</main>
</Aux>
)
}
}
const mapStateToProps = ({ navigation, auth }) => {
const { sidebarOpen } = navigation;
const { token } = auth;
return {
sidebarOpen,
isAuthenticated: token !== null
};
};
const mapDispatchToProps = {
onSidebarToggle, getNavTree
};
export default connect(
mapStateToProps, mapDispatchToProps
)(Layout);
Sidedrawer Component
class sideDrawer extends Component {
state = {
popupMenuOpen: false
}
shouldComponentUpdate ( nextProps, nextState ) {
if(nextProps.dontUpdate)
return false;
else return true;
}
…
render() {
…
let navitems = [];
if(this.props.navData && !this.props.error) {
navitems = (
<NavigationItems
showClients={this.props.showClientsBtn}
navData={this.props.navData}
curClientid={this.props.curClientid}
curSiteid={this.props.curSiteid}
curDashid={this.props.curDashid}
curPageid={this.props.curPageid}
closeSidebar={this.props.closed}
onPageClick={this.handlePageClick}
onCSDClick={this.handleOpenPopupMenu}
/>
);
} else
navitems = <p>Problem Loading Tree</p>;
return (
<Aux>
<div className={attachedClasses.join(' ')}>
<div className={classes.Logo}>
<div className={classes.CloseWrapper}>
<Chip onClick={this.props.closed} className={classes.CloseChip}>X</Chip>
</div>
<div className={classes.CrumbWrapper}>
<Breadcrumbs
backBtn={this.handleBackClick}
handleCrumbClick={this.handleCrumbClick}
breadcrumbs={this.props.breadcrumbs}
/>
</div>
</div>
<nav>
{navitems}
<Popover
style={{width: "90%"}}
open={this.state.popupMenuOpen}
anchorEl={this.state.anchorEl}
anchorOrigin={{horizontal: 'middle', vertical: 'bottom'}}
targetOrigin={{horizontal: 'middle', vertical: 'top'}}
onRequestClose={this.handleClosePopupMenu}
>
<Menu
style={{width: "87%"}}>
{MIs}
</Menu>
</Popover>
</nav>
</div>
</Aux>
);
}
};
const mapStateToProps = ({ navigation }) => {
const { dontUpdate, clientid, breadcrumbs,currentPage, selectedClient, selectedSite, selectedDash, selectedPage, navigationData, sidebarOpen, navError } = navigation;
...
}
return {
dontUpdate,
clientid,
showClientsBtn,
navData,
curClientid,
curSiteid,
curDashid,
curPageid,
parentPageid,
sidebarOpen,
navError,
breadcrumbs,
currentPage
};
};
const mapDispatchToProps = {
getNavTree,
onPageSelected,
onSwitchCSD,
onPageRoute
};
export default withRouter(connect(
mapStateToProps, mapDispatchToProps
)(sideDrawer));
Page Component
class Page extends Component {
componentWillMount () {
this.props.getCurPage();
}
render () {
let content = null;
if(this.props.location.state && this.props.location.state.currentPage)
content = (<GraphContainer pageid={this.props.location.state.currentPage} />);
return this.props.location.state && this.props.location.state.currentPage ? (
<Aux>
<p>A PAGE!</p>
{content}
</Aux>
) : (<Redirect to="/" />);
}
}
const mapStateToProps = ({pages}) => {
const { clientid, curPage } = pages;
return {
clientid, curPage
};
};
const mapDispatchToProps = {
getSelectedPage, getCurPage
};
export default connect(
mapStateToProps, mapDispatchToProps
)(Page);
Graph Container
class GraphsContainer extends Component {
componentWillReceiveProps(newProps) {
if(this.props.pageid !== newProps.pageid)
this.props.getFirstEight(newProps.pageid);
}
componentDidMount() {
if(this.props.pageid)
this.props.getFirstEight(this.props.pageid);
}
handleNextClick = (event) => {
event.preventDefault();
this.props.getNextEight(this.props.pageid, this.props.lastNum, this.props.ttlShown);
}
render() {
let graphcards = null;
let disableNext = null;
if (this.props.lastNum >= this.props.ttl)
disableNext = true;
if(this.props.graphs && this.props.graphs.length > 0) {
graphcards = ...
}
return (
<div className={classes.Shell}>
{graphcards}
{this.props.lastNum < this.props.ttl ? (
<div className={classes.NavBtns}>
<RaisedButton disabled={disableNext} onClick={this.handleNextClick}>{'V'}</RaisedButton>
</div>
):null}
</div>
);
}
}
const mapStateToProps = ({pageGraphs}) => {
const { graphs, ttl, lastNum, ttlShown } = pageGraphs;
return {
graphs, ttl, lastNum, ttlShown
};
};
const mapDispatchToProps = {
getFirstEight, getNextEight
};
export default connect(
mapStateToProps, mapDispatchToProps
)(GraphsContainer);
Actions
export const getFirstEight = (pageid) => {
let str = ...;
return (dispatch) => {
axios.get( str )
.then( response => {
let data = {};
let graphs;
let ttl;
let newLastNum = 0;
if((typeof response.data !== 'undefined') && (response.data !== null)) {
data = {...response.data};
ttl = data.total;
if(ttl <= 8) {
graphs = [...data.graphs];
newLastNum = ttl;
} else {
graphs = [...data.graphs].slice(0,8);
newLastNum = 8;
}
}
dispatch({type: GET_FIRSTEIGHT, payload: {ttl,graphs, lastNum:newLastNum}});
} )
.catch( error => {
console.log('ERROR FETCHING NAV TREE', error);
dispatch({type: GET_FIRSTEIGHT, payload: {}});
} );
};
};
export const getNextEight = (pageid, lastNum, ttlShown) => {
let str = ...;
let newLastNum = 0;
return (dispatch) => {
if(ttlShown < lastNum) {
dispatch({type: SHOW_NEXTFOUR, ttlShown: ttlShown+4});
} else {
axios.get( str )
.then( response => {
// console.log('[RESPONSE]', response);
let data = {};
let graphs;
let ttl;
if((typeof response.data !== 'undefined') && (response.data !== null)) {
data = {...response.data};
ttl = data.total;
if(ttl <= (lastNum+8)) {
graphs = [...data.graphs].slice(lastNum);
newLastNum = ttl;
} else {
graphs = [...data.graphs].filter((el,index) => {
return (index > (lastNum-1)) && (index < (lastNum+8));
});
newLastNum = lastNum+8;
}
}
dispatch({type: GET_NEXTEIGHT, payload: {ttl,graphs, lastNum:newLastNum, ttlShown: ttlShown+4}});
} )
.catch( error => {
console.log('ERROR FETCHING NAV TREE', error);
dispatch({type: GET_NEXTEIGHT, payload: {}});
} );
}
};
};

React/Redux Connect issues with mapStateToProps

I have the following React Component
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import _ from 'lodash';
import Product from './product';
import { openPaymentModal } from '../../../state/modalActions';
import { fetchAccountProducts } from '../../../lib/ecApi';
import { fetchChargifyCallById } from '../../../lib/chargifyApi';
import { filterProductsForUser, prepProducts } from '../../../_helpers';
class Products extends Component {
constructor () {
super();
this.state = {
products: [],
currentProduct: '',
showSuccess: false,
}
}
componentDidMount() {
const { location, user } = this.props;
fetchAccountProducts()
.then(this.addBasicProduct)
.then(this.filterProducts(user));
this.checkChargifyCall(location.query, user);
}
addBasicProduct(products) {
return prepProducts(products);
}
filterProducts(user) {
return products => {
this.setState({products: filterProductsForUser(products, user)});
}
}
checkChargifyCall (query, user) {
if (_.isEmpty(query)) {
const currentProduct = this.determineProduct(user);
this.setState({currentProduct});
return;
}
fetchChargifyCallById(query.call_id).done(data => {
const { product } = data.response.signup;
const { errors } = data.response.meta;
if (query && query.status_code !== '200') {
this.props.dispatch(openPaymentModal(
product.handle,
errors,
));
} else {
this.setState({
currentProduct: product.handle,
showSuccess: true
});
}
});
}
determineProduct(user) {
const subscription = user.chargifySubscriptions[0];
if (subscription && subscription.product) {
return subscription.product.handle;
}
return this.state.currentProduct;
}
render () {
let calloutEl = (
<div className='callout success'>Success!</div>
);
return (
<div className="row medium-up-2 large-up-3 products-row">
{this.state.showSuccess && calloutEl}
{this.state.products.map((object, i) => {
return <div className="column"><Product
price={object.price}
name={object.name}
interval={object.interval}
intervalUnit={object.interval_unit}
isPaid={object.require_credit_card}
description={object.description}
handle={object.handle}
key={i}
currentProduct={this.state.currentProduct} /></div>;
})}
</div>
);
}
}
const mapStateToProps = state => ({user: state.user});
export default connect(mapStateToProps)(Products);
The problem I am having is that if I console.log(this.props.user) in my componentDidMount method, it is the initial state from the reducer vs the fully propagated user state. Any reasons why that might be happening? I'm fairly new to React/Redux, so my apologies for ignorance
Answer: it is the initial state from the reducer.
reason reducer represents a piece of state. and please your a promise middle to handle your data fetching.
The problem I am having is that if I console.log(this.props.user) in my componentDidMount method, it is the initial state from the reducer vs the fully propagated user state. Any reasons why that might be happening? I'm fairly new to React/Redux, so my apologies for ignorance.
connect is a high order component that passes data to your container component. in your case Products component receives state as props from connect
const mapStateToProps = state => ({user: state.user}); //the state you want
export default connect(mapStateToProps)(Products); //user as state to you products component.

Resources