Handle slow function in react component - reactjs

I face a challenge in React about performance and how to handle a slow function.
Basically, I have a form with 6 inputs.
Each input component call the heavyCalculation() (slow function) function before the render function.
So when the user starts to type on the input field the render is very slow.
The question is how to handle this kind of situation without editing the heavyCalculation function?
I started to think about using the useMemo or useCallback hook but I am not pretty sure of myself.
App.js
import React, { useState } from "react";
import Input from "components/Input";
import Button from "components/Button";
import { Card } from "antd";
import "antd/dist/antd.css";
import * as S from "./style";
const App = () => {
const [form, setForm] = useState({ userName: "", email: "", password: "", firstName: "", lastName: "", city: "" });
const handleChange = (fieldName, value) => {
const tmpForm = { ...form };
tmpForm[fieldName] = value;
setForm(tmpForm);
};
const handleSubmit = () => {
console.log(form);
};
return (
<S.Content>
<Card title={"Sign Up"} bordered={false} style={{ width: 500, height: 450 }}>
<S.Form>
<Input value={form.userName} fieldName="userName" onChange={handleChange} placeholder="Username" />
<Input value={form.email} fieldName="email" onChange={handleChange} placeholder="Email" />
<Input value={form.password} fieldName="password" type="password" onChange={handleChange} placeholder="Password" />
<Input value={form.firstName} fieldName="firstName" onChange={handleChange} placeholder="First Name" />
<Input value={form.lastName} fieldName="lastName" onChange={handleChange} placeholder="Last Name" />
<Input value={form.city} fieldName="city" onChange={handleChange} placeholder="City" />
<Button onClick={handleSubmit} label="Submit" />
</S.Form>
</Card>
</S.Content>
);
};
export default App;
Input Component
import React from "react";
import { heavyCalculation } from "global";
import * as Antd from "antd";
const Input = ({ fieldName, value, onChange, placeholder }) => {
heavyCalculation();
const handleChange = (e) => {
onChange(fieldName, e.target.value);
};
return <Antd.Input value={value} onChange={handleChange} placeholder={placeholder} />;
};
export default Input;
heavyCalculation function
export const heavyCalculation = (num = 10) => {
for (let i = 0; i < num; i++) {
heavyCalculation(num - 1);
}
};
Thanks for the help

Related

Setting value on TextField input text become non editable

I am creating a form for updating the data from mongodb I was able to fetch the data and added onchange if the currentId exist then all the data will populate on the form but, my problem is I cannot edit or I cannot type anything on the input to edit the value. I really need your eyes to see something that have missed or missed up. Thanks in advance y'all.
Profile container
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getProfile } from '../../../actions/profile'; //fetch method
import Profile from './Profile';
function Index() {
const dispatch = useDispatch();
const posts = useSelector((state) => state.posts);
const currentId = useState(null);
useEffect(() => {
dispatch(getProfile());
}, [currentId, dispatch]);
return (
<div className="custom-container">
{posts.map((profile) => (
<div key={profile._id}>
<Profile profile={profile} currentId={currentId} />
</div>
))}
</div>
);
}
export default Index;
Profile form component
import './Profile.css';
import { React, useState, useEffect } from 'react';
import Button from 'react-bootstrap/Button';
import { TextField } from '#material-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import { updateProfile } from '../../../actions/profile';
const Profile = ({ profile, currentId }) => {
const dispatch = useDispatch();
currentId = profile._id;
const [postData, setPostData] = useState(
{
profile: {
name: "",
description: "",
email: "",
number: "",
}
}
);
const post = useSelector((state) => currentId ? state.posts.find((p) => p._id === currentId) : null);
useEffect(() => {
if(post) setPostData(post);
}, [post])
const handleSubmit = (e) => {
e.preventDefault();
if(currentId) {
dispatch(updateProfile(currentId, postData));
}
}
// const [ImageFileName, setImageFileName] = useState("Upload Profile Picture");
// const [fileName, setFileName] = useState("Upload CV");
return (
<form autoComplete="off" noValidate className="form" onSubmit={handleSubmit}>
<TextField
id="name"
name="name"
className="name"
label="Full Name"
variant="outlined"
value={postData.profile.name}
onChange={(e) => setPostData({ ...postData, name: e.target.value })}
/>
<TextField
id="outlined-multiline-static"
label="Multiline"
multiline
rows={4}
variant="outlined"
size="small"
className="mb-3"
name="description"
value={postData.profile.description}
onChange={(e) => setPostData({ ...postData, description: e.target.value })}
fullWidth
/>
<TextField
id="email"
label="Email"
variant="outlined"
size="small"
className="mb-3"
name="email"
value={postData.profile.email}
onChange={(e) => setPostData({ ...postData, email: e.target.value })}
/>
<TextField
id="phone"
label="Phone Number"
variant="outlined"
size="small"
name="phone"
value={postData.profile.number}
onChange={(e) => setPostData({ ...postData, number: e.target.value })}
/>
<Button variant="light" type="submit" className="Save">Save</Button>
</form>
);
}
export default Profile;
If you'd look at the name field for example, you can see the value is postData.profile.name while onChange your setting postData.name.
Try setting the profile object, for example:
onChange={(e) => setPostData({ ...postData, profile: { ...postData.profile, name: e.target.value } })}

