I have created one universal custom spinner/loader hook for my react application , I want to use that loader inside my component where I used to invoke API calls, the problem is the API calls are written inside the class components and as per the react doc we cannot use hooks inside the class component, most of the API calls written inside the class component.
I just want to know is there anyway to achieve the same, as the loader I have created is class based, but I then created a hook for the usage.
LoaderComponent
import React, {Component} from 'react';
import { Spin } from 'antd';
import 'antd/dist/antd.css';
import { LoadingOutlined } from '#ant-design/icons';
export default class LoaderComponent extends React.PureComponent {
render(){
const antIcon = <LoadingOutlined style={{ fontSize: 24 }} spin />;
return (
<div className="fp-container">
<Spin indicator={antIcon}className="fp-loader" alt="loading" />;
</div>)
}
}
useLoader
import React from 'react'
import LoaderComponent from '../index'
export const useLoader = () => {
const[initLoad, setInitLoad]=useState(false)
return [ initLoad ? <LoaderComponent /> : null,
() => setInitLoad(true), //Show loader
() => setInitLoad(false) //Hide Loader
]
}
Component
The below is the component where I want to use the loader, there is two API calls implemneted into the same. I have tried to use the same but not suceeded.
import React, { Component } from 'react';
import { Row, Col , notification} from 'antd';
import WaitingForCallComponent from '#components/WaitingForCallComponent';
import { connect } from 'react-redux';
import { SET_AGENT_DETAILS } from '#actions';
import { SET_CONFIG_SERVER, GET_REGISTER_AGENT } from "#Utils/Urls";
import makeRequest from '#Utils/MakeRequest';
import { sessionService } from "redux-react-session";
import socketConnection from '#Hoc/SocketComponent';
import useLoader from '#Hoc/LoaderComponent/hook';
export class WaitingContainer extends Component {
constructor(props) {
super(props);
this.state = {
extensionNo: "",
agentId: "",
genesysId: "",
username: "",
agentStatus:"",
};
}
componentDidMount = () => {
window.history.pushState(null, document.title, window.location.href);
window.addEventListener('popstate', this.callWindow)
sessionService.loadUser().then((currentUser) => {
this.setState({
username: currentUser.name,
agentId: currentUser.params.AgentID,
genesysId: currentUser.params.genesysID,
}
, () => {
this.setConfig();
});
});
};
callWindow =()=>{
window.history.pushState(null, document.title, window.location.href);
}
handleException = (e) => {
notification.error({
message: 'Agent Registration Error',
description: e?.data?.description,
duration: 0
});
this.setState({
spinLoader: false
});
};
available = (extensionNo, agentId, genesysId) => {
makeRequest
.postAuth(GET_REGISTER_AGENT, {
data: {
extensionNo: extensionNo,
agentId: agentId,
reason: 'unknown',
agentStatus: 'ready',
genesysId: genesysId
}
})
.then((response) => {
if (response && response.data && !response.data.error) {
if (response.data.data.phoneStatus) {
this.props.setExtension({
agentStatus: response.data.data.agentStatus??'ready',
agentSessionId: response.data.data.agentSessionId,
extensionNo: extensionNo,
agentId: agentId,
genesysId: genesysId
});
this.setState({
agentStatus:response.data.data.agentStatus??'ready'
})
setTimeout(() => {
sessionService.loadUser().then((currentUser) => {
if (!currentUser.extraDetails) {
currentUser.extraDetails = {};
}
currentUser.extraDetails.agentStatus = response.data.data.agentStatus;
currentUser.extraDetails.agentSessionId = response.data.data.agentSessionId;
currentUser.extraDetails.extensionNo = extensionNo;
sessionService.saveUser(currentUser).then(() => {
socketConnection(this.props);
});
});
}, 1000);
} else {
this.handleException({
data: {
description: 'Please login into softphone extension ' + extensionNo
}
});
}
} else {
this.handleException(response);
}
})
.catch(this.handleException);
};
setConfig = () => {
sessionService.loadUser().then((currentUser) => {
makeRequest
.postAuth(SET_CONFIG_SERVER, {
data: {
username: currentUser?.params?.username,
},
})
.then((response) => {
if (response?.data?.data.extensionNo ?? false) {
this.setState({
extensionNo: response?.data?.data.extensionNo ?? "",
}, () => {
this.available(this.state.extensionNo, this.state.agentId, this.state.genesysId);
notification.success({
type: "success",
message: "Extension Number",
description: "Extension Verified",
});
})
} else {
notification.error({ type: "error", message: "Extension Number Error" });
}
})
.catch(function (event) {
console.error(event);
});
});
};
render() {
return (
<Row>
<Col span="24" className="lgnpges waitingPage">
<WaitingForCallComponent />
{loader}
</Col>
</Row>
);
}
}
export const mapStateToProps = (state) => {
return {
agentStatus: state?.agentDetails?.agentDetails?.agentStatus,
agentSessionId: state?.agentDetails?.agentDetails?.agentSessionId,
extensionNo: state?.agentDetails?.agentDetails?.extensionNo,
agentId: state?.agentDetails?.agentDetails?.agentId,
genesysId: state?.agentDetails?.agentDetails?.genesysId
};
};
export const mapDispatchToProps = (dispatch) => {
return {
setExtension: (value) => dispatch({ type: SET_AGENT_DETAILS, payLoad: value })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(WaitingContainer);
kindly suggest me the way or either is it required to create class based component for loader
Related
I am trying to load spinner using react-redux hooks (useSelector and useDispatch). I am able to fetch data but not loader (in my case showLoader and hideLoader)
Expectation: when I click the refresh button I want to load spinner (in background it will refresh the data). Before clicking the button I am able to fetch data using useEffect hook.
//ActionCreators.js
export const EVENT_LOG = "EVENT_LOG";
export const EVENT_FAILURE = "EVENT_FAILURE";
export const SHOW_LOADER = "SHOW_LOADER";
export const HIDE_LOADER = "HIDE_LOADER";
//Actions.js
import {
EVENT_LOG,
EVENT_FAILURE,
SHOW_LOADER,
HIDE_LOADER,
} from "./actionCreators";
import { readList } from "./APIUtilsNew";
export const readLogs = (path) => {
return (dispatch) => {
readList(path)
.then((data) =>
dispatch(
{
type: EVENT_LOG,
payload: data,
},
console.log("EventLog Actions: ", data)
)
)
.catch((error) => {
dispatch({
type: EVENT_FAILURE,
payload: error,
});
throw error;
});
};
};
export const showLoader = () => (dispatch) => {
dispatch({
type: SHOW_LOADER,
});
};
export const hideLoader = () => (dispatch) => {
dispatch({
type: HIDE_LOADER,
});
};
//Reducers.js
import {
EVENT_LOG,
EVENT_FAILURE,
HIDE_LOADER,
SHOW_LOADER,
} from "../../actionCreators/index";
export const initialState = {
loading: false,
eventData: [],
eventError: false,
};
const eventReducer = (state = initialState, action) => {
switch (action.type) {
case EVENT_LOG:
return {
...state,
eventData: action.payload,
};
case EVENT_FAILURE:
return {
...state,
eventError: action.payload,
};
case HIDE_LOADER:
return {
...state,
loading: false,
};
case SHOW_LOADER:
return {
...state,
loading: true,
};
default:
return state;
}
};
export default eventReducer;
//React Component
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { readLogs, showLoader, hideLoader } from "./eventActions";
import { FormattedMessage } from "react-intl";
import { XGrid } from "#material-ui/x-grid";
import { CSVLink } from "react-csv";
import IconBtn from "./IconBtn";
import MaterialTheme from "./MaterialTheme";
import { ThemeProvider as MuiThemeProvider } from "#material-ui/core/styles";
import Refresh from "./Refresh";
export default function EventsLog() {
const dispatch = useDispatch();
const eventLogs = useSelector(
(state) => state.eventReducer.eventData.data || []
);
const show = useSelector((state) => state.eventReducer.loading);
const hide = useSelector((state) => state.eventReducer.loading);
useEffect(() => {
dispatch(readLogs("/events"));
}, [dispatch]);
const update = () => {
dispatch(showLoader());
dispatch(hideLoader());
};
let rows = eventLogs.map((obj, index) => {
return (rows = {
id: index + 1,
Time: obj.time,
dateTime: obj.dateTime,
ID: obj.deviceId
});
});
const columns = [
{
field: "Time",
flex: 1,
type: "dateTime",
renderHeader: () => <FormattedMessage id={"time"} />
},
{
field: "dateTime",
flex: 1,
type: "dateTime",
renderHeader: () => <FormattedMessage id={"dateTime"} />
},
{
field: "ID",
flex: 1,
renderHeader: () => <FormattedMessage id={"id"} />
}
];
return (
<div>
<h1>
<FormattedMessage id="event.eventLog" />
<span>
<IconBtn iconLabel="refresh" />
</span>
<CSVLink data={rows} filename={"Log.csv"}>
<IconBtn iconLabel="cloud_download" onClick={update} />
</CSVLink>
</h1>
<div style={{ height: "90%", width: "100%" }}>
<MuiThemeProvider theme={MaterialTheme}>
<Refresh />
<XGrid
pageSize={50}
rowsPerPageOptions={[25, 50, 100]}
rows={rows}
columns={columns}
pagination={true}
hideFooterSelectedRowCount={true}
/>
</MuiThemeProvider>
</div>
</div>
);
}
This is the component where my spinner resides. I want to fetch this component while loading spinner
//Refresh Component
import React from "react";
export default function Refresh() {
return <div>Spinner....</div>;
}
I saw few examples online, where I found everything is in class components
// component Example
class FullPageLoader extends Component {
state = { }
render() {
const {loading} = this.props;
if(!loading) return null;
return (
<div class="loader-container">
<div className="loader">
<img src={LoaderGif} />
</div>
</div>
);
}
}
const mapStateToProps = state => ({ loading: state.application.loading })
export default connect(mapStateToProps)(FullPageLoader);
// Another Component
updateProfile = () =>{
this.props.dispatch( showLoader() )
Axios.post(`https://jsonplaceholder.typicode.com/users`, { user : { name : 'Test User' } })
.then(res => {
console.log( res );
this.props.dispatch( hideLoader() )
})
/* setTimeout(() => {
this.props.dispatch( hideLoader() )
}, 2000); */
}
<Button bsStyle="info" pullRight fill onClick={this.updateProfile} >
Update Profile
</Button>
Can somebody help me how to convert the above class to functional based component and instead of using mapStateToProps to hooks (or) please tell me how to load the spinner using react-redux hooks. I appreciate the help!
More easier way is to show and hide the loader in the action itself. Before the promise, setLoader as true. And in then and catch you can hide loader.
export const readLogs = (path) => {
return (dispatch) => {
showLoader();
readList(path)
.then((data) => {
hideLoader();
dispatch(
{
type: EVENT_LOG,
payload: data,
},
console.log("EventLog Actions: ", data)
)
})
.catch((error) => {
hideLoader();
dispatch({
type: EVENT_FAILURE,
payload: error,
});
throw error;
});
};
};
if it has to be done in the component itself, You can add a delay rather than calling them immediately. There doesn't seem to be any action that is happening here.
const update = () => {
dispatch(showLoader());
setTimeout(() => {
dispatch(hideLoader());
}, 1000);
};
I am using redux to get the async data and response. In the below component when i post recipe and from server i get response through redux the success modal popup twice. reducer is running only once i have checked eveything, only component has problem. the problem could be with the lifecycle method.
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import {withRouter} from 'react-router-dom';
import {connect} from 'react-redux';
import * as actionCreators from '../../actions/recipe-action/index';
import { Modal, Button } from "antd";
import Spinner from '../../UI/spinner';
class PostRecipe extends Component {
state = {
url: '',
visible: false,
}
showModal = () => {
this.setState({ visible: true });
};
onChangeHandler = (e) => {
this.setState({[e.target.name]: e.target.value});
}
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: ""});
this.setState({ visible: false });
};
handleCancel = e => {
this.setState({ visible: false });
};
render() {
const { postRecipes } = this.props;
if(postRecipes.loading) {
return <Spinner />;
}else if(postRecipes.success.ok) {
// this success model popup twice after uploading the recipe
Modal.success({
content: "Recipe Uploaded"
});
}else if(postRecipes.failure.error) {
Modal.error({
title: "Error while uploading recipe",
});
}
return (
<div>
<div>
<Button type="primary" onClick={this.showModal}>
Add Recipe
</Button>
<Modal
title="Add Recipe"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<input
style={{ width: "100%", padding: "5px", fontSize: "15px" }}
type="text"
placeholder="enter the url"
name="url"
value={this.state.url}
onChange={this.onChangeHandler}
/>
</Modal>
</div>
</div>
);
}
}
const mapStateToProps = ({ postRecipeReducers }) => {
const { recipe: { post: postRecipes } } = postRecipeReducers;
return {
postRecipes
}
};
const mapStateToDispatch = dispatch => {
return {
recipe: (url) => dispatch(actionCreators.postRecipes(url))
}
}
export default withRouter(connect(mapStateToProps, mapStateToDispatch)(PostRecipe));
// my action creators
import {POST_RECIPE_LOADING, POST_RECIPE_SUCCESS, POST_RECIPE_FAILURE, POST_RECIPE_RESET} from '../types';
import {GET_RECIPE_LOADING, GET_RECIPE_SUCCESS, GET_RECIPE_FAILURE, GET_RECIPE_RESET} from '../types';
import Parse from 'parse';
export const postRecipes = (url) => async(dispatch) => {
try {
dispatch({type: POST_RECIPE_LOADING, payload: null});
const {data} = await Parse.Cloud.run('post_recipe', {url: url});
dispatch({type: POST_RECIPE_SUCCESS, payload: data});
} catch(e) {
dispatch({type: POST_RECIPE_FAILURE, payload: {message: e.message}})
}
}
export const getRecipes = () => async (dispatch) => {
try {
dispatch({type: GET_RECIPE_LOADING, payload: null});
const {data} = await Parse.Cloud.run('get_recipe');
dispatch({type: GET_RECIPE_SUCCESS, payload: data});
} catch(e) {
dispatch({type: GET_RECIPE_FAILURE, payload: {message: e.message}})
}
};
Try this:
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: "", visible: false});
};
state variable of class is a object with two keys: url and visible. You have to set both at once.
I would try implementing a constructor function to make sure that you have this bound to your local state.
In this code block,
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: ""});
this.setState({ visible: false });
};
you could set the whole state in one line like this,
handleOk = e => {
this.props.recipe(this.state.url);
this.setState({url: "", visible: false});
}
I don't know that this will fix your problem. Just a bit of house keeping.
I'm validating an email field with react-js, antd, and redux, my problem is why does the loading icon disappeared in the input when i integrated redux(created-form.js) but when i remove redux integration, the loading icon is working fine, am i missing something here, or doing something not right?
base-form.js
...
// Constructor
constructor() {
super();
this._validateEmail = _.debounce(this._validateEmail, 1000);
}
// Private method
_validateEmail = (rule, email, callback) => {
const url = 'http://localhost:8000/api/user/isExist';
axios
.post(url, { email })
.then(res => {
if (res.data.isExist) {
callback('Email is already exist');
}
callback();
})
.catch(res => console.log(res));
};
// Render
<Form.Item hasFeedback>
{getFieldDecorator('email', {
rules: [...rules.email, { validator: this._validateEmail }]
})(<Input placeholder="Email" />)}
</Form.Item>
...
created-form.js
import { Form } from 'antd';
import AccSetupForm from './base-form';
function mapPropsToFields(props) {
return {
email: Form.createFormField({
value: props.email
}),
password: Form.createFormField({
value: props.password
}),
confirm_pass: Form.createFormField({
value: props.confirm_pass
})
};
}
function onFieldsChange(props, changedField) {
const field = Object.values(changedField)[0];
if (field !== undefined) {
props.updateAccSetup({
[field.name]: field.value
});
}
}
const CreatedForm = Form.create({ mapPropsToFields, onFieldsChange })(
AccSetupForm
);
export default CreatedForm;
index.js
import { connect } from 'react-redux';
import { updateAccSetup } from '../actions';
import CreatedForm from './created-form';
function mapStateToProps(state) {
return {
email: state.getIn(['registration', 'user', 'email']),
password: state.getIn(['registration', 'user', 'password']),
confirm_pass: state.getIn(['registration', 'user', 'confirm_pass'])
};
}
function mapDispatchToProps(dispatch) {
return {
updateAccSetup: userInfo => dispatch(updateAccSetup(userInfo))
};
}
const StepOne = connect(
mapStateToProps,
mapDispatchToProps
)(CreatedForm);
export default StepOne;
I found the problem, i forgot to add ...props.username inside form.createFormField
/* Antd Docu */
mapPropsToFields(props) {
return {
username: Form.createFormField({
...props.username,
value: props.username.value,
}),
};
},
here are some reference:
https://github.com/ant-design/ant-design/issues/9561
https://ant.design/components/form/#components-form-demo-global-state
Aim :
I want to put firstName and lastName on my Navbar. So, I'm using axios request by id with userId
EDIT: Thanks to #Isaac, I have no more infinite loop when I'm using componentWillUpdate() now.
Problem : Data doesn't change (firstName and lastName) when I'm logout and login with another account
No problems from servers.
here a picture :
Description : I've login as a & g (firstName and lastName), then I've logout and login as j & j.
navbar.js:
import React, { Component } from 'react';
import { fade } from '#material-ui/core/styles/colorManipulator';
import { withStyles } from '#material-ui/core/styles';
import { connect } from 'react-redux';
import AuthA from '../store/actions/AuthA';
import { withRouter } from 'react-router-dom';
import '../Navbar.css';
import NavbarV from './NavbarV';
import PropTypes from 'prop-types';
import axios from 'axios';
class NavbarC extends Component {
constructor(props){
super(props);
this.state = {
client:[]
}
}
componentWillMount(){
this.getUser();
}
getUser(){
axios.get (`http://localhost:3002/api/clients/${localStorage.getItem("userId")}?access_token=${localStorage.getItem("token")}`)
.then(res => {
this.setState({client: res.data}, () => {
console.log(this.state)
})
})
}
shouldComponentUpdate(nextState){
return (this.state.client.firstName !== nextState.firstName ||
this.state.client.lastName !== nextState.lastName);
}
componentWillUpdate(){
this.getUser();
console.log(this.state)
}
logout = () => {
this.props.authfn.logout();
};
render() {
return(
<NavbarV logout = {this.logout}
firstName={this.state.client.firstName}
lastName={this.state.client.lastName}
userId={this.props.userId}
auth = {this.props.auth}
classes={this.props.classes}/>
)
}
}
NavbarC.propTypes = {
auth: PropTypes.bool.isRequired,
firstName: PropTypes.string.isRequired,
lastName: PropTypes.string.isRequired
};
const mapStateToProps = (state) => {
return {
auth: state.AuthR.auth,
firstName: state.AuthR.firstName,
lastName: state.AuthR.lastName,
userId: state.AuthR.userId
};
};
const mapDispatchToProps = dispatch => {
return {
authfn: AuthA(dispatch)
}
};
export default connect(mapStateToProps, mapDispatchToProps) (withStyles(styles)(withRouter(NavbarC)));
If someone have a solution or any questions, I'm here :)
thank you all in advance
First of all, you should avoid componentWillUpdate lifecycle as it's been deprecated.
And for your case, this.getUser(); will be triggered to pull data which then trigger this.setState({client: res.data}). When the app executing this.setState(), your component will be re-render so there's no need to have any other componentLifeCycle.
class NavbarC extends Component {
state = { client:[], userID: null, token: null };
componentDidMount(){
this.setState({
userID: localStorage.getItem("userId"),
token: localStorage.getItem("token")
}, () => {
this.getUser();
})
}
getUser(){
axios.get (`http://localhost:3002/api/clients/${this.state.userID}?access_token=${this.state.token}`)
.then(res => {
this.setState({ client: res.data }, () => {
console.log(this.state)
})
})
}
componentDidUpdate(prevProps, prevState){
if(prevState.userID !== this.state.userID) {
this.getUser();
}
}
logout = () => this.props.authfn.logout();
render() {
return(
<NavbarV
logout = {this.logout}
firstName={this.state.client.firstName}
lastName={this.state.client.lastName}
userId={this.props.userId}
auth = {this.props.auth}
classes={this.props.classes} />
)}
}
I solve it !
This is a solution :
componentDidMount(){
this.setState({
userId: localStorage.getItem("userId"),
token: localStorage.getItem("token")
}, () => {
this.getUser();
})
}
getUser = () => {
axios.get (`http://localhost:3002/api/clients/${this.state.userId}?access_token=${this.state.token}`)
.then(res => {
this.setState({ client: res.data, userId: localStorage.getItem("userId") }, () => {
console.log(this.state)
})
})
}
componentDidUpdate(prevProps, prevState){
if(prevState.userId !== this.props.userId) {
this.setState({ userId: this.props.userId }, () => {
this.getUser();
})
}
}
I'm using Admin on Rest 1.2 and I want to create a custom 'My Account Page' for the dashboard. So I created a custom route for that and I prepared a custom query to API which works perfectly. But when I want to get the values from this.state to Edit Component I've got error Cannot read property '_currentElement' of null. How should I pass custom props to Edit component?
My custom page looks like that:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import AvatarUpload from './AvatarUploadComponent';
// import FlatButton from 'material-ui/FlatButton';
import { showNotification as showNotificationAction } from 'admin-on-rest';
import { push as pushAction } from 'react-router-redux';
import {TextField, RaisedButton} from 'material-ui';
import { GET_ONE, UPDATE, SimpleForm, DisabledInput, LongTextInput, Edit } from 'admin-on-rest';
import restClient from '../RestClient';
const initialState = {
user: {
first_name: "",
last_name: "",
current_password: "",
new_password: "",
confirm_password: "",
avatar_url: ""
}
};
export const MyAccountEdit = (props) => (
<Edit title="My account" {...props}>
<SimpleForm>
<DisabledInput source="first_name" />
<LongTextInput source="last_name" />
<LongTextInput source="first_name" />
</SimpleForm>
</Edit>
);
class MyAccountForm extends Component {
constructor(props) {
super(props);
this.state = initialState;
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.componentWillMount = this.componentWillMount.bind(this);
}
componentWillMount = () => {
const { showNotification } = this.props;
restClient(GET_ONE, 'users', { id: 2 })
.then((req) => {
this.setState({user: req.data});
showNotification('Account has been getted');
// push('/myaccount');
})
.catch((e) => {
console.error(e);
showNotification('Error: Account hasn\'t been getted', 'warning');
});
};
handleChange = (event, newValue) => {
event.persist(); // allow native event access (see: https://facebook.github.io/react/docs/events.html)
// give react a function to set the state asynchronously.
// here it's using the "name" value set on the TextField
// to set state.person.[firstname|lastname].
this.setState((state) => state.user[event.target.name] = newValue);
};
handleClick = () => {
const { push, record, showNotification } = this.props;
// const updatedRecord = { ...record, is_approved: true };
console.log(this.state);
restClient(UPDATE, 'users', { id: 2, data: this.state })
.then(() => {
showNotification('Account has been updated');
push('/myaccount');
})
.catch((e) => {
console.error(e);
showNotification('Error: Account hasn\'t been updated', 'warning');
});
};
render() {
return (<div>
<p>My Account</p>
<img src={this.state.user.avatar_url} />
<p>first name: {this.state.user.first_name}</p>
//below is the problem
<MyAccountEdit {...this.state.user}/>
</div>);
}
}
MyAccountForm.propTypes = {
push: PropTypes.func,
record: PropTypes.object,
showNotification: PropTypes.func,
};
export default connect(null, {
showNotification: showNotificationAction,
push: pushAction,
})(MyAccountForm);