Why is this React method not working as expected? - javascript

I have some code that is meant to eliminate placeholders onFocus and return them onBlur, it seems to work properly for the login text input, but not for the password one. Do you mind having a look at the code?
This is the method in question:
togglePlaceholder = (nodeType:string,localStateValue:string) => {
switch (eval(`this.state.${localStateValue}`)) {
case nodeType:
return this.setState({[localStateValue]:null});
case null:
return this.setState({[localStateValue]:nodeType});
}
}
I'm using eval here because I'm planning to reuse this function across multiple components to toggle their local state.
These are the components:
<TextInput style={styles.logInFormInput}
placeholder={this.state.logInPlaceholder}
onFocus={()=>this.togglePlaceholder('login','logInPlaceholder')}
onBlur={()=>this.togglePlaceholder('login','logInPlaceholder')}
></TextInput>
<TextInput style={styles.logInFormInput}
secureTextEntry={true}
placeholder={this.state.passwordPlaceholder}
onFocus={()=>this.togglePlaceholder('password','passwordPlaceholder')}
onBlur={()=>this.togglePlaceholder('password','passwordPlaceholder')}
></TextInput>
Seems to be working properly for login, but not password.

Given that you are just hiding or showing I think you could use a pivot of thinking to solve this a little easier :)
The simplest way in my mind to would be to track the id of the currently focused element and use it to conditionally render the placeholder:
<TextInput onFocus={() => setActive(id)} onBlur={clearActive} placeholder={this.state.activeId === id ? 'placeholder' : ''} />
It would be simple to create a HoC which manages the inputs within, allowing you to group it accordingly

Related

Improve textfield render performance