custom hook to simplify react controlled form

i'd like to simplify form creation avoiding to write for each fied value={} and onChange={} using a custom hook.
this is my code:
https://codesandbox.io/s/busy-noether-pql8x?file=/src/App.js
the problem is that, each time i press the button, the state is cleaned except for the field i've currently edited
import React, { useState } from "react";
import "./styles.css";
const useFormField = (initialValue) => {
const [values, setValues] = React.useState(initialValue);
const onChange = React.useCallback((e) => {
const name = e.target.name;
const value = e.target.value;
setValues( (prevValues) => ({...prevValues,[name]:value}));
}, []);
return { values, onChange };
};
export default function App() {
//hoot to simplify the code,
const {values,onChange} = useFormField({
Salary: "",
Email: ""
})
const onCreateUser = () => {
console.log(values);
};
return (
<div className="App">
<form>
<label htmlFor="Email">Email: </label>
<input name="Email" onChange={onChange} />
<label htmlFor="Email">Salary: </label>
<input name="Salary" onChange={onChange} />
<button type="button" onClick={onCreateUser}>
Send
</button>
</form>
</div>
);
}

Fetch and use response to change state in React

I would like to change the state of a component based on the response of a PUT request using react-refetch.
Especially when the response of the PUT is unsuccessful, as is the case with for example a 500 response.
The following example is an example in a form. When a user submits the form it should then fire off a PUT.
If the PUT response is fulfilled, it should reset the form. Otherwise nothing should happen, and the user should be able to retry.
./MyForm.jsx
import React from "react";
import PropTypes from "prop-types";
import { PromiseState } from "react-refetch";
import { Formik, Form, Field, ErrorMessage } from "formik";
import ResetOnSuccess from "./ResetOnSuccess";
const MyForm = ({ settingsPut, settingsPutResponse }) => {
const submitForm = (values, formik) => {
settingsPut(true);
// Here it should pick up the settingsPutResponse,
// and then do the following ONLY if it's successful:
//
// formik.resetForm({ values });
// window.scrollTo(0, 0);
};
return (
<div>
<Formik
noValidate
initialValues={{ name: "", password: "" }}
onSubmit={submitForm}
>
{({ dirty }) => (
<Form>
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" />
<Field type="password" name="password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={dirty !== null ? !dirty : false}>
Submit
</button>
{settingsPutResponse && settingsPutResponse.rejected && (
<p style={{ color: "red" }}>Please try again</p>
)}
</Form>
)}
</Formik>
</div>
);
};
MyForm.propTypes = {
settingsPut: PropTypes.func.isRequired,
settingsPutResponse: PropTypes.instanceOf(PromiseState)
};
MyForm.defaultProps = {
userSettingsPutResponse: null
};
export default MyForm;
I might have a solution by creating a component:
./ResetOnSuccess.jsx
import React, { useEffect, useState } from "react";
import { useFormikContext } from "formik";
import PropTypes from "prop-types";
import { PromiseState } from "react-refetch";
const ResetOnSuccess = ({ settingsPutResponse }) => {
const { values, resetForm } = useFormikContext();
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsPutResponse && settingsPutResponse.fulfilled) {
setSuccess(true);
}
}, [settingsPutResponse]);
// only if settingsPutResponse is fulfilled will it reset the form
if (success) {
resetForm({ values });
window.scrollTo(0, 0);
setSuccess(false);
}
return null;
};
ResetOnSuccess.propTypes = { settingsPutResponse: PropTypes.instanceOf(PromiseState) };
ResetOnSuccess.defaultProps = { settingsPutResponse: null };
export default ResetOnSuccess;
And then in ./MyForm.jsx add the reset component:
<Formik
noValidate
initialValues={{ name: "", password: "" }}
onSubmit={submitForm}
>
{({ dirty }) => (
<Form>
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" />
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
// etc...
But since it's a component that returns a 'null'. This feels a bit like an anti-pattern.
Is there a better way?
I've created an codesandbox example here: https://codesandbox.io/s/quizzical-johnson-dberw

React Hook Form with AntD Styling

I'm trying to figure out how to use react-hook-form with antd front end.
I have made this form and it seems to be working (it's part 1 of a multipart form wizard) except that the error messages do not display.
Can anyone see what I've done wrong in merging these two form systems?
I'm not getting any errors, but I think I have asked for both form fields to be required but if I press submit without completing them the error messages are not displayed.
import React from "react";
import useForm from "react-hook-form";
import { BrowserRouter as Router, Route } from "react-router-dom";
import { StateMachineProvider, createStore } from "little-state-machine";
import { withRouter } from "react-router-dom";
import { useStateMachine } from "little-state-machine";
import updateAction from "./updateAction";
import { Button, Form, Input, Divider, Layout, Typography, Skeleton, Switch, Card, Icon, Avatar } from 'antd';
const { Content } = Layout
const { Text, Paragraph } = Typography;
const { Meta } = Card;
createStore({
data: {}
});
const General = props => {
const { register, handleSubmit, errors } = useForm();
const { action } = useStateMachine(updateAction);
const onSubit = data => {
action(data);
props.history.push("./ProposalMethod");
};
return (
<div>
<Content
style={{
background: '#fff',
padding: 24,
margin: "auto",
minHeight: 280,
width: '70%'
}}
>
<Form onSubmit={handleSubmit(onSubit)}>
<h2>Part 1: General</h2>
<Form.Item label="Title" >
<Input
name="title"
placeholder="Add a title"
ref={register({ required: true })}
/>
{errors.title && 'A title is required.'}
</Form.Item>
<Form.Item label="Subtitle" >
<Input
name="subtitle"
placeholder="Add a subtitle"
ref={register({ required: true })}
/>
{errors.subtitle && 'A subtitle is required.'}
</Form.Item>
<Form.Item>
<Button type="secondary" htmlType="submit">
Next
</Button>
</Form.Item>
</Form>
</Content>
</div>
);
};
export default withRouter(General);
react-hook-form author here. Antd Input component doesn't really expose inner ref, so you will have to register during useEffect, and update value during onChange, eg:
const { register, setValue } = useForm();
useEffect(() => {
register({ name: 'yourField' }, { required: true });
}, [])
<Input name="yourField" onChange={(e) => setValue('yourField', e.target.value)}
i have built a wrapper component to make antd component integration easier: https://github.com/react-hook-form/react-hook-form-input
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
function App() {
const { handleSubmit, register, setValue, reset } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: true }}
name="reactSelect"
register={register}
setValue={setValue}
/>
<button
type="button"
onClick={() => {
reset({
reactSelect: '',
});
}}
>
Reset Form
</button>
<button>submit</button>
</form>
);
}
This is my working approach:
const Example = () => {
const { control, handleSubmit, errors } = useForm()
const onSubmit = data => console.log(data)
console.log(errors)
return (
<Form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="email"
control={control}
rules={{ required: "Please enter your email address" }}
as={
<Form.Item
label="name"
validateStatus={errors.email && "error"}
help={errors.email && errors.email.message}
>
<Input />
</Form.Item>
}
/>
<Button htmlType="submit">Submit</Button>
</Form>
)
}
On writing such code:
<Input
name="subtitle"
placeholder="Add a subtitle"
ref={register({ required: true })}
/>
You assume that Input reference is bound to input, but that's not true.
In fact, you need to bind it to inputRef.input.
You can check it with the next code:
const App = () => {
const inputRef = useRef();
const inputRefHtml = useRef();
useEffect(() => {
console.log(inputRef.current);
console.log(inputRefHtml.current);
});
return (
<FlexBox>
<Input ref={inputRef} />
<input ref={inputRefHtml} />
</FlexBox>
);
};
# Logs
Input {props: Object, context: Object, refs: Object, updater: Object, saveClearableInput: function ()…}
<input></input>
Note that antd is a complete UI library (using 3rd party "helpers" should "turn a red light"), in particular, Form has a validator implemented, you can see a variety of examples in docs.
In Ant Design v4.x + react-hook-form v6.x. We can implement as normally
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '#hookform/resolvers/yup';
import { useIntl } from 'react-intl';
import { Input, Button, Form } from 'antd';
const SignInSchema = yup.object().shape({
email: yup.string().email().required(),
password: yup.string().required('required').min(6, 'passwordMin'),
});
interface PropTypes {
defaultValues?: {
email: string;
password: string;
};
handleFormSubmit: SubmitHandler<{ email: string; password: string }>;
}
function SignInForm({ defaultValues, handleFormSubmit }: PropTypes) {
const intl = useIntl();
const { handleSubmit, control, errors } = useForm({
defaultValues,
resolver: yupResolver(SignInSchema),
});
return (
<Form onFinish={handleSubmit(handleFormSubmit)}>
<Form.Item
validateStatus={errors && errors['email'] ? 'error' : ''}
help={errors.email?.message}
>
<Controller
as={Input}
name="email"
autoComplete="email"
control={control}
placeholder={intl.formatMessage({ id: 'AUTH_INPUT_EMAIL' })}
/>
</Form.Item>
<Form.Item
validateStatus={errors && errors['password'] ? 'error' : ''}
help={errors.password?.message}
>
<Controller
as={Input}
name="password"
type="password"
control={control}
autoComplete="new-password"
defaultValue=""
placeholder={intl.formatMessage({ id: 'AUTH_INPUT_PASSWORD' })}
/>
</Form.Item>
<Button type="primary" htmlType="submit">
{intl.formatMessage({ id: 'SIGN_IN_SUBMIT_BUTTON' })}
</Button>
</Form>
);
}
export default SignInForm;
In case anyone is still interested in getting close to the default styles of the Form Inputs provided by Ant, here's how I got it to work:
import { Form, Button, Input } from 'antd';
import { useForm, Controller } from 'react-hook-form';
function MyForm() {
const { control, handleSubmit, errors, setValue } = useForm();
const emailError = errors.email && 'Enter your email address';
const onSubmit = data => { console.log(data) };
const EmailInput = (
<Form.Item>
<Input
type="email"
placeholder="Email"
onChange={e => setValue('email', e.target.value, true)}
onBlur={e => setValue('email', e.target.value, true)}
/>
</Form.Item>
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
as={EmailInput}
name="email"
control={control}
defaultValue=""
rules={{
required: true
}}
validateStatus={emailError ? 'error' : ''}
help={emailError}
/>
<Button block type="primary" htmlType="submit">
Submit
</Button>
</form>
);
}
Codesandbox sample

