Strange behavior of material-ui when component is inside a function - javascript

I'm trying to split my code in more functions inside react functional components, so it's clearer to read and maintain the code, ie:
import React, { useEffect, useState } from "react";
import { StyledExchangeRateProvider } from "./styles";
import useUpdateRates from "../../hooks/useUpdateRates";
import {
FormControl,
InputLabel,
MenuItem,
Select,
TextField
} from "#material-ui/core";
export default function ExchangeRateProvider() {
// rates hook
const ratesContext = useUpdateRates();
const rates = ratesContext.state.rates;
// update rate on component did mount
useEffect(() => {
async function updateRates() {
if (!rates) {
await ratesContext.updateRate();
}
}
updateRates();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// save input values
const [values, setValues] = useState({
country: "VES",
amount: "",
total: ""
});
// change values
const handleChange = event => {
setValues({
...values,
[event.target.name]: event.target.value
});
};
function Amount() {
return (
<TextField
name="amount"
variant="filled"
label="Amount"
onChange={handleChange}
value={values.amount}
fullWidth
/>
);
}
function Country() {
return (
<FormControl fullWidth variant="filled" className="input">
<InputLabel id="Country">Country</InputLabel>
<Select
labelId="Country"
id="country"
name="country"
value={values.country}
onChange={handleChange}
>
<MenuItem value="ARS">Argentina</MenuItem>
<MenuItem value="BRL">Brazil</MenuItem>
<MenuItem value="INR">India</MenuItem>
<MenuItem value="VES">Venezuela</MenuItem>
<MenuItem value="ZAR">South Africa</MenuItem>
</Select>
</FormControl>
);
}
return (
<StyledExchangeRateProvider>
<Amount />
<Country />
</StyledExchangeRateProvider>
);
}
In this code, I'm separating in functions what I'll render in this component, so, ie, the Amount function returns a material-ui TextField. It will return more things, but for simplicity of this question, let's consider just this.
This code renders well, and all elements are shown. However, when I type something in the TextField, the cursor moves away from the TextField each caracter I type.
If I move the <TextField ... /> away from the Amount function and put it directly in the React Component return (switch the <Amount /> for <TextField ... />), the TextField works fine.
I've made a CodeSandBox with the behavior: https://codesandbox.io/s/dreamy-brattain-r4irj
My question is: why does it happen and how to fix it maintaining the code separated in functions?

Move Amount and Country Components outside of ExchangeRateProvider and pass data via props. The Issue is because on each render the functions are being recreated

Related

SonarQube "Do not define components during render" with MUI/TS but can't send component as prop

I am getting the following error during sonarqube scan:
Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state. Instead, move this component definition out of the parent component “SectionTab” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true.
I understand that it says that I should send the component as a prop from the parent, but I don't want to send the icon everytime that I want to use this component, is there another way to get this fixed?
import Select from "#mui/material/Select";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faAngleDown } from "#fortawesome/pro-solid-svg-icons/faAngleDown";
const AngleIcon = ({ props }: { props: any }) => {
return (
<FontAwesomeIcon
{...props}
sx={{ marginRight: "10px" }}
icon={faAngleDown}
size="xs"
/>
);
};
const SectionTab = () => {
return (
<Select
id="course_type"
readOnly={true}
IconComponent={(props) => <AngleIcon props={props} />}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
export default SectionTab;
What can you do:
Send the component as the prop:
IconComponent={AngleIcon}
If you need to pass anything to the component on the fly, you can wrap it with useCallback:
const SectionTab = () => {
const IconComponent = useCallback(props => <AngleIcon props={props} />, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
This would generate a stable component, but it's pretty redundant unless you need to pass anything else, and not via the props. In that case, a new component would be generated every time that external value changes, which would make it unstable again. You can use refs to pass values without generating a new component, but the component's tree won't be re-rendered to reflect the change in the ref.
const SectionTab = () => {
const [value, setValue] = useState(0);
const IconComponent = useCallback(
props => <AngleIcon props={props} value={value} />
, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};

Building dynamic form with dropdown and radio button

I'm trying to build a dynamic form with react which is auto populated with components when received data from api, here I'm able to display and render the components which are the n numbers of dropdowns and radio buttons on my form basis on what I'll be receiving from the api response, but still I want to pass the data selected from the from on onSubmit() function. I'm facing certain challenges in collecting the data altogether, as I'm using rfce, I can get data of single selection whereas I want a collective selection data.
Below attached is my code.
This is the main card component. Which is responsible for getting api and passing props to sub components that are dropdowns and radio button based on their types
import React, { useEffect, useState } from "react";
import { Card, Button, FloatingLabel, Form } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import {
SurveyDropdown,
SurveyRadioButton,
} from "./subcomponents/SurveyComponents";
import { getQuestions } from "../util/http";
const SurveyCard = () => {
const [surveyData, setSurveyData] = useState([]);
useEffect(() => {
// Update the document title using the browser API
getQuestions()
.then((resp) => {
setSurveyData(resp.data);
})
.catch((err) => {
console.log(err);
if (
!err.response ||
!err.response.data ||
!err.response.data.errorCode
) {
console.log("server failed to response");
return;
}
switch (err.response.data.errorCode) {
case 50: // Survey submitted
window.location.replace("/surveysubmitted");
break;
default:
console.error("Error code not registered ");
break;
}
});
}, []);
return (
<>
<Card>
<Card.Header></Card.Header>
<Card.Body>
<Card.Title>Please provide your feedback</Card.Title>
{surveyData.map((question) => {
if (question.type === "dropdown") {
return <SurveyDropdown key={question.id} value={question} />;
} else if (question.type === "button") {
return <SurveyRadioButton value={question} />;
}
})}
<FloatingLabel
controlId="floatingTextarea2"
label="Comments"
className="for-spacing"
>
<Form.Control
as="textarea"
placeholder="Leave a comment here"
style={{ height: "100px" }}
/>
</FloatingLabel>{" "}
<br />
<Button variant="primary">Submit</Button>
</Card.Body>
</Card>
<br />
<br />
</>
);
};
export default SurveyCard;
This is component which is rendering all the dropdowns and radio buttons. Here I'm trying to get the collective data from all displayed dropdowns and radio button, but instead am getting only one state a time.
import React, { useState } from "react";
import { Form, FloatingLabel } from "react-bootstrap";
export const SurveyDropdown = (props) => {
const [selectedOption, setSelectedOption] = useState();
console.log("Selected Option", selectedOption);
function handleSelectChange(event) {
setSelectedOption(event.target.value);
}
return (
<React.Fragment>
<FloatingLabel
className="for-spacing"
controlId="floatingSelect"
label={props.value.surveyQuestion}
>
<Form.Select onChange={handleSelectChange}>
{props.value.option.map((surveyD, index) => (
<option
value={surveyD}
key={index}
selected={surveyD === selectedOption}
>
{surveyD}
</option>
))}
</Form.Select>
</FloatingLabel>
</React.Fragment>
);
};
export const SurveyRadioButton = (props) => {
const [checkedOption, setCheckedOption] = useState([]);
console.log("Checked Option", checkedOption);
function handleCheckedChange(event) {
setCheckedOption(event.target.value);
}
return (
<div className="for-spacing">
<label>{props.value.surveyQuestion}</label>
<Form>
{["radio"].map((type) => (
<div key={`inline-${type}`} className="mb-3">
{props.value.option.map((answer, index) => {
return (
<Form.Check
inline
label={answer}
key={index}
name="group1"
type="radio"
value={answer}
id={`inline-${type}-1`}
onChange={handleCheckedChange}
/>
);
})}
</div>
))}
</Form>
</div>
);
};
what shall I try, please help!
Thanks!
I have so many remarks, but let's try to focus:
If you want to have controlled inputs you need to define the whole state in the upper component (=SurveyCard) and then pass down a callback for setting state accordingly.
If you want to have uncontrolled inputs first of all you need to have one single Form, not many. So remove the Form from Dropdown and Button components and wrap your SurveyCard Card.Body with it. On submit, you need to define a strategy on how to get those values out. You can for example use FormData object and iterate over the fields or - my preferable option - use some library for uncontrolled forms (i.e. "react-hook-form).
Please see into this sandbox with a working example: https://codesandbox.io/s/summer-grass-3gn4gm.
I have tried to mock your data.

Using react-number-format: cannot type more than one symbol at once

I am using react-number-format package inside of my TextField (material-ui). It is having strange behavior and not letting me put more than one symbol at once in the TextField. When I start typing after first click the Field is not focused anymore. I have used the same thing in other projects and it is working fine but here I cannot see from where the problem is coming. Here is the sandbox:
https://codesandbox.io/s/little-cherry-ubcjv?file=/src/App.js
import React,{useState} from 'react'
import { TextField} from "#material-ui/core";
import NumberFormat from "react-number-format";
export default function App() {
const [minAssets, setMinAssets] = useState();
const NumberFormatCustom = (props) => {
const { inputRef, onChange, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={(values) => {
onChange({
target: {
name: props.name,
value: values.value,
},
});
}}
thousandSeparator
isNumericString
prefix="$"
/>
);
};
return (
<div className="App">
<TextField
label="Min Assets"
value={minAssets}
onChange={(e) => setMinAssets(e.target.value)}
name="minAssets"
variant="outlined"
id="Minimum-Income-filter"
InputProps={{
inputComponent: NumberFormatCustom,
}}
/>
</div>
);
}
In your case you don't really need the onValueChange prop on the NumberFormat component since you already have an onChange on the TextField component that update the minAssets state.
So you can directly use this minAssets as the value of the prop value from NumberFormat like:
return (
<NumberFormat
{...other}
value={minAssets}
getInputRef={inputRef}
thousandSeparator
isNumericString
prefix="$"
/>
);

How to get the data from dropdown component in React and update state?

I am a beginner React developer and I am stuck at this problem. I am trying to make a user registration form. It contains three text fields and two dropdown buttons. I am able to update the state from the data entered in the text fields but I cannot do the same with the dropbdown buttons. I am sharing the screenshot and the code below of my approach so far.
Here is the code for the same:
NOTE: App.js calls the component Registration.js which in turn calls the component User.js
Registration.js has all the logic to control the form, the state to hold data and the functions to handle forward and backward movement (code incomplete for this).
App.js
import React from 'react';
import './App.css';
import Registration from './components/Registration';
function App() {
return (
<div className="App">
<h1>Hello World</h1>
<h2>This is a sample app!</h2>
<h5>Built by Anish Pal!</h5>
<Registration />
</div>
);
}
export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Registration.js
import React, { Component } from 'react';
import User from './User';
class Registration extends Component {
// State to store user data
state = {
step: 1,
userFirstName: '',
userLastName: '',
userEmail: '',
userContact: '',
userRole: '',
userExperience: '',
userEducation: '',
userAddress: '',
userBio: ''
}
// Move to the next page (component)
nextStep = () => {
const { step } = this.state;
this.setState({
step: step + 1
});
};
// Move to the previous page (component)
prevStep = () => {
const { step } = this.state;
this.setState({
step: step + 1
});
};
// Handle input field change (when data is entered)
handleChange = input => e => {
this.setState({ [input]: e.target.value });
};
render() {
// Get the step counter
const { step } = this.state;
// Get the input fields
const { userFirstName, userLastName, userEmail, userContact, userRole, userExperience, userEducation, userAddress, userBio } = this.state;
// Assign a new variable to all the above
const values = { userFirstName, userLastName, userEmail, userContact, userRole, userExperience, userEducation, userAddress, userBio };
// Switch the components based on the step counter
switch(step) {
case 1:
return (
<User values={values} handleChange={this.handleChange} nextStep={this.nextStep} />
);
default:
return (
<User />
);
}
}
}
export default Registration
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
User.js
import React, { Component } from 'react';
import Card from 'react-bootstrap/Card';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Dropdown from 'react-bootstrap/Dropdown';
import DropdownButton from 'react-bootstrap/DropdownButton';
class User extends Component {
// Handle movement to next component
continue = e => {
e.preventDefault();
this.props.nextStep();
};
render() {
// Extract the props passed from parent
const { values, handleChange } = this.props;
// Handle dropdown selections
const handleJobSelect = e => {
console.log(e);
this.setState({
userRole: e
});
}
return (
<div className="d-flex justify-content-center">
<Card border="primary" style={{ width:"48rem" }}>
<Card.Header>REGISTER USER</Card.Header>
<Card.Body>
<Card.Title>Enter User Details</Card.Title>
<Container>
<Row>
<br />
</Row>
<Row>
<Col>
<Form>
<Form.Control
type="text"
placeholder="Enter First Name"
onChange={handleChange('userFirstName')}
value={values.userFirstName}
/>
</Form>
</Col>
<Col>
<Form>
<Form.Control
type="text"
placeholder="Enter Last Name"
onChange={handleChange('userLastName')}
value={values.userLastName}
/>
</Form>
</Col>
</Row>
<Row>
<br />
</Row>
<Row>
<Col>
<Form>
<Form.Control
type="text"
placeholder="Enter Email ID"
onChange={handleChange('userEmail')}
value={values.userEmail}
/>
</Form>
</Col>
</Row>
<Row>
<br />
</Row>
<Row>
<Col>
<DropdownButton
id="dropdown-basic-button"
title="Select Job Role"
align="start"
onClick={handleChange('userRole')}
//onSelect={handleJobSelect}
value={values.userRole}>
<Dropdown.Item eventKey="Front End Web Developer">Front-End Web Developer</Dropdown.Item>
<Dropdown.Item value="option-2">Back-End Web Developer</Dropdown.Item>
<Dropdown.Item>Data Scientist</Dropdown.Item>
<Dropdown.Item>Data Engineer</Dropdown.Item>
<Dropdown.Item>Dev-Ops</Dropdown.Item>
<Dropdown.Item>Cloud Architect</Dropdown.Item>
</DropdownButton>
</Col>
<Col>
<DropdownButton id="dropdown-basic-button" title="Select User Experience" align="start">
<Dropdown.Item>No Experience (0-1 years)</Dropdown.Item>
<Dropdown.Item>Some Experience (1-3 years)</Dropdown.Item>
<Dropdown.Item>Medium Experience (3-5 years)</Dropdown.Item>
<Dropdown.Item>Experienced (5-8 years)</Dropdown.Item>
<Dropdown.Item>Specialist (8-10 years)</Dropdown.Item>
</DropdownButton>
</Col>
</Row>
</Container>
</Card.Body>
</Card>
</div>
)
}
}
export default User
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
WHAT I HAVE TRIED
I changed the onClick={handleChange('usereRole')} to onChange={handleChange('userRole')} but that did not generate any change in the state. Using onClick() at least generates some response on the app though I keep getting undefined
I used onSelect={handleJobSelect} and then defined the function as :
const handleChange = e => {
console.log(e);
}
Using this approach, I was able to successfully get the required value from the list into the console. However, I am still not able to update the state with this value. I need to update the state variable userRole with the item selected from the dropdown button.
Also, if anyone could tell me how to update the title of the button with the selected menu entry, it would be an added bonus (it is good to provide feedback to the user about their selection).
I would also like to mention I am using React-Bootstrap for the components so you guys can refer there for any additional information.
You can change onSelect call back like this
onClick={this.handleChange.bind(this)}
inside handleChange function you can access selected value in e

Passing React Hooks in a stepper component

I'm learning React and I deep dived into hooks as they are elegant, minimize the use of classes, and (at first) looked easy to understand.
Using Material-ui's stepper and checkboxes component. I'm trying to export the values of what was selected and display it on the next step of the stepper. Checked: This But it seems too complicated in my case.
I'm not sure though if I need to pass the state array as props and pass it when returning the component of the 2 checkboxes or map the array and pass it through function?
const [state, setState] = React.useState({
checkedA: false,
checkedB: false,
});
const handleChange = (event) => {
setState({ ...state, [event.target.name]: event.target.checked });
//In my try to export the state I'm passing it to a funcvtion every time a change is made
SelectedBoxes({state})
};
return (
<FormGroup row>
<FormControlLabel
control={
<Checkbox checked={state.checkedA} onChange={handleChange} name="checkedA" />
}
label="Secondary"
/>
<FormControlLabel
control={
<Checkbox
checked={state.checkedB}
onChange={handleChange}
name="checkedB"
color="primary"
/>
}
label="Primary"
/>
</FormGroup>
);
}
//Here is where the function of Selectedboxes is defined
export function SelectedBoxes(checked) {
return (
<div>You selected: {checked}</div>
);
}
function getSteps() {
return ['Checkboxes', 'SelectedBoxes'];
}
function getStepContent(step) {
switch (step) {
case 0:
return <Checkboxes />;
case 1:
return <SelectedBoxes />;
default:
return 'Unknown step';
}
}
export default function HorizontalLinearStepper() {...}
How can I avoid making it so complicated?
Thank you
I solved my problem by moving the state inside the main component.
Then I was easily passing the props from the parent to the child compenent.

Categories