I'm using React 17 and MUI 5. I found a problem in my project when I have a component with a huge form with multiple <Textfields>.
handleChange = (event) => {
this.setState({ value: event.target.value });
};
...
<TextField
value={this.state.value}
onChange={this.handleChange}
label="Textfield"
/>
Everytime I type a char in my textfield the setState is triggered and my render() function is called everytime. So on my huge form I have a lag when I type.
So I tried the solution to use onBlur event:
<TextField
onBlur={this.handleChange}
label="Textfield"
/>
It works well if I'm on "Add" mode because the setState is triggered only when I leave the textfield. But I have a problem when I'm on "Edit" mode. When I'm on "Edit" mode I get the current value and I put it inside my textfield. And with onBlur event I can't use the textfield value prop because there is a conflict and I can't write anymore in the textfield.
So I found another solution. It's to use ref. But I have the same problem like with onBlur event. I can't use value prop. So I did this :
<TextField
inputRef={(el) => {
this.textRef = el;
if (el && this.state.mode === "edit") {
this.textRef.value = this.state.node.value;
}
}}
label="Textfield"
/>
So when I'm on edit mode I init the textfield with the current value (this.state.node is a node of my TreeView and each node have a edit button). This code works. But I have the feeling it's not the good way to do that. This if mode == edit is weird.
Do you have a better way ?
You could examine uncontrolled inputs, it could fit in your case
An uncontrolled form doesn’t have state, so every time you type, your component doesn’t re-render, increasing overall performance.
There is an explanation: https://hackernoon.com/you-might-not-need-controlled-components-fy1g360o
I doubt the "huge form" is actually the problem. React only updates the DOM when there are changes, so even though the component is rerendered on every character typed, only the one input field that changes should get flushed to the DOM.
Check if you're doing expensive calculations in your render() function. In that case, memoize these.
Another take on fixing this would be to use uncontrolled components as mentioned in the other answer.
Also consider if you can split up your component into multiple, smaller components so the problem is minimized.
The following "benchmark" code is written with functional components but is using MUI v5 and React 17 and runs with 100 TextFields without any noticable lag when entering text, at least on my (relatively beefy) desktop PC.
Using <input> from HTML even allowed me to get this working with 1000 elements.
const inputFieldKeys = Array.from({
length: 100
}).map((e, i) => "field_" + i.toString());
const App = (props) => {
const [inputState, setInputState] = React.useState({});
// this runs fine even without the `useCallback` optimization, at least for me
const handleChange = React.useCallback((e) => {
setInputState(oldState => ({ ...oldState,
[e.target.name]: e.target.value
}));
}, [setInputState]);
return ( <div>
{inputFieldKeys.map(key=>(
<MaterialUI.TextField name={key} key={key} value={inputState[key] || ""} onChange={handleChange}
label={key}/>
))}
</div>
);
};
ReactDOM.render( < App / > , document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/#mui/material#5/umd/material-ui.production.min.js"></script>
<div id="root"></div>
Note for MUI performance:
MUI v5 uses the sx prop for inline styling. This can lead to bad performance in cases with many components when used without care. mui docs

React: Warning, a component is changing an uncontrolled input to be controlled

I'm using Google maps address autocomplete in my React app. It works by hooking into an input element, watching for changes, and providing a dropdown location select.
Relevant code:
<InputGroup hasValidation className="mb-3">
<FormControl id="autocomplete"/>
</InputGroup>
useEffect(() => {
// only specify the fields we need, to minimize billing
const options = {"fields": ["address_components"]}
const autocompleteElement = document.getElementById("autocomplete")
if(autocompleteElement) {
autocomplete.current = new google.maps.places.Autocomplete(autocompleteElement, options);
const listener = autocomplete.current.addListener("place_changed", placeSelected);
return function cleanUp() {
google.maps.event.clearInstanceListeners(listener);
}
} else {
// nothing to do yet
return;
}
});
However, I'm getting a warning in the browser console:
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component
Seems obvious enough- the autocomplete functionality is changing the input itself as opposed to using react state to make a controlled component. However that's the way I want it. Is there a way I can silence this error? I've tried adding an empty defaultValue and an empty onChange function, but still got the error. Thanks in advance!
(There were a few questions with the same issue, but nothing about deliberately disabling the warning)
I have faced such warnings on a couple of projects here is one of the causes and solution.
const [value, setValue] = useState("");
<input value={value} onChange={inputHandler} />
From the code above notice that the state initial value is "", check yours. Its possible you are using null or empty value.
You need to change it to empty string and that warning will disappear.
Let me know if its helpful.
We cleared this error by simply adding default values to our form inputs:
<p style={{ fontWeight: 'bold' }}>Devices: </p>{' '}
<Select
isMulti
value={mapValuesToSelect(devices) || []}
// this one rigth here ^^^^^
onChange={(e) => editDevicesMultiSelect(e)}
or simple input:
<Input
type="text"
value={code || ''}
// ^^^^^
P.S. we also have a handleSelectOnClose function:
<Button onClick={handleUnselect}>
CLOSE
</Button>
const handleUnselect = () => {
dispatch(setCurrentDesk(undefined));
};
Use a regular uncontrolled html input instead of one of the controlled react-bootstrap inputs.
You can use a ref to refer to the input.
<InputGroup hasValidation className="mb-3">
<input
defaultValue="Belgium"
type="text"
ref={this.autocomplete} />
</InputGroup>
More info on uncontrolled inputs and ref usage here:
https://reactjs.org/docs/uncontrolled-components.html
You can try using a third party custom package like:
React Places autocomplete.
This package provides an option to use controlled input, as well as other props to customise styling and other methods
const [value, setValue] = useState(null);
<GooglePlacesAutocomplete
selectProps={{
value,
onChange: setValue,
}}
/>
You need to give an initial value and an onChange method. I'd probably use a hook to make this easier to manage. You could then use the useEffect() hook to call the API when the value of inputText changes.
const [inputText, setInputText] = useState('');
useEffect(() => {
if (inputText.length > 0) {
... call the api
}
},[inputText])
<InputGroup hasValidation className="mb-3">
<FormControl id="autocomplete" value={inputText} onChange={(event) => {setInputText(event.target.value}}/>
</InputGroup>
as Blessing Ladejobi said its beacuse of
<input value={myValue} onChange={inputHandler} /> and myValue=null
solution is define a default value for myValue (like myValue="")

ReactJS autocomplete from React Bootstrap not working

I'm trying to build an autocomplete search field, using this form component from React Bootstrap, which is rendered as a input by the browser.
Here's what I did in my React component:
<FormControl
id="frenchToEnglishInput"
placeholder="type a word..."
aria-label="Recipient's username"
autocomplete="on"
data={frenchToEnglishWords}
onChange={this.fetchFrenchToEnglish}
renderItem = {item => {
return (
<div>
{item}
</div>
);
}}
/>
the frenchToEnglishWords array is declared outside the component as a var, as I intend to update it as I type some value into the input field.
Now here is the function that triggers the onChange event :
fetchFrenchToEnglish = async () => {
if(document.getElementById("frenchToEnglishInput").value!==''){
axios.get(dictionaryURIs.english.French_English+""+document.getElementById("frenchToEnglishInput").value)
.then(response => {
frenchToEnglishWords = response.data
})
}
}
The request is made from MongoDb, after setting up an autocomplete index on a collection, but this part works fine.
My question is, why is the only "autocomplete" I'm getting is the one made of the previous words I've typed ?
Or maybe the array I'm using as input data must be a const (as I've seen in many examples) and not a var ?
When I do type in some word, I do not get any autosuggestion from the frenchToEnglishWords, which is being updated from the DB.
You need to use State!
This is not working because the data field is not a State, you need to bind the fetchFrenchToEnglish function to the data state.
But first of all, there's no reason to use var, because the most differences between them is just scope and immutability, here is more about it.
Also, you can use hooks like useState and useRef to no use getElementById.

Input fields lose focus on each value change in a complex Formik form

I'm building an app that will need a complex form generation (from json/js object).
Here's an example of what I'm building: https://codesandbox.io/s/formik-form-wizard-test-vcj1t
My problem is the following:
all the input fields lose focus on every value change
The folder structure of the CodeSandbox project is the following (for easier code comprehension):
./src:
App.js - main file with all the form generation (generateWizardSteps is the main function that does all the generation).
formSetup.js - that's an object that defined form configuration. From objects like this I need to build dynamic forms. This object has the array of pages (these are the steps of the wizard), and each page has an array of fields (like input, select, checkbox etc). In its turn, each field has props property. props is what I pass to the React component as props.
formComponents.js - this file contains all the form field React components that I use for generating my forms.
decorateWithFormik.js - this file is just to make App.js a bit smaller. It's just the useFormik decorator.
The form is built using the formik library. Also, as I need a wizard-like form, I've found a nice library for it: formik-wizard-form.
I've looked through the stackoverflow questions on similar topics but couldn't find something that could fit my needs. Many questions/answers are about the problem with dynamic key props, but mine are static as far as I can tell (they all are taken from the initial formSetup object).
Another thing that I've noticed is that my form gets re-rendered on every value change, but I'm not sure if this is a problem at all.
Could you help me to figure out what the problem is, why does it happend and how to make my form fields not lose focus?
Solved the issue
I've removed all the possible component creation code and moved everything inside the component props of the Step component provided by the formik-wizard-form library.
Here's my working solution: https://codesandbox.io/s/formik-form-wizard-test-lz3x7 (hope this will help somebody)
Many thanks to everyone in the comments section!
Main insights:
Losing focus means that the component unmounts and remounts every time.
This unmounting/remounting behaviour on every change was caused by the creation of components inside the render function of another component. Consequently, on every re-render the input components were created anew.
My final working code:
const MyForm = props => {
return (
<FormikWizardProvider {...props}>
{renderProps => (
<Wizard {...renderProps}>
<StepsList>
{formSetup.pages.map(page => (
<Step
title={page.name}
key={page.name}
component={() =>
page.fields.map(field => {
if (
field.props &&
field.props.visibilityCheckbox &&
!props.values[field.props.visibilityCheckbox]
) {
return null;
}
const fieldProps = {
formik: props,
key: field.props.name,
...field.props
};
switch (field.type) {
case "input":
return <MyTextInput {...fieldProps} />;
case "radio":
return <RadioButtonGroup {...fieldProps} />;
case "checkbox":
return <MyCheckbox {...fieldProps} />;
case "select":
return <MySelect {...fieldProps} />;
default:
return null;
}
})
}
/>
))}
</StepsList>
<ButtonsList>
<PreviousButton />
<NextButton />
<SubmitButton />
</ButtonsList>
</Wizard>
)}
</FormikWizardProvider>
);
};
export default decorateWizardWithFormik(MyForm);

How to update a field value after action dispatched with redux

I would like to populate a form after an ajax call with redux.
My case is pretty simple:
I have a simple user form (with only one text field for now), it's a react view bound to the state with connect().
I call a rest API to fetch the user.
When the api call is done, an action is dispatched with the user.
A reducer update the store state with the user.
I would like to populate/update the form with the retrieved values.
Solution 1:
If I set the value from the props like that:
const Field = ({ field, onFieldChange, value }) => (
<input
value={value}
onChange={(event) => { onFieldChange(field, event.target.value) }}
type="text"
/>
)
It works but I get this warning:
Field is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa).
I understand why I get this error as I should not use a component to display something and also be able to update it.
I also tried to use the defaultValue props but this is only used at the component creation (and we don't have the user yet). After the ajax call return, defaultValue cannot be called.
Solution 2:
Use redux-form with a custom plugin to update the form model each time the state get updated. I don't find this solution really clean but maybe I'm wrong.
I really thin that I'm going in the wrong direction and that it should exist a better way.
Does somebody already faced this kind of issue?
I encountered the same problem when I was trying to pass undefined as the input value.
To fix this, ensure that you are passing at least empty string to the input, not undefined
const Field = ({ field, onFieldChange, value }) => (
<input
value={value || ''} // <- add fallback value here
onChange={(event) => { onFieldChange(field, event.target.value) }}
type="text"
/>
)
Actually you might try to make your component statefull - store and manage value of input inside of it (they say it's ok).
Or if you really need this value in the store use redux-form, I have realy good experience of using it (you'll have to write less boilerplate code).
By the way, you will not have to use any custom plugin, you can use initialValues, see more here
The solution above will work for sure, but it doesn't seem to be nice.

Categories

Resources