ComponentWillMount keeps being called repeately - javascript

I ma trying to call the backend api to get the user's profile on page load:
Given the following action:
export const GET_MY_PROFILE_START = 'GET_MY_PROFILE_START';
export const GET_MY_PROFILE_ERROR = 'GET_MY_PROFILE_ERROR';
export const GET_MY_PROFILE_SUCCESS = 'GET_MY_PROFILE_SUCCESS';
let a = 0;
export function getMyProfile() {
a = a+1;
window.console.log("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
window.console.log(a);
return dispatch => {
window.console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
dispatch(getMyProfileStart());
$.ajax({
type: 'GET',
url: getMyProfileURL,
contentType: "application/json",
dataType: 'json',
}).done(function(res){
if (!res.values || res.values.count > 0) {
dispatch(getMyProfileSuccess(res.data))
} else {
dispatch(getMyProfileError())
}
}).fail(function(error) {
dispatch(getMyProfileError())
})
}
}
function getMyProfileStart() {
return {
type: GET_MY_PROFILE_START,
}
}
function getMyProfileSuccess(profile) {
return {
type: GET_MY_PROFILE_SUCCESS,
profile: profile,
}
}
function getMyProfileError() {
return {
type: GET_MY_PROFILE_ERROR,
}
}
and following reducer:
import { SET_USER_PROFILE, CLEAR_USER_PROFILE, GET_MY_PROFILE_START, GET_MY_PROFILE_ERROR, GET_MY_PROFILE_SUCCESS } from '../serActions'
export default (state = {
loggedIn: false,
profiledLoading: false,
profile: {},
}, action) => {
switch (action.type) {
case SET_USER_PROFILE:
return {
loggedIn: true,
profiledLoading: false,
profile: {},
}
case CLEAR_USER_PROFILE:
return {
loggedIn: false,
profiledLoading: false,
profile: {},
}
case GET_MY_PROFILE_START:
return {
loggedIn: false,
profiledLoading: true,
profile: {},
}
case GET_MY_PROFILE_ERROR:
return {
loggedIn: true,
profiledLoaded: false,
profile: {},
}
case GET_MY_PROFILE_SUCCESS:
return {
loggedIn: true,
profiledLoading: false,
profile: action.profile,
}
default:
return state
}
}
and the following component:
class AvatarButton extends Component {
componentWillMount() {
window.console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
this.props.dispatch(getMyProfile())
}
render() {
//const { profile, toggleMenu, showHideMenu } = this.props
const { user } = this.props
window.console.log(user);
return (
<a className="user-button nav-button avatar-button"
onClick={user.toggleMenu}
onBlur={this.hideMenu.bind(this)}
>
<i className="glyphicon glyphicon-user"/>
</a>
)
}
hideMenu() {
this.props.user.showHideMenu(false)
}
}
AvatarButton.propTypes = {
toggleMenu: PropTypes.func.isRequired,
showHideMenu: PropTypes.func.isRequired,
}
function select(state) {
return {
user: state.user,
}
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(AvatarButton)
This Component is used in:
class UserNavContainer extends Component {
constructor(props) {
super(props)
this.state = {
displayMenu: false,
}
}
render() {
const { dispatch, user, pathname } = this.props
if (!user.loggedIn)
return (
<LoginButton pathname={pathname}/>
)
return (
<div className="user-nav">
<AvatarButton
toggleMenu={this.toggleMenu.bind(this)}
showHideMenu={this.showHideMenu.bind(this)}
/>
<UserMenu
visible={this.state.displayMenu}
logoutHandler={this.logout.bind(this)}
hideMenu={this.showHideMenu.bind(this, false)}
/>
</div>
)
}
logout() {
window.location = "some url"
}
toggleMenu() {
this.showHideMenu(!this.state.displayMenu)
}
showHideMenu(show) {
this.setState({
displayMenu: show,
})
}
}
function select(state) {
return {
user: state.user,
}
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(UserNavContainer)
and finally the top level component that is using UserNavContainer:
class AppHandler extends Component {
componentWillMount() {
authUser(this.authSuccess.bind(this), this.authFail.bind(this))
}
authSuccess() {
this.props.dispatch(login())
}
authFail() {
this.props.dispatch(logout())
}
render() {
window.console.log("renderrenderrenderrenderrenderrenderrenderrenderrenderrender");
return (
<div className="app-container">
<div className="top-nav row">
<div className="col-md-8 col-md-offset-2 col-sm-10 col-sm-offset-1 col-xs-12">
<BackButton
pathname={this.props.location.pathname}
/>
<UserNavContainer
pathname={this.props.location.pathname}
/>
<div className="header">
<img src="https://www.wewherego.com/img/logo/logo_wherego.png"/>
<h1>{this.props.pageHeader}</h1>
</div>
</div>
</div>
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={1000}
transitionLeaveTimeout={1000}
>
{React.cloneElement(this.props.children, {
key: this.props.location.pathname,
})}
</ReactCSSTransitionGroup>
</div>
)
}
}
function select(state) {
return {
selectedCity: state.selectedCity,
pageHeader: state.pageHeader,
}
}
// Wrap the component to inject dispatch and state into it
export default connect(select)(AppHandler)
In the AppHandler, it is calling the following methods in the ComponentWillMount:
export function authUser(loginCb, logoutCb) {
const data = readCookie('something')
if (!data) {
logoutCb()
} else {
loginCb()
}
return
}
export function signoutUser() {
//clean up some stuff
}
When I am opening the page, I can only see 1 line of
renderrenderrenderrenderrenderrenderrenderrenderrenderrenderrender
but I see the log keeps printing:
UserNavContainerUserNavContainerUserNavContainerUserNavContainerUserNavContainerUserNavContainer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1057
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1058
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
1059
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
So apparently the componentWillMount method, for some reason, is kept being called, and the a in getMyProfile() now goes to 1059(and still counting).
I have no idea why this would happen?

If you're dispatching actions in component lifecycle methods, you should do it in componentDidMount not willMount. It appears you're triggering an infinite loop blocking the component from mounting.

Try by wrapping your onClick function call as-
onClick={() => user.toggleMenu}

The real problem is that in the components, they are dispatching SET_USER_PROFILE event, which changes the loggedIn variable to true, but then the app dispatches GET_MY_PROFILE_START which changes the loggedIn variable to false. This in return triggers the new cycle and moves loggedIn to true, so the loop keeps going.
The right way to do it is that in the reducer, instead of setting variables, use object.assign to change the values of the variable. So it really should be:
export default (state = {
loggedIn: false,
profiledLoading: false,
profile: {},
}, action) => {
switch (action.type) {
case GET_MY_PROFILE_START:
return Object.assign({}, state, {
loggedIn: true,
profiledLoading: true,
})
case CLEAR_USER_PROFILE:
return Object.assign({}, state, {
loggedIn: false,
})
case GET_MY_PROFILE_ERROR:
return Object.assign({}, state, {
profiledLoaded: false,
profile: {},
})
case GET_MY_PROFILE_SUCCESS:
return Object.assign({}, state, {
profiledLoading: false,
profile: action.profile,
})
default:
return state
}
}

Related

React class Component not re render after props change from redux action

The component does not re render after successfully update state in redux
i have tried to do some condition in componentShouldUpdate end up with loading true without change
reducer.js
import * as types from "./actionsType";
const INITIAL_STATE = {
slide_data: [],
error: null,
loading: false,
};
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties,
};
};
const slideReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.SLIDES_FETCH_START:
return updateObject(state, {
error: null,
loading: true,
});
case types.SLIDES_FETCH_SUCSSES:
return updateObject(state, {
slide_data: action.payload,
error: null,
loading: false,
});
case types.SLIDES_FETCH_FAIL:
return updateObject(state, {
error: action.error,
loading: false,
});
default:
return state;
}
};
export default slideReducer;
actions.js
import * as types from "./actionsType";
import axios from "axios";
import { selectSlides } from "./slides.selectors";
export const slidesStart = () => {
return {
type: types.SLIDES_FETCH_START,
};
};
export const slidesSucces = (slides) => {
return {
type: types.SLIDES_FETCH_SUCSSES,
payload: slides,
};
};
export const slidesFail = (error) => {
return {
type: types.SLIDES_FETCH_FAIL,
error: error,
};
};
export const fetchSlides = () => {
return (dispatch) => {
console.log("fetch Start");
dispatch(slidesStart());
axios
.get("http://127.0.0.1:8000/slides/", {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
dispatch(slidesSucces(res.data));
})
.catch((err) => dispatch(slidesFail(err)));
};
};
component
class IntroPage extends Component {
constructor(props) {
super(props);
this.tlitRef = React.createRef();
this.titlelRef = React.createRef();
this.subTitleRef = React.createRef();
this.showcase = React.createRef();
}
componentDidMount() {
this.props.fetchSlides();
}
render() {
const { slides, loading } = this.props;
if (loading) {
return <h1>Loading</h1>;
}
return (
<div className="intro">
<div className="wrapper">
{slides.map((data) => (
<SwiperSlides data={data} key={data.name} />
))}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.slides.loading,
error: state.slides.error,
slides: state.slides.slide_data,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchSlides: () => dispatch(fetchSlides()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(IntroPage);
Register the redux-logger correctly. The data was returned but nothing changes when I do redux-persist and try to reload data come through
update ::
when I change the size of the browser data it correctly appears what is this !!
update : this problem related to swiperjs
and the solution will be like that:
1 - assign swiper instance to React.CreateRef(null) : this.swiper = React.CreateRef(null)
2 - in componentDidUpdate() make a swiper update : this.swiper.current.update()
4 - use a arrow function syntax in swiper on functions to refer to the outer scope

Unable to handle state 'loading' properly in react with redux

Hey guys just moved to redux so in react what i was doing was in componentDidMount(), i was calling api and soon as i received the data i was setting loading to false (initially loading was true) to get rid of the 'react spinner',
but after using redux now in componentDidMount() i am calling my action creater which is in another and there i am receving my data so how do i manage 'loading' here ? can i somehow pass something from action creater to my component that triggers state and set loading to false ? or is there any other to do it ? How do you all manage it ?
here is my code
Home.js
class home extends Component {
UNSAFE_componentWillMount() {
this.props.verifyToken();
}
componentDidMount() {
this.props.categoryAction();
}
constructor(props) {
super(props);
this.state = {
categoriesWithTheirImages: [],
displayToggle: false,
loading: false,
};
}
renderCategory = () => {
return this.props.allCategories.map((item) => {
return (
<div
className="category_div"
key={item._id}
onClick={() => this.setState({ displayToggle: true })}
>
<img
src={item.image}
alt="miss-mistake"
className="category_image_home"
/>
<span className="category_heading_home">{item.categoryName}</span>
</div>
);
});
};
render() {
if (this.state.loading) {
return (
<div className="sweet-loading-main">
<FadeLoader
css={override}
sizeUnit={"px"}
size={50}
color={"#ff9d72"}
loading={this.state.loading}
/>
</div>
);
} else {
console.log(this.props.allCategories);
return (
<React.Fragment>
{/* <Fade left> */}
<Header />
<div className="main_content_homepage">
<p className="category_select">Please select a category</p>
<div className="category_list">{this.renderCategory()}</div>
</div>
{this.renderStoryActionDialog()}
{/* </Fade> */}
</React.Fragment>
);
}
}
}
const mapStateToProps = (state) => {
console.log(state);
const images = [family, ring, beer, feedback, academic];
let categoriesWithImages = state.getCategoryReducer.map((item, index) => {
item.image = images[index];
return item;
});
console.log(categoriesWithImages);
return { allCategories: categoriesWithImages };
};
export default connect(mapStateToProps, { verifyToken, categoryAction })(home);
and my action.js file
import { CATEGORY } from "../actionTypes";
export const categoryAction = ()=> {
return dispatch => {
fetch("http://localhost:3000/api/get_categories", {
method: "GET",
}).then(res=>res.json())
.then(response => {
console.log(response)
dispatch({ type: CATEGORY, payload: response });
})
.catch(err => console.log("Eror in adding", err));
};
};
reducer file
import { USER, CATEGORY} from "../actionTypes";
const getCategoryReducer = (state = [], action) => {
switch (action.type) {
case CATEGORY:
return action.payload;
default:
return state;
}
};
export default getCategoryReducer;
You should handle the loading state in your reducer file. At the moment, it's defined in your Component file. For e.g when you dispatch the action, it should update your loading state too. I would do something like this in reducer.
import { USER, FETCH_CATEGORY, FETCH_CATEGORY_SUCCESS, FETCH_CATEGORY_FAIL} from "../actionTypes";
const INITIAL_STATE = {
loading: false,
err: false,
data: []
}
const getCategoryReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_CATEGORY:
return Object.assign({}, state, {
loading: true,
data: [],
})
case FETCH_CATEGORY_SUCCESS
return Object.assign({}, state, {
loading: false,
data: action.payload,
})
case FETCH_CATEGORY_FAIL
return Object.assign({}, state, {
loading: false,
data: action.payload,
err: true
})
default:
return state;
}
};
export default getCategoryReducer;
and your action file would look something like this
import { FETCH_CATEGORY, FETCH_CATEGORY_SUCCESS, FETCH_CATEGORY_FAIL } from "../actionTypes";
export const categoryAction = ()=> {
//setting loading to true
return dispatch => {
dispatch({ type: FETCH_CATEGORY });
fetch("http://localhost:3000/api/get_categories", {
method: "GET",
}).then(res=>res.json())
.then(response => {
//setting loading to false
dispatch({ type: FETCH_CATEGORY_SUCCESS, payload: response });
})
.catch(err => console.log("Eror in adding", err); dispatch({ type: FETCH_CATEGORY_FAIL, payload: err }););
};
};
You can then read the loading props in your Home.js

How to set a loading in component with Redux when call API

Preface: I'm new to react.
I'm creating a project based on React, Redux.
I want to set a loading status when I press the register button on the register component.
I did a lot of research for a possible solution, but wasn't able to find anything useful for my situation.
What's the best way to fix this?
Register reducer
const initialState = {
pending: false,
users: [],
error: null,
showModal: false,
loading: false
}
export function userReducer(state = initialState, action) {
switch (action.type) {
case 'TOGGLE_LOADING': return {
...state,
loading: !state.loading
}
case 'USER_ADD':
{
debugger;
state.users = state.users.concat(action.payload);
return {
...state,
loading: false,
users: state.users
}
}
case FETCH_USERS_PENDING:
return {
...state,
pending: true,
loading: false
}
case FETCH_USERS_SUCCESS:
return {
...state,
pending: false,
loading: false,
users: action.payload
}
case FETCH_USERS_ERROR:
return {
...state,
pending: false,
loading: false,
error: action.error
}
default:
return state;
}
}
export default userReducer;
Register action
export const userRegisterFetch = user => {
user.Username = user.Mobile;
return dispatch => {
dispatch({ type: 'TOGGLE_LOAD' })
return fetch(`${baseUrl}/users/Register`,
{
method: 'POST', body: JSON.stringify(user), headers: {
'Content-Type': 'application/json',
}
}
)
.then(resp => resp.json())
.then(data => {
if (data.result == undefined) {
return alert('error');
}
if (!data.result) {
alert(data.message);
}
else {
const location = {
pathname: '/Users/Confirm',
state: { mobile: user.Mobile }
}
history.push(location);
}
})
}
}
Register.js component
const { loading } = this.props
return(
{loading ? <Loading /> :
<Form>
...my form
</Form>
}
)
I think you only need 3 reducers to be honest, FETCH_USERS_INIT, FETCH_USERS_SUCCESS and FETCH_USERS_FAIL.
Register reducer
const initialState = {
users: [],
loading: false,
error: null
};
function userReducer(state = initialState, action) {
switch (action.type) {
case 'FETCH_USERS_INIT':
return {
...state,
loading: true
};
case 'FETCH_USERS_SUCCESS':
return {
...state,
loading: false,
error: null,
users: action.payload.users
};
case 'FETCH_USERS_FAIL':
return {
...state,
loading: false,
error: action.payload.error
};
default:
return initialState;
}
}
export default userReducer;
export const userRegisterFetch = user => {
user.Username = user.Mobile;
return async dispatch => {
dispatch({ type: 'FETCH_USERS_INIT' });
fetch(`${baseUrl}/users/Register`, {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
/* dispatch 'FETCH_USERS_SUCCESS' with the list of users */
}).catch(error => {
/* dispatch 'FETCH_USERS_FAIL' with the corresponding error */
});
};
};
The action is not finished but i think it's clear how to finish it. Let me know if you have any doubt.

How to render a route in on the basis of some data

I am new to the react-redux. Now here what I am doing is ,
In this, On click of login , and on the success of this ,
history.push('/');
I am redirecting user on this page.
Now,
import React from 'react';
import { fetchUserJd } from '../action/index';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import JobList from '../../JobList/container/JobList';
class LandingPage extends React.Component {
componentDidMount() {
this.props.fetchJobDescription();
}
render() {
if (this.props.jobs && this.props.jobs.content && this.props.jobs.content.length > 0) {
return <JobList />;
}
else {
return <Redirect to="/create-job" />
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchJobDescription: () => dispatch(fetchUserJd())
}
}
const mapStateToProps = (state) => {
return {
jobs: state.UserJobs.response
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LandingPage);
Here this is my landing page.
Now, Here what I do is I call an api in the componentDidMount and get some data. If data is avaliable then I redirect user to some another page or to some diff route.
const initialState = {
response: [],
error: false
}
case FETCHING_JOBDESCRIPTION_SUCCESS:
return {
...state,
response: action.data,
error: false,
}
This is my reducer where I used the initial state as an empty array.
Now Here what is happening, it is directly going to the else condition to the create-job but the data is avaliable so it should have rendered that component. But, Somehow it is not happening.
Response is like =>
{
"content": [{
"id": "5b7d4a566c5fd00507501051",
"hrmsJdId": null,
"companyId": null,
"jdName": "Senior/ Lead UI Developer",
"jobDescription": null,
}]
}
render() {
if (this.props.isFetching) {
return null;
}
else if (this.props.jobs && this.props.jobs.content && this.props.jobs.content.length > 0) {
return <JobList />;
}
else {
return <Redirect to="/create-job" />
}
}
}
The solution that I used is like this ,
export function fetchUserJd() {
return (dispatch) => {
let url = FETCH_JD_ROOT_URL + page + "&" + size;
dispatch({
type: REQUEST_INITIATED
})
return get(url)
.then((response) => {
if (response.status === 200) {
dispatch(
{
type: FETCHING_JOBDESCRIPTION_SUCCESS,
data: response.payload
}
)
dispatch({
type: REQUEST_SUCCESSED
})
} else {
dispatch({
type: REQUEST_SUCCESSED
})
}
})
}
return Promise.resolve();
};
So, Here I used Promise.resolve Now, in the componentDidMount,
in constructure,
constructor(props) {
super(props);
this.state = {
isloading: true
}
}
componentDidMount() {
this.props.fetchJobDescription().then(() => {
this.setState({
isloading: false
})
});
}
SO, can any one please help me with this issue?

Difference between use mapDispatchToProps and not use?

Would you mind helping me to be clear about mapDispatchToProps.
I have a example code like this:
// ----------------------- not use mapDispatchToProps -----------------------------
//var onSubmit = (event) => {
// event.preventDefault()
// var email = event.target.elements[0].value
// var password = event.target.elements[1].value
// // const path = `/repos/${userName}/${repo}`
// store.dispatch(action.requestLogin({username:email,password:password}))
// // store.dispatch(action.receiveLogin({user{username:email,password:password,objectId:1,sessionToken:"asdfg"}}))
// }
// ----------------------- use mapDispatchToProps -----------------------------
const mapDispatchToProps = (dispatch) => {
return {
onSubmit: (event) => {
event.preventDefault()
var email = event.target.elements[0].value
var password = event.target.elements[1].value
dispatch(action.requestLogin({username:email,password:password}))
}
}
}
const mapStateToProps = state => ({
// onSubmit: onSubmit,
error: state.login.error
});
var LoginPage = ({ onSubmit,error }) => {
return (
`<div className="row">
<div className="col-md-12">
<LoginFormComponent className="account-form text-center" title="Log in to Portal" error={error !== null ? error : ""} onSubmit={onSubmit}/>
</div>
</div>`
)
}
export default connect(mapStateToProps,mapDispatchToProps)(LoginPage)
//-----------------------------and this is the reducer -------------------------------------
export default function login(state = {
logedAt: null,
isLogging: false,
error: null,
data: {},
}, action) {
switch (action.type) {
case types.LOGIN_REQUEST:
return update(state, {
isLogging: { $set: true },
error: { $set: null }
});
case types.LOGIN_SUCCESS:
return update(state, {
data: { $set: action.body },
isLogging: { $set: false },
logedAt: { $set: action.logedAt },
});
case types.LOGIN_FAILURE:
return update(state, {
logedAt: { $set: null },
error: { $set: action.error },
});
default:
return state;
}
}
//-----------------------------and the middleware -------------------------------------
export function login({dispatch, getState}){
return next => action => {
return callLogin().then(
response => dispatch(Object.assign({},{
body: response,
logedAt: Date.now(),
type: LOGIN_SUCCESS,
isFetching: false,
isAuthenticated: true,
// callLogin: callLogin,
})),
error => dispatch(Object.assign({} ,{
error: error.response.text,
type: LOGIN_FAILURE,
isFetching: false,
isAuthenticated: false,
// callLogin: callLogin,
}))
);
}
}
When I don't use the mapDispatchToProps, I just can dispatch the action for type:LOGIN_REQUEST but not the LOGIN_SUCCESS,LOGIN_FAILURE, when use mapDispatchToProps, it work. could you explain for me
Thanks a lot.

Categories