Reactjs: Fetch data from API using helper class and functions - javascript

I'm beginner for Reactjs and trying to improve myself with a project. As progressing with my project, I now need to setup a structure such that my UI component will connect to some REST API, and use the returned data. This has many examples on the internet and fine.
My question is, if and how I could separate the API connection from the actual UI components. I believe it could be good, as I would have the chance to reuse the API connection functions within various UI components. (It would be ideal to do the common tasks related with API connections within these helper methods.)
For that, I created a PrimaryForm.js file, that is the UI component. And for API calls, I created an APIManager.js file. Ideally, APIManager should not have any jsx, but only functions that return API call results to the PrimaryForm.js.
I am sharing the code I wrote so far, to achieve that.
PrimaryForm.js, that deletages the API call to APIManager.js (see handleTestConnection part below):
import React from 'react';
import withStyles from '#material-ui/styles/withStyles';
import {Link, withRouter } from 'react-router-dom';
import Paper from '#material-ui/core/Paper';
import TextField from '#material-ui/core/TextField';
import Typography from '#material-ui/core/Typography';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import InputLabel from '#material-ui/core/InputLabel';
import OutlinedInput from '#material-ui/core/OutlinedInput';
import Select from '#material-ui/core/Select';
import FormHelperText from '#material-ui/core/FormHelperText';
import PrimaryFormValidator from '../../validators/PrimaryFormValidator'
import styles from '../../Styles';
import Grid from '#material-ui/core/Grid';
import Tooltip from '#material-ui/core/Tooltip';
import CancelIcon from '#material-ui/icons/Cancel';
import BackIcon from '#material-ui/icons/ArrowBackIosRounded';
import TestIcon from '#material-ui/icons/Power';
import ForwardIcon from '#material-ui/icons/ArrowForwardIosRounded';
import Button from '#material-ui/core/Button';
import APIManager from '../../managers/APIManager';
function PrimaryForm(props) {
const { classes } = props;
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
React.useEffect(() => {setLabelWidth(inputLabel.current.offsetWidth);}, []);
const [state, setState] = React.useState({
hostname: {
value: "test",
isError: false,
errorText: "",
},
serverIp: {
value: "192.168.16.1",
isError: false,
errorText: "",
},
osVariant: {
value: "Linux",
isError: false,
errorText: "",
},
databaseSid: {
value: "mysql",
isError: false,
errorText: "",
},
listenerPort: {
value: "3306",
isError: false,
errorText: "",
},
isFormValid: true,
isPrimaryDbValid: false,
});
const evaluateFormValid = (prevState) => {
return ((prevState.hostname.value!=="" && !prevState.hostname.isError) &&
(prevState.serverIp.value!=="" && !prevState.serverIp.isError) &&
(prevState.osVariant.value!=="" && !prevState.osVariant.isError) &&
(prevState.databaseSid.value!=="" && !prevState.databaseSid.isError) &&
(prevState.listenerPort.value!=="" && !prevState.listenerPort.isError));
};
const handleChange = event => {
var valResult;
switch (event.target.id) {
case 'hostname':
valResult = PrimaryFormValidator.validateHostname(event.target.value, event.target.labels[0].textContent);
setState({
...state,
hostname:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
},
});
break;
case 'serverIp':
valResult = PrimaryFormValidator.validateIpAddress(event.target.value, event.target.labels[0].textContent);
setState({
...state,
serverIp:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
case 'databaseSid':
valResult = PrimaryFormValidator.validateDatabaseSid(event.target.value, event.target.labels[0].textContent);
setState({
...state,
databaseSid:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
case 'listenerPort':
valResult = PrimaryFormValidator.validateListenerPort(event.target.value, event.target.labels[0].textContent);
setState({
...state,
listenerPort:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
default:
//setState({...state,});
}
setState(prevState => ({
...prevState,
isFormValid: evaluateFormValid(prevState),
}));
}
const handleTestConnection = event => {
APIManager.testConnection(state.hostname.value, state.serverIp.value, state.osVariant.value, state.databaseSid.value, state.listenerPort.value);
//console.log("Data:" + APIManager.state.testConnectionResult);
}
const handleSelect = osVariant => event => {
var valResult = PrimaryFormValidator.validateOsVariant(event.target.value, inputLabel.current.textContent);
setState(prevState => ({
...prevState,
osVariant:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
},
}));
setState(prevState => ({
...prevState,
isFormValid: evaluateFormValid(prevState),
}));
}
return (
<React.Fragment>
<div className={classes.bigContainer}>
<Paper className={classes.paper}>
<div>
<div>
<Typography variant="subtitle1" gutterBottom className={classes.subtitle1} color='secondary'>
Primary Database System
</Typography>
<Typography variant="body1" gutterBottom>
Information related with the primary database system. Please note that the primary database has to be up and running.
</Typography>
</div>
<div className={classes.bigContainer}>
<form className={classes.formArea}>
<TextField className={classes.formControl}
id="hostname"
label="FQDN Hostname *"
onChange={handleChange}
value={state.hostname.value}
error={state.hostname.isError}
helperText={state.hostname.errorText}
variant="outlined" autoComplete="off" />
<TextField className={classes.formControl}
id="serverIp"
label="Server Ip Address *"
onChange={handleChange}
value={state.serverIp.value}
error={state.serverIp.isError}
helperText={state.serverIp.errorText}
variant="outlined" autoComplete="off" />
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel id="osVarLabel" htmlFor="osVariant" ref={inputLabel}>OS Variant *</InputLabel>
<Select
id="osVariant"
label="OS Variant *"
value={state.osVariant.value}
error={state.osVariant.isError}
onChange={handleSelect("osVariant")}
input={<OutlinedInput id="outlinedinput" labelWidth={labelWidth}/>}>
<MenuItem value={''}></MenuItem>
<MenuItem value={'Linux'}>Linux</MenuItem>
<MenuItem value={'Windows'}>Windows</MenuItem>
</Select>
<FormHelperText error={state.osVariant.isError} hidden={!state.osVariant.isError}>
{state.osVariant.errorText}
</FormHelperText>
</FormControl>
<TextField className={classes.formControl}
id="databaseSid"
label="Database SID"
onChange={handleChange}
value={state.databaseSid.value}
error={state.databaseSid.isError}
helperText={state.databaseSid.errorText}
variant="outlined" autoComplete="off" />
<TextField className={classes.formControl}
id="listenerPort"
label="Listener Port"
onChange={handleChange}
value={state.listenerPort.value}
error={state.listenerPort.isError}
helperText={state.listenerPort.errorText}
variant="outlined" autoComplete="off" />
{/* <TextField className={classes.formControl}
disabled={true}
id="isFormValid"
label="Is Form Valid Now?"
value={state.isFormValid}
variant="outlined" autoComplete="off" /> */}
</form>
</div>
</div>
</Paper>
<Grid container spacing={2} className={classes.grid}>
<Grid item xs={12}>
<div className={classes.flexBar}>
<Tooltip title="Back to previous step">
<div>
<Button variant="contained"
disabled={true}
className={classes.actionButton}
size='large'>
<BackIcon className={classes.rightIcon}/>Back
</Button>
</div>
</Tooltip>
<Tooltip title="Test Connection">
<div>
<Button variant="contained" className={classes.actionButton}
color="primary"
size='large'
disabled={!state.isFormValid}
onClick={handleTestConnection}>
<TestIcon className={classes.rightIcon}/>Test
</Button>
</div>
</Tooltip>
<Tooltip title="Proceed the next step">
<div>
<Button
variant="contained" className={classes.actionButton}
color="primary"
size='large'
disabled={!state.isPrimaryDbValid}>
<ForwardIcon className={classes.rightIcon} />Next
</Button>
</div>
</Tooltip>
<Tooltip title="Cancel creating new configuration">
<Button variant="contained" color="default" className={classes.actionButton}
component={Link} to={'/configs'} style={{ marginLeft: 'auto' }}>
<CancelIcon className={classes.rightIcon} />Cancel
</Button>
</Tooltip>
</div>
</Grid>
</Grid>
</div>
</React.Fragment>
)
}
export default withRouter(withStyles(styles)(PrimaryForm));
And here is my APIManager.js file:
import React, { Component } from 'react';
export default class APIManager extends Component{
constructor(props) {
super(props);
this.state = {
testConnectionResult: {},
...this.props,
}
this.testConnection = this.testConnection.bind(this);
}
static testConnection(hostname, serverIp, osVariant, databaseSid, listenerPort) {
fetch(`http://localhost:8000/api?objtype=ConnectionDef&hostname=${hostname}&serverIp=${serverIp}&osVariant=${osVariant}&databaseSid=${databaseSid}&listenerPort=${listenerPort}`)
.then(response => response.json())
.then(result => this.setState({testConnectionResult: result}));
//.catch((error) => console.error(error));
console.log("Data:" + this.testConnectionResult);
return this.testConnectionResult;
}
}
Now I could not solve getting below error:
APIManager.js:16 Uncaught (in promise) TypeError: _this2.setState is
not a function
at APIManager.js:16
I guess I'm struggling now to pass the result taken from API call to the external (callee) class / function.
I Googled, also checked other questions on Stackoverflow with this error, but that really didn't help me figure out the issue. I also wanted to ask if I'm making a principal mistake.
Any help would be appreciated. Thanks.

you don't have to create a component just to hold API call logic. in react components are used to declare visual stuff(so they will have jsx).
If you want to extract api call logic out of component, you can put that logic in some function which returns a promise. like so:
function testApi(...args) {
return fetch(/* url parms */)
}
then in your component, on some event say click you can make API call and set response data to component state to change view:
handleTestEvent(...args) {
testApi(...args)
.then(response => response.json())
.then(result => this.setState({testConnectionResult: result}));
.catch((error) => {
console.error(error));
this.setState({testConnectionResult: []});
});
}

You can't access this (an instance context) from a static method. Make it a regular instance method and it should work.

Related

Issue in react context api

Facing issues in react context api, getting undefined while transferring anything using context api.
getting this error, getting undefined when sending functions through context api.
*** Context.js
import React, { useReducer, createContext } from "react";
import contextReducer from "./contextReducer";
const initialState = [];
export const ExpenseTrackerContext = createContext(initialState);
export function Provider({ children }) {
const [transactions, dispatch] = useReducer(contextReducer, initialState);`enter code here`
const deleteTransaction = (id) =>
dispatch({ type: "DELETE_TRANSACTION", payload: id });
const addTransaction = (transaction) =>
dispatch({ type: "ADD_TRANSACTION", payload: transaction });
return (
<ExpenseTrackerContext.Provider
value={{
deleteTransaction,
addTransaction,
transactions,
}}
>
{children}
</ExpenseTrackerContext.Provider>
);
}***
getting undefined in this file while using the function in this file Form.js > and getting error addTransaction is not a function
*** Form.js
import React, { useState, useContext } from "react";
import {
TextField,
Typography,
Grid,
FormControl,
InputLabel,
Select,
MenuItem,
Button,
} from "#material-ui/core";
import { ExpenseTrackerContext } from "../../context/context";
import { v4 as uuidv4 } from "uuid";
import useStyles from "./styles";
const initialState = {
amount: "",
category: "",
type: "Income",
date: new Date(),
};
const Form = (props) => {
const classes = useStyles();
const [formData, setFormData] = useState(initialState);
// console.log(useContext(ExpenseTrackerContext));
const { addTransaction } = useContext(ExpenseTrackerContext);
console.log("context: " + ExpenseTrackerContext.displayName);
console.log("add: " + typeof addTransaction);
const createTransaction = () => {
const transaction = {
...formData,
amount: Number(formData.amount),
id: uuidv4(),
};
addTransaction(transaction);
setFormData(initialState);
};
return (
<Grid container spacing={2}>
<Grid item xs={12}>
<Typography align="center" variant="subtitle2" gutterBottom>
...
</Typography>
</Grid>
<Grid item xs={6}>
<FormControl fullWidth>
<InputLabel>Type</InputLabel>
<Select
value={formData.type}
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
>
<MenuItem value="Income">Income</MenuItem>
<MenuItem value="Expense">Expense</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={6}>
<FormControl fullWidth>
<InputLabel>Category</InputLabel>
<Select
value={formData.category}
onChange={(e) =>
setFormData({ ...formData, category: e.target.value })
}
>
<MenuItem value="business">Business</MenuItem>
<MenuItem value="Salary">Salary</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={6}>
<TextField
type="number"
label="Amount"
fullWidth
value={formData.amount}
onChange={(e) => setFormData({ ...formData, amount: e.target.value })}
/>
</Grid>
<Grid item xs={6}>
<TextField
type="date"
label=" "
fullWidth
value={formData.date}
onChange={(e) => setFormData({ ...formData, date: e.target.value })}
/>
</Grid>
<Button
className={classes.button}
variant="outlined"
color="primary"
fullWidth
onClick={createTransaction}
>
Create
</Button>
</Grid>
);
};
export default Form; ***
why?
you are forget to call the context with in From.js. Because you are created the context hook with same page
Better create the context file seperate js file
context.js
import React,{createContext} from 'react';
export const ExpenseTrackerContext = createContext(null);
Contextr.js
import {ExpenseTrackerContext} from './context.js' // path of context.js
From.js
import React,{useContext} from 'react';
import {ExpenseTrackerContext} from './context.js' // path of context.js
export default function From(props){
const expContext = useContext(ExpenseTrackerContext);
expContext.addTransaction()// you could call like this
...
Updated - React Doc
Accepts a context object (the value returned from React.createContext)
and returns the current context value for that context. The current
context value is determined by the value prop of the nearest
<MyContext.Provider> above the calling component in the tree.
Context passing value only with in tree of children. please check Form.js is execute inside the {children} component of context provider. if you are using parallel or sibling component its does not work
<ExpenseTrackerContext.Provider
value={{
deleteTransaction,
addTransaction,
transactions,
}}
>
{children} // From.js present in this children in any one of the tree otherwise undefined (value only passed inside the provider component tree)
</ExpenseTrackerContext.Provider>

MaterialUI TextField lag issue. How to improve performance when the form has many inputs?

I encountered an annoying problem with Textfield using MateriaUI framework. I have a form with many inputs and it seems to be a bit laggy when typing or deleting values inside the fields. In other components when there are like 2 or 3 inputs there's no lag at all.
EDIT: The problem seems to be with my onChange handler.
Any help is much appreciated. Thanks in advance.
This is my custom input code:
import React, { useReducer, useEffect } from 'react';
import { validate } from '../utils/validators';
import TextField from '#material-ui/core/TextField';
import { ThemeProvider, makeStyles, createMuiTheme } from '#material-ui/core/styles';
import { green } from '#material-ui/core/colors';
const useStyles = makeStyles((theme) => ({
root: {
color: 'white'
},
input: {
margin: '10px',
'&& .MuiInput-underline:before': {
borderBottomColor: 'white'
},
},
label: {
color: 'white'
}
}));
const theme = createMuiTheme({
palette: {
primary: green,
},
});
const inputReducer = (state, action) => {
switch (action.type) {
case 'CHANGE':
return {
...state,
value: action.val,
isValid: validate(action.val, action.validators)
};
case 'TOUCH': {
return {
...state,
isTouched: true
}
}
default:
return state;
}
};
const Input = props => {
const [inputState, dispatch] = useReducer(inputReducer, {
value: props.initialValue || '',
isTouched: false,
isValid: props.initialValid || false
});
const { id, onInput } = props;
const { value, isValid } = inputState;
useEffect(() => {
onInput(id, value, isValid)
}, [id, value, isValid, onInput]);
const changeHandler = event => {
dispatch({
type: 'CHANGE',
val: event.target.value,
validators: props.validators
});
};
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
const classes = useStyles();
return (
<ThemeProvider theme={theme}>
<TextField
className={classes.input}
InputProps={{
className: classes.root
}}
InputLabelProps={{
className: classes.label
}}
id={props.id}
type={props.type}
label={props.label}
onChange={changeHandler}
onBlur={touchHandler}
value={inputState.value}
title={props.title}
error={!inputState.isValid && inputState.isTouched}
helperText={!inputState.isValid && inputState.isTouched && props.errorText}
/>
</ThemeProvider>
);
};
export default Input;
In addition to #jony89 's answer. You can try 1 more workaround as following.
On each keypress (onChange) update the local state.
On blur event call the parent's change handler
const Child = ({ parentInputValue, changeValue }) => {
const [localValue, setLocalValue] = React.useState(parentInputValue);
return <TextInputField
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
onBlur={() => changeValue(localValue)} />;
}
const Parent = () => {
const [valMap, setValMap] = React.useState({
child1: '',
child2: ''
});
return (<>
<Child parentInputValue={valMap.child1} changeValue={(val) => setValMap({...valMap, child1: val})}
<Child parentInputValue={valMap.child2} changeValue={(val) => setValMap({...valMap, child2: val})}
</>
);
}
This will solve your problems if you do not want to refactor the existing code.
But the actual fix would be splitting the state so that update in the state of child1 doesn't affect (change reference or mutate) state of child2.
Make sure to extract all constant values outside of the render scope.
For example, each render you are providing new object to InputLabelProps and InputProps which forces re-render of child components.
So every new object that is not must be created within the functional component, you should extract outside,
That includes :
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
color: 'white'
},
input: {
margin: '10px',
'&& .MuiInput-underline:before': {
borderBottomColor: 'white'
},
},
label: {
color: 'white'
}
}));
const theme = createMuiTheme({
palette: {
primary: green,
},
});
Also you can use react memo for function component optimization, seems fit to your case.
I managed to get rid of this lagging effect by replacing normal <TextField /> by <Controller /> from react-hook-form.
Old code with Typing Lag
<Grid item xs={12}>
<TextField
error={descriptionError.length > 0}
helperText={descriptionError}
id="outlined-textarea"
onChange={onDescriptionChange}
required
placeholder="Nice placeholder"
value={description}
rows={4}
fullWidth
multiline
/>
</Grid>
Updated Code with react-hook-form
import { FormProvider, useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
const methods = useForm({
resolver: yupResolver(validationSchema)
});
const { handleSubmit, errors, reset } = methods;
const onSubmit = async (entry) => {
console.log(`This is the value entered in TextField ${entry.name}`);
};
<form onSubmit={handleSubmit(onSubmit)}>
<Grid item xs={12}>
<FormProvider fullWidth {...methods}>
<DescriptionFormInput
fullWidth
name="name"
placeholder="Nice placeholder here"
size={matchesXS ? 'small' : 'medium'}
bug={errors}
/>
</FormProvider>
</Grid>
<Button
type="submit"
variant="contained"
className={classes.btnSecondary}
startIcon={<LayersTwoToneIcon />}
color="secondary"
size={'small'}
sx={{ mt: 0.5 }}
>
ADD
</Button>
</form>
import React from 'react';
import PropTypes from 'prop-types';
import { Controller, useFormContext } from 'react-hook-form';
import { FormHelperText, Grid, TextField } from '#material-ui/core';
const DescriptionFormInput = ({ bug, label, name, required, ...others }) => {
const { control } = useFormContext();
let isError = false;
let errorMessage = '';
if (bug && Object.prototype.hasOwnProperty.call(bug, name)) {
isError = true;
errorMessage = bug[name].message;
}
return (
<>
<Controller
as={TextField}
name={name}
control={control}
defaultValue=""
label={label}
fullWidth
InputLabelProps={{
className: required ? 'required-label' : '',
required: required || false
}}
error={isError}
{...others}
/>
{errorMessage && (
<Grid item xs={12}>
<FormHelperText error>{errorMessage}</FormHelperText>
</Grid>
)}
</>
);
};
With this use of react-hook-form I could get the TextField more responsive than earlier.

Cannot read property 'indexOf' of null, be expected to be react lifecycle problem

I had fetched user data from local API server, which sends JSON data, and it worked before I added searching feature. I recognized the problem that these error occurs from React's lifecycle style, but because I haven't studied about React much, so I'm asking question to solve this problem with you.
macOS Mojave, Express.js, React.js and React-Router are running my local dev server. I never tried this searching feature before.
These are my code:
UsersList.js:
import React, {Component} from 'react';
import {List, Avatar, Button, Modal, Input} from 'antd';
import UserItem from "../components/UserItem";
import blue_check from '...';
const {Search} = Input;
class UsersList extends Component {
state = {
users: [],
modalVisible: false,
dataKey: 0,
keyword: '',
};
constructor(props) {
super(props);
this.state = {
users: [{
member_idx: 0,
nickname: 'loading...',
}]
};
}
componentDidMount() {
fetch('/api/getUsers')
.then(res => res.json())
.then(users => this.setState({users: users}));
}
showModal = (key) => {
this.setState({
modalVisible: true,
dataKey: key
});
};
handleClose = e => {
console.log(e);
this.setState({
modalVisible: false
});
};
handleSearch = (e) => {
this.setState({
keyword: e.target.value,
})
};
render() {
const {users, keyword} = this.state;
const filteredUsers = users.filter(
user => user.nickname.indexOf(keyword) !== -1
); // This line makes error
return (
<div>
<Search
placeholder="Input Search Text"
enterButton="Search"
onSearch={value => this.state.handleSearch(value)}
size="large"
/>
<List
itemLayout="horizontal"
dataSource={filteredUsers}
renderItem={item => (
<List.Item
actions={[<a onClick={() => this.showModal(item.member_idx)}>상세정보</a>, <a>이 프로필을...</a>]}>
<List.Item.Meta
style={{
display: 'flex',
alignItems: 'center'
}}
avatar={<Avatar src={item.image}/>}
title={
<span>{item.nickname === null ? "<undefined>" : item.nickname} {item.blue_check === 1 &&
<img src={blue_check} alt="BLUE" style={{height: '16px'}}/>
}</span>}
description={
<span><strong>{item.follow_count}</strong> Follower{item.follow_count !== 1 && "s"}</span>}
/>
</List.Item>
)}
/>
<Modal
title="상세 정보"
visible={this.state.modalVisible}
onClose={this.handleClose}
footer={[
<Button key="back" onClick={this.handleClose}>
닫기
</Button>
]}
centered
>
<UserItem dataKey={this.state.dataKey} users={this.state.users}/>
</Modal>
</div>
);
}
}
export default UsersList;
UserItem.js doesn't need in this post because it has no problem about this error.
I linked the error trace image here. Sorry about inconvenience.
It is possible that in your data nickname is null for one or more users and hence you should make a check for it too while filtering
const filteredUsers = users.filter(
user => user.nickname && user.nickname.indexOf(keyword) !== -1
);

Transforming values from react template for further use in states

This might be a duplicate question but I still couldn't find an answer. I am new to react and is struggling to create a login page. I am trying t set states with the values I have just entered in the application for further usage. Using these values I will be making a backed call for authentication.
My React Code
import React from "react";
// core components are added
class LoginPage extends React.Component {
constructor(props) {
super(props);
// we use this to make the card to appear after the page has been rendered
this.state = {
cardAnimaton: "cardHidden"
};
}
handleSubmit() {
console.log(this);
}
handleChange(event) {
console.log(event)
}
componentDidMount() {
// we add a hidden class to the card and after 700 ms we delete it and the transition appears
setTimeout(
function() {
this.setState({ cardAnimaton: "" });
}.bind(this),
700
);
}
render() {
const { classes, ...rest } = this.props;
return (
<div>
<div className={classes.container}>
<GridContainer justify="center">
<GridItem xs={12} sm={12} md={12}>
<Card className={classes[this.state.cardAnimaton]}>
<form className={classes.form} onSubmit = {this.handleSubmit}>
<CardHeader color="primary" className={classes.cardHeader}>
<h4>Login</h4>
</CardHeader>
<CardBody>
<CustomInput
labelText="Email..."
id="email"
onChange={this.handleChange}
value = {this.state.email}
formControlProps={{
fullWidth: true
}}
inputProps={{
type: "email",
endAdornment: (
<InputAdornment position="end">
<Email className={classes.inputIconsColor} />
</InputAdornment>
)
}}
/>
</CardBody>
<CardFooter className={classes.cardFooter}>
<Button color="primary" size="lg" type="submit">
Login
</Button>
</CardFooter>
</form>
</Card>
</GridItem>
</GridContainer>
</div>
</div>
);
}
}
export default withStyles(loginPageStyle)(LoginPage);
I am trying to send values from my HTML to state in handleChange but it is not reacting whenever I make a change.
If it something with states, please let me know. I am using a custom theme and hope that the issue is in my implementation and have nothing to do with the theme.
Custom Input Component
import React from "react";
// nodejs library to set properties for components
import PropTypes from "prop-types";
// nodejs library that concatenates classes
import classNames from "classnames";
// #material-ui/core components
import withStyles from "#material-ui/core/styles/withStyles";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Input from "#material-ui/core/Input";
import customInputStyle from "assets/jss/material-kit-react/components/customInputStyle.jsx";
function CustomInput({ ...props }) {
const {
classes,
formControlProps,
labelText,
id,
labelProps,
inputProps,
error,
white,
inputRootCustomClasses,
success
} = props;
const labelClasses = classNames({
[" " + classes.labelRootError]: error,
[" " + classes.labelRootSuccess]: success && !error
});
const underlineClasses = classNames({
[classes.underlineError]: error,
[classes.underlineSuccess]: success && !error,
[classes.underline]: true,
[classes.whiteUnderline]: white
});
const marginTop = classNames({
[inputRootCustomClasses]: inputRootCustomClasses !== undefined
});
const inputClasses = classNames({
[classes.input]: true,
[classes.whiteInput]: white
});
var formControlClasses;
if (formControlProps !== undefined) {
formControlClasses = classNames(
formControlProps.className,
classes.formControl
);
} else {
formControlClasses = classes.formControl;
}
return (
<FormControl {...formControlProps} className={formControlClasses}>
{labelText !== undefined ? (
<InputLabel
className={classes.labelRoot + " " + labelClasses}
htmlFor={id}
{...labelProps}
>
{labelText}
</InputLabel>
) : null}
<Input
classes={{
input: inputClasses,
root: marginTop,
disabled: classes.disabled,
underline: underlineClasses
}}
id={id}
{...inputProps}
/>
</FormControl>
);
}
CustomInput.propTypes = {
classes: PropTypes.object.isRequired,
labelText: PropTypes.node,
labelProps: PropTypes.object,
id: PropTypes.string,
inputProps: PropTypes.object,
formControlProps: PropTypes.object,
inputRootCustomClasses: PropTypes.string,
error: PropTypes.bool,
success: PropTypes.bool,
white: PropTypes.bool
};
export default withStyles(customInputStyle)(CustomInput);

How can I avoid to get too much recursion when storing data using firebase?

I'm creating a small application using only React.js, material-ui and firebase. I don't want to use Redux now in order to be familiar more with react.
I create a form which is described by:
User.jsx:
import React, { Component } from 'react'
import Button from '#material-ui/core/Button'
import Grid from '#material-ui/core/Grid';
import { withStyles } from '#material-ui/core/styles';
import PropTypes from 'prop-types';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import db from '../db/config';
import InputTextField from './textField';
import RadioGroup from './radioGroup';
import SnackBar from './snackBar';
const styles = (theme) => ({
button: {
margin: theme.spacing.unit,
},
root: {
display: 'flex',
marginTop: theme.spacing.unit * 8,
padding: theme.spacing.unit * 3,
},
item: {
padding: theme.spacing.unit * 2
}
});
class User extends Component {
state = {
birthday: moment().format('YYYY-MM-DD'),
message: '',
name: '',
open: false,
gender: 'male',
};
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ open: false });
};
handleSubmit = (event) => {
event.preventDefault();
const {
birthday,
name,
gender,
} = this.state;
console.log(`birthday: ${birthday} \n` +
`Name: ${name} \n` +
`gender: ${gender} \n`)
const ref = db.ref('users/');
ref.orderByChild('name').on('child_added', (snapshot) => {
const existedName = (snapshot.val().name).toLowerCase().trim();
const newName = name.toLowerCase().trim();
if(existedName === newName){
this.setState({
open: true,
message: 'Name already exists!!',
})
} else {
ref.push({
name: name.trim(),
gender,
birthday
})
.then(() => {
this.setState({
open: true,
message: 'saved successfully!!',
})
return true;
})
.catch((error) => {
this.setState({
open: true,
message: `Error adding baby: ${error}`,
})
return false;
});
}
})
}
render(){
const { classes } = this.props;
const {
birthday,
message,
name,
open,
gender,
} = this.state;
return (
<div className={classes.root}>
<Grid
container
spacing={40}
justify='center'
direction="column"
alignItems="center"
>
{open && (
<SnackBar
handleClose={this.handleClose}
message={message}
open
/>
)}
<Typography
align="center"
gutterBottom
variant="title"
>
Add New User
</Typography>
<form onSubmit={this.handleSubmit} className={classes.form}>
<Grid item className={classes.item} xs={12}>
<InputTextField
label="Name"
handleChange={this.handleChange('name')}
required
value={name}
type="text"
/>
</Grid>
<Grid item className={classes.item}>
<InputTextField
label="Birthday"
handleChange={this.handleChange('birthday')}
required
value={birthday}
type="date"
InputLabelProps={{shrink: true}}
/>
</Grid>
<Grid item className={classes.item}>
<RadioGroup
name="Gender"
handleChange={this.handleChange('gender')}
value={gender}
/>
</Grid>
<Grid item className={classes.item}>
<Grid
container
direction="row"
>
<Grid item>
<Button
variant="contained"
color="primary"
className={classes.button}
>
Cancel
</Button>
</Grid>
<Grid item>
<Button
variant="contained"
color="primary"
className={classes.button}
type='submit'
>
Save
</Button>
</Grid>
</Grid>
</Grid>
</form>
</Grid>
</div>
)
}
}
User.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(User);
SnackBar.jsx:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Snackbar from '#material-ui/core/Snackbar';
import IconButton from '#material-ui/core/IconButton';
import CloseIcon from '#material-ui/icons/Close';
const styles = theme => ({
close: {
width: theme.spacing.unit * 4,
height: theme.spacing.unit * 4,
},
});
const SimpleSnackbar = (props) => {
const {
classes,
handleClose,
message,
open,
} = props;
return (
<div>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={open}
autoHideDuration={6000}
onClose={handleClose}
ContentProps={{
'aria-describedby': 'message-id',
}}
message={<span id="message-id">{message}</span>}
action={[
<Button key="undo" color="secondary" size="small" onClick={handleClose}>
UNDO
</Button>,
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={handleClose}
>
<CloseIcon />
</IconButton>,
]}
/>
</div>
);
}
SimpleSnackbar.propTypes = {
classes: PropTypes.object.isRequired,
handleClose: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
open: PropTypes.bool.isRequired,
};
export default withStyles(styles)(SimpleSnackbar);
When I enter the different attributes of the form, I got this warning in the console: Firebase Warning
Added to the message too much recursion described by Firebase: too much recursion
I'm beginner to firebase and it's my first app using it.I think that I missed something or I didn't use the suitable function in order to fetch if a given name already exists. Unless it will be saved. I will be grateful if anyone tried to help me to fix the warning and the error displayed on the console.
I tried this solution and it works:
handleSubmit = (event) => {
event.preventDefault();
const {
birthday,
name,
gender,
} = this.state;
const ref = db.ref('users/');
const onChildAdded = (snapshot) => {
if(snapshot.exists()){
this.setState({
open: true,
message: 'Name already exists!!',
})
} else {
ref.push({
name: name.trim(),
gender,
birthday
})
.then(() => {
this.setState({
open: true,
message: 'saved successfully!!',
})
return true;
})
.catch((error) => {
this.setState({
open: true,
message: `Error adding baby: ${error}`,
})
return false;
});
}
}
ref.orderByChild('name').equalTo(name).once('value').then(onChildAdded);
}

Categories

Resources