I have HOC component which wraps all the page Components. The page component has pagination, when user clicks next, the route params changes and the difference between route param and state is compared in componentDidUpdate and then api is called. The code works on without wrapping HOC.
Routes
import React from 'react';
import { Redirect, Route, Switch, withRouter } from 'react-router-dom';
import hocWrapper from './hocWrapper'
import Dashboard from './components/screens/dashboard/Dashboard';
import Movies from './components/screens/movies/Movies';
const Routes = (props) => (
<Switch style={{ position: 'absolute' }}>
<Route exact path="/all/page:pageNumber" component={hocWrapper(Dashboard)} />
<Route exact path="/movies/page:pageNumber" component={Movies} />
</Switch>
);
export default withRouter(Routes);
HOC wrapper Component
import React, { useEffect } from 'react';
import { useDispatch } from "react-redux";
import { searchTextAction } from './containers/actions/userActions'
export default function (ComposedClass) {
const ClosedRouteForUser = (props) => {
const dispatch = useDispatch();
useEffect(() => {
console.log(window.location.pathname)
if (window.location.pathname !== `/search/page1` &&
window.location.pathname.includes('details') === false) {
dispatch(searchTextAction(''))
}
}, []);
return <ComposedClass {...props} />;
};
return ClosedRouteForUser;
}
Page Component
import React, { Component } from 'react'
import apiCall from '../../../services/apiCall';
import { trendingURL } from '../../../services/apiURL'
import MediaList from '../../common/MediaList'
import { withRouter } from 'react-router-dom';
class Dashboard extends Component {
state = {
dataList: [],
refresh: false,
pageNumber: this.props.match?.params && this.props.match.params.pageNumber,
}
async componentDidMount() {
try {
if (this.props.match?.params.routedFrom) {
localStorage.setItem("routedFrom", this.props.match.params.routedFrom)
}
console.log('cd mount')
window.scrollTo(0, 0)
this.setState({ refresh: true })
let data = { page: 1, media_type: "all" }
let apiData = await apiCall(trendingURL, data)
this.setState({ dataList: apiData.results, refresh: false })
} catch (error) {
console.log(error)
}
}
async componentDidUpdate(prevProps, prevState) {
if (this.props.match.params.pageNumber !== this.state.pageNumber) {
console.log('cd updates')
let data = { page: this.props.match.params.pageNumber, media_type: "all" }
let apiData = await apiCall(trendingURL, data)
this.setState({
dataList: apiData.results,
pageNumber: this.props.match.params.pageNumber,
refresh: false
})
}
}
pageNavigate = (value) => {
window.scrollTo(0, 0)
this.setState({ pageNumber: value })
this.props.history.replace({ pathname: `/all/page${value}` })
}
previous = () => {
this.pageNavigate(parseInt(this.props.match.params.pageNumber) - 1)
}
next = () => {
this.pageNavigate(parseInt(this.props.match.params.pageNumber ?
this.props.match.params.pageNumber :
1) + 1)
}
render() {
const { dataList, refresh } = this.state
return (
<MediaList
listData={dataList}
refresh={refresh}
previous={this.previous}
next={this.next}
/>
)
}
}
export default withRouter(Dashboard)
Related
I am having some issues updating my team's code in order to app the inactivity function. But when trying to do that I getting an Error message on my console
TypeError: Object(...) is not a function
If anyone knows or has an idea of what the problem really is please let me know.
Here is the console
Here is the code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as Actions from '../actions';
import NeedContainer from './NeedContainer';
import ProfilContainer from './ProfilContainer';
import DealContainer from './DealContainer';
import AmountContainer from './AmountContainer';
import DurationContainer from './DurationContainer';
import MonthlyContainer from './MonthlyContainer';
import ContributionContainer from './ContributionContainer';
import FeesContainer from './FeesContainer';
import createActivityDetector from 'activity-detector'
import { useState, useEffect } from 'react';
function useIdle(time) {
const [isIdle, setIsIdle] = useState(false)
useEffect(() => {
const activityDetector = createActivityDetector(time)
activityDetector.on('idle', () => setIsIdle(true))
activityDetector.on('active', () => setIsIdle(true))
return () => activityDetector.stop()
}, [])
return isIdle;
}
class SimulatorContainer extends Component {
render() {
const isIdle = useIdle({timeToIdle:1000})
if (! this.props.ready)
return (<div className="wf-loading">Loading ...</div>);
return (
<div className={this.props.className}>
<NeedContainer />
<ProfilContainer />
<AmountContainer />
<ContributionContainer />
<DurationContainer />
<MonthlyContainer />
<DealContainer />
<FeesContainer />
</div>
);
}
}
const mapStateToProps = state => {
return {
ready:state.simulator.ready,
interest:state.simulator.interest,
}
}
const mapDispatchToProps = dispatch => {
return {
isReady: (ready) => {
dispatch(Actions.isReady(ready));
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(SimulatorContainer)
class useIdle extends Component() {
constructor() {
super(props)
this.state = {
isIdle: false
}
}
componentDidMount() {
this.activityDetector = createActivityDetector(time)
this.activityDetector.on('idle', () => this.setState({isIdle: true}))
this.activityDetector.on('active', () => this.setState({isIdle: true}))
}
componentWillUnmount() {
this.activityDetector.stop()
}
render(){
return this.state.isIdle;
}
}
I wonder why the test is failing when I use it with redux hooks:
The code is working finem but the tests are failing for some reason. I am unable to test if the component is being rendered or not.
Component:
import React, { useEffect, useState } from 'react';
import { fetchAllApis } from '../../../redux/actions/marketplace/marketplaceActions';
import { useDispatch, useSelector, connect } from 'react-redux';
import ApiCard from '../ApiCard/ApiCard';
import Spinner from '../../../components/Extras/Spinner/Spinner';
const ApiSection = ({ apiList, error, loading, categories }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchAllApis({ page, category: categories }));
}, [dispatch, categories]);
const renderApiCards = () => {
return apiList.map((each) => (
<ApiCard key={each.apiId} info={each} data-test="ApiCard" />
));
};
if (loading) {
return <Spinner data-test="Spinner" />;
}
if (error) {
return <h1 data-test="Error">Error while fetching</h1>;
}
return (
<div className="ApiSection" data-test="ApiSection">
<div className="ApiSection__cards">{renderApiCards()}</div>
</div>
);
};
const mapStateToProps = ({ marketplaceApiState }) => {
const { apiList, error, loading } = marketplaceApiState;
return {
error,
loading,
apiList: Object.values(apiList),
};
};
export default connect(mapStateToProps)(ApiSection);
Here is the test for the above component:
Test:
import React from 'react';
import { mount } from 'enzyme';
import ApiListSection from './ApiListSection';
import { findByTestAttr, createTestStore } from '../../../../testUtils';
import { Provider } from 'react-redux';
const setup = (props = {}) => {
let initialState = {
marketPlaceState: {
apiList: {
a: { apiId: 'a', name: 'name', description: 'desc', categories: 'cat'}
},
},
};
const store = createTestStore(initialState);
const wrapper = mount(
<Provider store={store}>
<ApiListSection {...props} />
</Provider>
);
return wrapper;
};
describe('ApiListSection Component', () => {
let component;
beforeEach(() => {
component = setup();
});
// assertions
it('Should render without failing', () => {
const apiSection = findByTestAttr(component, 'ApiSection');
expect(apiSection.length).toBe(1); // <===== FAILING HERE !!!!!
});
});
I would really appreciate the help, thanks in advance
I am trying to combine navigation, more exactly stack navigation with my react native redux application, and I encountered this error during debugging. From what I've already searched, it's because the navigation isn't really compatible with the redux mode of work. So I tried a solution to my problem, but still I have the same error. Here is my code:
Login.js
import React, { Component } from 'react';
import { View, ActivityIndicator, TouchableHighlight } from 'react-native';
import { getLogger, issueToText } from '../core/utils';
import styles from '../core/styles';
import { Card, Button, FormLabel, FormInput } from "react-native-elements";
import { connect } from 'react-redux'
import { loginAction } from '../actions/LoginActions'
class LoginComponent extends Component {
constructor(props) {
super(props);
this.login = this.login.bind(this)
}
render() {
const { error, isLoading } = this.props;
const inputFormProp = {
username: '',
password: ''
};
return (
<View style={{ paddingVertical: 20 }}>
<Card>
<FormLabel>Email</FormLabel>
<FormInput value={inputFormProp.username} onChangeText={(text) => inputFormProp.username = text} />
<FormLabel>Password</FormLabel>
<FormInput value={inputFormProp.password} onChangeText={(text) => inputFormProp.password = text} />
<Button
buttonStyle={{ marginTop: 20 }}
backgroundColor="#03A9F4"
title="SIGN IN"
onPress={this.login(inputFormProp)}
/>
</Card>
<ActivityIndicator animating={this.props.isLoading} style={styles.activityIndicator} size="large" />
</View>
);
}
login(inputFormProp) {
console.log(this.props)
const { store } = this.props.screenProps.store;
console.log(loginAction)
const { dispatch } = this.props
console.log(dispatch)
dispatch(loginAction(inputFormProp))
.then(() => {
if (this.props.error === null && this.props.isLoading === false) {
if (store.getState().auth.token) {
this.props.navigation.navigate('ProductList', { token: store.getState().auth.token });
}
}
})
.catch(error => {
});
}
}
function mapStateToProps(state) {
const { error, isLoading } = state.auth
return {
error,
isLoading,
}
}
export default connect(mapStateToProps)(LoginComponent)
App.js
import React, { Component } from 'react';
import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';
import { authReducer } from "./src/reducers/LoginReducer";
import { productReducer } from "./src/product/service";
import { ProductList } from "./src/product/ProductList";
import { LoginComponent } from "./src/components/Login";
import { Provider, connect } from "react-redux";
import { StackNavigator, addNavigationHelpers } from "react-navigation";
import Routes from "./src/core/routes";
const AppNavigator = StackNavigator(Routes, {
navigationOptions: {
title: ({ state }) => {
if (state.params) {
return `${state.params.title}`;
}
}
}
});
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
#connect(state => ({
nav: state.nav
}))
class AppWithNavigationState extends Component {
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
);
}
}
const initialState = {
auth: { isLoading: false, error: null },
};
const rootReducer = combineReducers({ product: productReducer, auth: authReducer, nav: navReducer });
let store = createStore(rootReducer, initialState,
compose(applyMiddleware(thunk, createLogger())));
export default function App() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
Routes.js
import { ProductList } from "../product/ProductList";
import { LoginComponent } from "../components/Login";
const Routes = {
Login: { screen: LoginComponent },
ProductList: { screen: ProductList }
};
export default Routes;
Here is my error: Route Login should declare a screen.
What did I do wrong with my code? Thank you.
I fixed the error. It was because I added between LoginComponent {} in the routes.js file at:
import { LoginComponent } from "../components/Login";
I have HOC for check is logged user.
import React from 'react';
import { Redirect } from 'react-router-dom';
export default function requireAuthComponent(store, Component, redirectTo = '/') {
return class RequireAuthComponent extends React.Component {
render() {
const state = store.getState();
const auth = state.auth;
if (auth.logged) {
return <Component {...this.props} />;
}
return <Redirect to={redirectTo} />;
}
};
}
And route
import PrivatContainer from './container/PrivatContainer';
import requireAuth from '../../components/requireAuth';
export default store => ({
path: '/privat',
exact: true,
component: requireAuth(store, PrivatContainer),
});
PrivatComponent
import React from 'react';
export default ({ auth: { logged }, toggleLogin }) => (
<div>
<h1>Privat Route</h1>
<h3>User is {logged.toString()}</h3>
<button onClick={() => toggleLogin()}>Logout</button>
</div>
);
When first enter on route all work fine, but when I change store with logged: false, I still stay on this component(route) because route call once and don't update on store update, how it fix or subscribe RequireAuthComponent on store update ?
Maybe it is not properly, so if you have better idea please tell me :)
export default function requireAuthComponent(store, Component, redirectTo = '/') {
return class RequireAuthComponent extends React.Component {
state = {
listener: null,
}
componentWillMount() {
const listener = store.subscribe(() => {
this.forceUpdate();
});
this.setState({ listener });
}
componentWillUnmount() {
const { listener } = this.state;
listener();
this.setState({ listener: null });
}
render() {
const state = store.getState();
const auth = state.auth;
if (auth.logged) {
return <Component {...this.props} />;
}
return <Redirect to={redirectTo} />;
}
};
}
I have component App which render its children and Header component. I use Preloader from react-loader repo which takes bool loaded and render preloader or page in depended from bool. When App componentWillMount, data fetch via actionCreators, action use redux-api-middleware, then when execute render in App, Header fetch data via actionCreator boundGetPhotos which execute recursively look PHOTOS_GET_SUCCESS in console screenshot here i log action.type in my fetchingMiddleware . All actions pass from my middleware fetchingMiddleware look belowe. Which can be reasons of recursive behavior why it execute again and again and how i can solve it
App
import React, { Component, PropTypes } from 'react';
import Counterpart from 'counterpart';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import { bindActionCreators } from 'redux';
import { getFriends, getMessages } from '../data/Data.Actions';
import { getUsers } from '../users/Users.Actions';
import Header from './Header';
class App extends Component {
componentWillMount() {
const { boundGetFriends, boundGetMessages, boundGetUsers } = this.props;
boundGetFriends();
boundGetMessages();
boundGetUsers();
}
render() {
const { children, fetching } = this.props;
return (
<Loader loaded={!fetching.size}>
<div>
<Header/>
{children}
</div>
</Loader>
);
}
}
App.propTypes = {
boundGetUsers: PropTypes.func,
boundGetMessages: PropTypes.func,
boundGetFriends: PropTypes.func,
fetching: PropTypes.array
};
export default connect((store) => {
return {
fetching: store.fetching
};
}, (dispatch) => {
return {
boundGetUsers: bindActionCreators(getUsers, dispatch),
boundGetFriends: bindActionCreators(getMessages, dispatch),
boundGetMessages: bindActionCreators(getFriends, dispatch)
};
})(App);
Header
import React, { Component, PropTypes } from 'react';
import React, { Component, PropTypes } from 'react';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { getPhotos } from '../user/User.Actions';
class Header extends Component {
componentWillMount() {
const { boundGetPhotos } = this.props;
boundGetPhotos();
}
render() {
return (
<Navbar fluid collapseOnSelect>
<Navbar.Brand>
MyProject
</Navbar.Brand>
</Navbar>
);
}
}
Header.propTypes = {
boundGetPhotos: PropTypes.func.isRequired
};
export default connect((store) => null, (dispatch) => {
return {
boundGetPhotos: bindActionCreators(getPhotos, dispatch)
};
})(Header);
FetchingMiddleware
import { startFetching, endFetching } from './FetchingMiddleware.Actions';
export default store => next => action => {
console.log(action.type);
if (typeof action !== 'function' && action.type.search(/REQUEST/) !== -1) {
store.dispatch(startFetching());
}
if (typeof action !== 'function' && action.type.search(/SUCCESS|FAILURE/) !== -1) {
store.dispatch(endFetching());
}
next(action);
};
FetchingMiddlewareReducers
import Immutable from 'immutable';
import { END_FETCHING, START_FETCHING, RESET_FETCHING } from './FetchingMiddleware.Actions';
import createReducer from '../utils/utils';
function addFetching(state, action) {
return state.push(true);
}
function removeFetching(state, action) {
return state.pop();
}
function resetFetching(state, action) {
return Immutable.List();
}
export default createReducer({
[END_FETCHING]: removeFetching,
[START_FETCHING]: addFetching,
[RESET_FETCHING]: resetFetching
}, Immutable.List());
FetchingMiddlewareActions
export const END_FETCHING = 'END_FETCHING';
export const START_FETCHING = 'START_FETCHING';
export const RESET_FETCHING = 'RESET_FETCHING';
export function endFetching() {
return {
type: END_FETCHING
};
}
export function startFetching() {
return {
type: START_FETCHING
};
}
export function resetFetching() {
return {
type: RESET_FETCHING
};
}
getPhotos
import { CALL_API } from 'redux-api-middleware';
export const PHOTOS_GET_SUCCESS = 'PHOTOS_GET_SUCCESS';
export function getPhotos() {
return {
[CALL_API]: {
endpoint: '/photos',
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
types: ['REQUESTPHOTOS', PHOTOS_GET_SUCCESS, 'FAILURE']
}
};
}
Your <Header /> component should be a pure component that knows nothing about your state container (Redux) or dispatch.
Using the approach you have here would litter your component tree with 'connect' and add awareness of Redux to all of your components. This is bad practice in terms of scalability - what if you wanted to replace Redux with another state container?.
I would recommend that all state and actions should be bound to props and passed down the tree into your components such as the <Header /> component.
This should also resolve the issues you are having.
App
import React, { Component, PropTypes } from 'react';
import Counterpart from 'counterpart';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import { getMasterDataSchema, getMasterDataData } from '../masterdata/MasterData.Actions';
import { getQuestionnaireSchema } from '../questionnaireschema/QuestionnaireSchema.Actions';
import Header from './Header';
class App extends Component {
componentWillMount() {
const {
GetMasterDataData,
GetMasterDataSchema,
GetQuestionnaireSchema
} = this.props;
GetMasterDataData();
GetMasterDataSchema();
GetQuestionnaireSchema();
}
render() {
const { children, fetching, GetPrincipal } = this.props;
return (
<Loader loaded={!fetching.size}>
<div>
<Header GetPrincipal={GetPrincipal} />
{children}
</div>
</Loader>
);
}
}
App.propTypes = {
GetPrincipal: PropTypes.func,
GetQuestionnaireSchema: PropTypes.func,
GetMasterDataSchema: PropTypes.func,
GetMasterDataData: PropTypes.func,
fetching: PropTypes.array
};
export default connect(({ fetching }) => ({
fetching
}), {
GetPrincipal,
GetQuestionnaireSchema,
GetMasterDataData,
GetMasterDataSchema,
})(App);
Header
import React, { Component, PropTypes } from 'react';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
export default class Header extends Component {
componentWillMount() {
const { GetPrincipal } = this.props;
GetPrincipal();
}
render() {
return (
<Navbar fluid collapseOnSelect>
<Navbar.Brand>
EMS
</Navbar.Brand>
</Navbar>
);
}
}
Header.propTypes = {
GetPrincipal: PropTypes.func.isRequired
};