How to create 'My Account Form' in Admin on Rest - reactjs

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);

Related

Can I use Custom Hook inside class component

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

How to configure or test a container with redux-mock-store in 2019?

I configured a container to test with redux-mock-store to the last version and I get some issues. The find() function not works. I ever receive zero nodes and zero length. When I use mount instead to shallow function this works but I get the issues where the redux mapDispatchToProps is not recognized. How I can guarantee that action will be called? I don't wanna test the store but the action function because I use thunk. Is my reasoning right?
My container:
import React, { useState } from 'react'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import styles from './Auth.module.css'
import Input from '../../components/UI/Input/Input'
import Button from '../../components/UI/Button/Button'
import Logo from '../../components/UI/Logo/Logo'
import Spinner from '../../components/UI/Spinner/Spinner'
import { auth as authAction } from '../../store/actions/index'
import { checkValidity } from '../../shared/utility'
export const Auth = (props) => {
const [formIsValid, setFormIsValid] = useState(false)
const [authForm, setAuthForm] = useState({
email: {
elementType: 'input',
elementConfig: {
type: 'email',
placeholder: 'Enter your email'
},
value: '',
validation: {
required: true,
isEmail: true
},
valid: false,
touched: false
},
password: {
elementType: 'input',
elementConfig: {
type: 'password',
placeholder: 'Enter your password'
},
value: '',
validation: {
required: true,
minLength: 6
},
valid: false,
touched: false
},
})
const inputChangeHandler = (event, controlName) => {
const updatedControls = {
...authForm,
[controlName]: {
...authForm[controlName],
value: event.target.value,
valid: checkValidity(event.target.value, authForm[controlName].validation),
touched: true
}
}
let formIsValid = true;
for (let inputIdentifier in updatedControls) {
formIsValid = updatedControls[inputIdentifier].valid && formIsValid
}
setAuthForm(updatedControls)
setFormIsValid(formIsValid)
}
const submitHandler = (event, signup) => {
event.preventDefault()
props.onAuth(
authForm.email.value,
authForm.password.value,
signup
)
}
const formElementsArray = []
for (let key in authForm) {
formElementsArray.push({
id: key,
config: authForm[key]
})
}
let formFields = formElementsArray.map(formElement => (
<Input
key={formElement.id}
elementType={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
invalid={!formElement.config.valid}
shouldValidate={formElement.config.validation}
touched={formElement.config.touched}
changed={(event) => inputChangeHandler(event, formElement.id)} />
))
let form = (
<>
<form onSubmit={(event) => submitHandler(event, false)}>
{formFields}
<Button
disabled={!formIsValid}
btnType="Default">Log In</Button>
</form>
<Button
clicked={(event) => submitHandler(event, true)}
disabled={!formIsValid}
btnType="Link">Sign Up</Button>
</>
)
if (props.loading) {
form = <Spinner />
}
const errorMessage = props.error ? (
<div>
<p style={{ color: "red" }}>{props.error}</p>
</div>
) : null;
let authRedirect = null;
if (props.isAuthenticated) {
authRedirect = <Redirect to={'/'} />
}
return (
<main className={styles.Auth}>
{authRedirect}
<div className={styles.AuthForm}>
<h1>Log in to your account</h1>
<Logo height="3em" />
{errorMessage}
{form}
</div>
</main>
)
}
const mapStateToProps = (state) => {
return {
loading: state.auth.loading,
error: state.auth.error,
isAuthenticated: state.auth.token !== null,
}
}
const mapDispatchToProps = (dispatch) => {
return {
onAuth: (email, password, isSignup) => dispatch(authAction(email, password, isSignup))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Auth)
My test:
import React from 'react';
import { Redirect } from 'react-router-dom';
import thunk from 'redux-thunk';
import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureStore from 'redux-mock-store';
import Auth from './Auth';
import Spinner from '../../components/UI/Spinner/Spinner';
import Button from '../../components/UI/Button/Button';
import Input from '../../components/UI/Input/Input';
configure({ adapter: new Adapter() });
const setup = () => {
const props = {
onAuth: jest.fn()
}
const middlewares = [thunk]
const mockStore = configureStore(middlewares);
const initialState = {
auth: {
token: null,
email: null,
error: null,
loading: false
}
};
const store = mockStore(initialState);
const enzymeWrapper = shallow(<Auth store={store} {...props} />).dive();
return {
enzymeWrapper,
props,
store
}
}
describe('<Auth />', () => {
it('should calls onSubmit prop function when form is submitted', () => {
const { enzymeWrapper: wrapper, props: reduxProps, store } = setup();
const form = wrapper.find('form');
form.simulate('submit', {
preventDefault: () => { }
});
expect(wrapper.props().onAuth).toHaveBeenCalled();
});
});
To be able to test the Auth class without the connection to store, you need to use the named import and not the default import. PFB the line to add in your test file for importing the Auth component:
import { Auth } from './Auth'; // notice the curly braces around the component name
Also, with this approach, you need not pass store as props to the component while rendering, and you can pass the actions as mocked functions (which you are already doing for onAuth action). Also you can use shallow with this approach.

Rerender component child after after state change in parent component

Hello am trying to refresh the graph after changing the value of select option but it shows the first graph and when I change the select option the state is changed but the graph didn't change I think the problem is in lifecycle component when the state changes didn't change only rendred for one time how can I fix it and thank you
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Select from "react-select";
import Graph from "../graph/Graph";
class Home extends Component {
state = {
selectedOption: null
};
handleChange = selectedOption => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
};
render() {
const { user } = this.props.auth;
const { organization } = user;
console.log(organization);
//const organization = user.organization;
console.log(user);
//let organization = user.organization[0];
const options = organization.map(org => ({
value: org.conceptPrefix,
label: org.name
}));
const { selectedOption } = this.state;
let graphObject;
if (selectedOption == null) {
graphObject = <h4>Choose Organization</h4>;
} else {
graphObject = (
<div>
<Graph org={this.state.selectedOption.value} />
</div>
);
}
return (
<div>
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
{graphObject}
</div>
);
}
}
Home.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
graph: state.graph
});
export default connect(
mapStateToProps,
{}
)(Home);
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { graphGet } from "../../actions/graphActions";
import GraphImp from "./GraphImp";
class Graph extends Component {
constructor(props) {
super(props);
this.state = {
org: props.org
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
componentDidMount() {
this.props.graphGet(this.props.org);
}
render() {
// {this.props.graph.graph && this.state.formSubmitted
// ? this.createList()
// : "wait Graph"}
const { graph, loading } = this.props.graph;
let graphContent;
if (graph == null || loading) {
graphContent = <h4>Loading ...</h4>;
} else {
graphContent = <GraphImp grapheData={graph} />;
}
return <div>{graphContent}</div>;
}
}
Graph.prototypes = {
graphGet: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
graph: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
graph: state.graph,
errors: state.errors
});
export default connect(
mapStateToProps,
{ graphGet }
)(Graph);
There are 2 ways to achieve your goal.
First option: Implement componentDidUpdate in Graph
componentDidUpdate(prevProps) {
if(prevProps.org !== this.props.org) {
this.setState({ org: this.props.org });
this.props.graphGet(this.props.org);
}
}
Second option: Force react to fully remount&render your graph whenever you change the option by changing the key (Make sure the key is not an object/array)
<Graph key={this.state.selectedOption.value} org={this.state.selectedOption.value} />

Change data on navbar when logout and login with another account

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();
})
}
}