getting errors in retrieving information through inputs

I've written my code using semantic-ui-react and react redux, created a compose UI and when we pass inputs to the field it is showing error like this
"TypeError: Cannot read property 'value' of undefined"
I'm trying this using semantic-ui-react and react-redux
import React, { Component } from 'react'
import { Button, Header, Icon, Modal, Form} from 'semantic-ui-react';
import { connect } from "react-redux";
class Compose extends Component {
handleSubmit = (e) => {
e.preventDefault();
console.log("button triggered")
const From = this.getFrom.value;
const To = this.getTo.value;
const Subject = this.getSubject.value;
const outboxMessage = {
From,
To,
Subject
}
console.log(outboxMessage)
}
render() {
return (
<Modal trigger={<Button animated inverted color='blue'>
<Button.Content visible>Compose</Button.Content>
<Button.Content hidden>
<Icon name='plus'/>
</Button.Content>
</Button>} closeIcon>
<Modal.Content>
<Form onSubmit={this.handleSubmit}>
<Form.Input fluid label='From' type="text" ref={(input)=>this.getFrom = input} />
<Form.Input fluid label='To' type="text" ref={(input)=>this.getTo = input}/>
<Form.Input fluid label='Cc' type="text" ref={(input)=>this.getTo = input}/>
<Form.Input fluid label='Bcc' type="text" ref={(input)=>this.getTo = input}/>
<Form.Input fluid label='Subject' type='text'ref={(input)=>this.getSubject = input}/>
</Form>
<Button animated inverted color='blue' id='sendBtn'>
<Button.Content visible>Send</Button.Content>
<Button.Content hidden>
<Icon name='envelope'/>
</Button.Content>
</Button>
<Button animated inverted color='red'><Button.Content visible>Discard</Button.Content>
<Button.Content hidden>
<Icon name='remove circle'/>
</Button.Content>
</Button>
</Modal.Content>
</Modal>
);
}
}
export default Compose;
"In redux folder, reducer file:"
import actions from './actions';
const initialState = {
outboxMessage: []
}
const emailReducer = (state = initialState, action) => {
switch (action.type) {
case actions.OUTBOX_MESSAGE:
return {
...state,
outboxMessage: state.outboxMessage.concat([action.outboxMessage])
};
default:
return state;
}
}
export default emailReducer;
"In Index.js, mofidied in this way:"
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import emailReducer from './components/redux/reducer';
const store = createStore(emailReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root'));
I'm getting error Like this:
Compose.handleSubmit [as onSubmit]
F:/compose/src/components/Email/Compose.js:14
11 |
12 | handleSubmit = (e) => {
13 | e.preventDefault();
> 14 | const to = this.getTo.value;
| ^ 15 | const cc = this.getCc.value;
16 | const bcc = this.getBcc.value;
17 | const subject = this.getSubject.value;
You can make use of state, which is a cleaner way as well.
Maintain a state for all your input's.
state = {
from: "",
to: "",
cc: "",
bcc: "",
subject: ""
}
Now you can have Controlled Component.
We need name, value and onChange properties on input.
<Form onSubmit={this.handleSubmit}>
<Form.Input fluid label='From' type="text" name="from" value={this.state.from} onChange={this.changeHandler}/>
<Form.Input fluid label='To' type="text" name="to" value={this.state.to} onChange={this.changeHandler}/>
<Form.Input fluid label='Cc' type="text" name="cc" value={this.state.cc} onChange={this.changeHandler}/>
<Form.Input fluid label='Bcc' type="text" name="bcc" value={this.state.bcc} onChange={this.changeHandler}/>
<Form.Input fluid label='Subject' type='text' name="subject" value={this.state.subject} onChange={this.changeHandler}/>
</Form>
Now your change handler would be,
changeHandler = (e) => {
this.setState({
[e.target.name] : e.target.value
})
}
And finally you can use values from state,
handleSubmit = (e) => {
e.preventDefault();
console.log("button triggered")
//Get the values from state
const From = this.state.from;
const To = this.state.to;
const Subject = this.state.subject;
const outboxMessage = {
From,
To,
Subject
}
console.log(outboxMessage)
}

Resources