TypeError: _this.props.onCreate is not a function

Help me out, I am new to React and Javascript
Getting this error:"TypeError: _this.props.onCreate is not a function" although the function has been passed in the props and has been bound.
Here is my current code in react.
UserCreate.js
import React, { Component } from 'react';
class UserCreate extends Component {
constructor(props){
super(props);
this.state = {
email: ''
};
}
handleChange = email => event => {
this.setState(
{
[email]: event.target.value,
}
)
}
handleCreate = () => {
console.log('create', this.state.email);
this.props.onCreate({'email': this.state.email});
}
render() {
let userData = this.props.user && this.props.user.email;
return (
<div>
<h3> New User Form </h3>
<input onChange={this.handleChange('email')} placeholder="Email"/>
<button onClick={this.handleCreate}>Create</button>
</div>
);
}
}
export default UserCreate;
App.js
const USerCreateWithData = compose(
graphql(UserCreateMutation, {
props: (props) => ({
onCreate: (user) => {
props.mutate({
variables: { ...user },
optimisticResponse: () => ({ createUser: { ...user, __typename: 'User'}})
})
}
}
),
options: {
update: (dataProxy, { data: { createUser }}) => {
}
}
})
)(UserCreate);
UserCreateMutation
export default gql`
mutation UserCreateMutation($email: String!){
createUser(
email: $email
) {
__typename
id
email
}
}
`;
What I am doing wrong in here? I have tried every solutions that I have seen on google, stackoverflow but haven't found a solution yet.

Resources