React Material UI - Make Slider update state - javascript

I am building a multi-step form (survey) with React.js and Material-UI components library.
At the step with a slider, the component doesn't update the state. I was trying to set value with setValue from React.useState() and other various methods. Didn't work out. Does anybody know what the problem is? I'm stuck.
Here is the link to Codesandox: project's code
Code of the Slider component:
import React from 'react';
import { Slider, Input } from '#material-ui/core/';
export default function DiscreteSlider({ values, handleChange }) {
const [value, setValue] = React.useState(values.areaSpace);
const handleSliderChange = (event, newValue) => {
setValue(newValue);
};
const handleInputChange = event => {
setValue(event.target.value === '' ? '' : Number(event.target.value));
};
const handleBlur = () => {
if (value < 0) {
setValue(0);
} else if (value > 100) {
setValue(100);
}
};
return (
<div onChange={handleChange('areaSpace')} style={{marginTop: '20px', marginBottom: '20px'}}>
<Input
value={value}
margin="dense"
onChange={handleInputChange}
onBlur={handleBlur}
inputProps={{
step: 1,
min: 0,
max: 800,
type: 'number',
'aria-labelledby': 'input-slider',
}}
/>
<Slider
style={{marginTop: '20px'}}
value={typeof value === 'number' ? value : 0}
onChange={handleSliderChange}
aria-labelledby="input-slider"
step={1}
min={0}
max={800}
onChangeCommitted={handleChange}
/>
</div>
);
}

On your Slider you have the following:
onChangeCommitted={handleChange}
The handleChange above is being passed from MainForm.js which defines it as:
// Handle inputs change
handleChange = input => event => {
this.setState({ [input]: event.target.value });
}
When that function gets called, all it is going to do is return another function. You need to call handleChange("areaSpace") in order to get a function that will then try to set the "areaSpace" state when it is called. Another problem is that the change function is getting the value from event.target.value, but for the Slider the value is passed as a second parameter.
The following code addresses both of these issues:
onChangeCommitted={(event, value) =>
handleChange("areaSpace")({ target: { value } })
}
There are more elegant ways of dealing with this, but the above fixes the problem without changing any other layers. It does still leave another problem which is that if you change the input instead of the Slider, the areaSpace state won't be updated, but I'll leave that as a problem for you to work through.

Related

How to set Formik custom component value to Formik value

I'm using Formik for my form with google place auto-complete, I want to render places auto-complete as a custom component in the Formik field.
form.js
<Formik initialValues={location:""}>
<Field name="location" component={PlacesAutoComplete} placeholder="enter your location"/>
{...rest of form}
</Formik>
auto-complete component
import PlacesAutocomplete , {
geocodeByAddress,
geocodeByPlaceId
} from "react-google-places-autocomplete";
export const PlacesAutoComplete = ({
field: { name, ...field }, // { name, value, onChange, onBlur }
form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
classes,
label,
...props
}: any) => {
const [fieldName, setFildName] = React.useState(field.name);
const [address, setAddress] = React.useState(props.value || "");
const error = errors[name];
// const touch = touched[name];
const handleSelect = () => {
// set this value to formik value
};
const handleChange = () => {
// set this value to formik value
};
const handleError = () => {
props.form.setFieldError(fieldName, error);
};
return (
<PlacesAutocomplete
value={address}
onChange={handleChange}
onSelect={handleSelect}
onError={handleError}
name={name}
placeholder={props.placeholder}
id={name}
{...props}
apiKey="Api key here"
>
{({
getInputProps,
suggestions,
getSuggestionItemProps,
loading
}: any) => (
<div>
<input
{...getInputProps({
placeholder: "Search Places ...",
className: "location-search-input form-control"
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map((suggestion: any) => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "#fafafa", cursor: "pointer" }
: { backgroundColor: "#ffffff", cursor: "pointer" };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
};
How I set places auto-complete value to formik value, I'm pretty new to react and confused in handle change and on change functions. also, I found a solution in react class component here, But when converting those codes into functional components I'm stuck in Onchange and onSlecet functions
Better not write functional components as you'll get stuck with the test cases if you are writing.
OnChange is even you type anything, the value gets stored in onChange.
Abe onSelect is when you select anything
Basically on change you need to call formik's field onChange function. So in case you get an event on handleChange, just do this
const handleChange = (event) => {
// set this value to formik value
field.onChange(event.target.value)
};
or in case you get value in handleChange then do this
const handleChange = (value) => {
// set this value to formik value
field.onChange(value)
};
This will sync your formik state with autocomplete state.
Now comes the part for select. In this case also you can take the same route
const handleSelect = (value) => {
// set this value to formik value
field.onChange(value)
};
or you can use the setField function of form to update the value
const handleSelect = (value) => {
// set this value to formik value
form.setField('location',value)
};

how to set focus to next input on enter key press in react js with refs

Iam using multiple inputs inside maps i want to set focus to next input when i click enter in react Hooks.
With the help of refs
Iam using material ui text field for getting input
I tried in react class component wihtout ref it works with error but in hooks it not works
class compomnent code:
constructor(props) {
this.state = {}
}
inputRefs = [];
_handleKeyPress = e => {
const {currentTarget} = e;
let inputindex = this.inputRefs.indexOf(currentTarget)
if (inputindex < this.inputRefs.length - 1) {
this.inputRefs[inputindex + 1].focus()
}
else {
this.inputRefs[0].focus()
}
};
Inside render in added this within map function
this.state.data.map((data) => return (
<TextField
inputProps = {{onKeyPress:(e) => this.function1(e, data)}}
onChange={this.changevaluefunction}
inputRef={ref => this.inputRefs.push(ref)}
onFocus={this.handleFocus} ref={`input${id}`} /> ))
I have implemented the solution in a different way with the functional component. I have taken the 4 fields and seated its ref with the createRef hook.
I can see from your solution, you wanted to move focus to the next input element whenever you press Enter key on the current element.
I am passing the next target element argument in the onKeyUp handler along with the actual event and then detecting whether the Enter key is pressed or not. If Enter key is pressed and the targetElem is present then I am moving focus to the passed targetElem. By this way you have better control over the inputs.
You can see my solution here
https://codesandbox.io/s/friendly-leftpad-2nx91?file=/src/App.js
import React, { useRef } from "react";
import TextField from "#material-ui/core/TextField";
import "./styles.css";
const inputs = [
{
id: "fName",
label: "First Name"
},
{
id: "lName",
label: "Last Name"
},
{
id: "gender",
label: "Gender"
},
{
id: "address",
label: "Address"
}
];
export default function App() {
const myRefs = useRef([]);
const handleKeyUp = (e, targetElem) => {
if (e.key === "Enter" && targetElem) {
targetElem.focus();
}
};
return (
<div>
{inputs.map((ipt, i) => (
<TextField
onKeyUp={(e) =>
handleKeyUp(e, myRefs.current[i === inputs.length - 1 ? 0 : i + 1])
}
inputRef={(el) => (myRefs.current[i] = el)}
id={ipt.id}
fullWidth
style={{ marginBottom: 20 }}
label={ipt.label}
variant="outlined"
key={ipt.id}
/>
))}
</div>
);
}
You can convert this.inputRefs into a React ref so it persists through renders, and other than this you pretty much remove all references to any this object.
Example Component:
const LENGTH = 10;
const clamp = (min, max, val) => Math.max(min, Math.min(val, max));
export default function App() {
const [data] = useState([...Array(LENGTH).keys()]);
const inputRefs = useRef([]); // <-- ref to hold input refs
const handleKeyPress = index => () => { // <-- enclose in scope
const nextIndex = clamp(0, data.length - 1, index + 1); // <-- get next index
inputRefs.current[nextIndex].focus(); // <-- get ref and focus
};
return (
<div className="App">
{data.map((data, index) => (
<div key={index}>
<TextField
inputProps={{ onKeyPress: handleKeyPress(index) }} // <-- pass index
inputRef={(ref) => (inputRefs.current[index] = ref)} // <-- save input ref
/>
</div>
))}
</div>
);
}
If you are mapping the input field and want to focus on click, you can directly give the id attribute to the input and pass the array id.
After that, you can pass id inside a function as a parameter, and get it by document.getElementById(id).focus().

How properly connect react-final-form and material-ui-chip-input?

I'm using material-ui-chip-input wrapped with Field react-final-form.
I wanted to limit "CHIPS" to 5 only.
Chips are compact elements that represent an input, attribute, or
action.
material ui docs
As you can see I'm using 2 states here
react useStates
react-final-form internal states
This is redundant because react-final-form handled states internally but I can't make to work I'm just showing what I have done so far.
Basically there are
two problems with my implementation.
It doesn't limit my chips.
My react-final-form field values - not updating when clicking DeleteChip
import ChipInput from 'material-ui-chip-input'
import { Field } from 'react-final-form'
const [state, setState] = useState([])
const AddChip = useCallback(
(chip) => {
if (state.length + 1 <= 5) {
setState((prev) => ([...prev.tags, chip]))
}
},
[setState, state.length]
)
const DeleteChip = useCallback(
(chip) => {
setState((prev) => (...prev.state.filter((p) => p !== chip)]
}))
},
[setState]
)
return (
<Field name="states" validate={isRequired} >
{({ input: { value, onChange, ...rest }, meta }) => {
<ChipInput
defaultValue={Array.isArray(value) ? value : []} // check value first because material-ui-chip-input require an array, by default react-final-form value is empty string
onChange={(event) => { // uncontrolled
AddChip(event)
onChange(event)
// I tried below code but same result not working
// if (state.length + 1 <= 5) {
// onChange(event)
// }
}}
onDelete={DeleteChip}
/>
}}
</Field>
)
material-ui-chip-input
react-final-form
see codesandbox demo
This is my take:
https://codesandbox.io/s/proud-water-xp2y1?file=/src/App.js
Key points:
Don't duplicate the state, let react final form handle the state for you
Pass an empty array as the initial state to the FORM, don't pass defaultValues to the Field.
according to the material-ui-chip-input package you need to use onAdd if used in controlled mode, which we do, since we let react final form handle the state.
Add the value prop to the Chipinput.
For cosmetic reasons: don't actually use the render-prop inside <Form /> - use a functional child component instead.
Code:
import ChipInput from "material-ui-chip-input";
import { Form, Field } from "react-final-form";
export default function App() {
return (
<Form
initialValues={{
states: []
}}
onSubmit={() => console.log("submitted")}
>
{({ values, onSubmit }) => (
<form onSubmit={onSubmit}>
<Field name="states">
{({ input: { value, onChange } }) => (
<ChipInput
value={value}
alwaysShowPlaceholder={true}
placeholder="type states here"
onAdd={(newVal) => {
if (value.length >= 5) return;
const newArr = [...value, newVal];
onChange(newArr);
}}
onDelete={(deletedVal) => {
const newArr = value.filter((state) => state !== deletedVal);
onChange(newArr);
}}
/>
)}
</Field>
<p>react useStates</p>
<p>react-final-form values</p>
<pre
style={{
backgroundColor: "rgba(0,0,0,0.1)",
padding: "20px"
}}
>
{JSON.stringify(values, 0, 2)}
</pre>
</form>
)}
</Form>
);
}

Invalid hook call when I'm setting values from numpad using useChange

This is my component:
const pricePicker = ({
step,
precision,
input,
placeholder,
label,
theme,
props,
meta: { touched, error },
...rest
}) => {
/*In the FOLLOWING LINES from "function useChange(e)" to "return [value,change]"
is the error known as Invalid hook.
*/
function useChange(e){
const [value,setValue] = useState(0);
function change(event){
setValue(value => event.target.value);
}
return [value,change];
}
const handleBlur = (e) => {
if (e.target.value === '0') e.target.value = '0'
}
const handleKeypress = (e) => {
const characterCode = e.key
if (characterCode === 'Backspace') return
const characterNumber = Number(characterCode)
if (characterNumber < 0) {
e.preventDefault()
}
}
const myTheme = {
fontFamily: 'Arial',
textAlign: 'center',
header: {
primaryColor: '#263238',
secondaryColor: '#f9f9f9',
highlightColor: '#FFC107',
backgroundColor: '#607D8B',
},
body: {
primaryColor: '#263238',
secondaryColor: '#32a5f2',
highlightColor: '#FFC107',
backgroundColor: '#f9f9f9',
},
panel: {
backgroundColor: '#CFD8DC'
}
};
return(
<div className='form-group'>
<label forname={input.name}>{label}</label> <br />
<NumPad.Number
{...rest}
step={0.1}
precision={2}
placeholder={!input.value ? 'Please, type a number' : input.value}
selected={input.value ? new NumPad.Number(input.value) : null}
onKeyDown={(changedVal) => handleKeypress(changedVal)}
onBlur={(changedVal) => handleBlur(changedVal)}
onChange={(changedVal) => useChange(changedVal)}
className='form-control'
/>
<div className='text-danger' style={{ marginBottom: '20px' }}>
{touched && error}
</div>
</div>
);
};
export default pricePicker;
When I'm executing this block of code:
function useChange(e){
const [value,setValue] = useState(0);
function change(event){
setValue(value => event.target.value);
}
return [value,change];
}
I'm getting the following issue:
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I've tried all ways but it seems it's impossible. I never used hooks and previously I post about something similar but unsuccesfully. Previous post talks about useState is inside pricePicker function is neither a funcional component or react hook component when executed previous code lines like this:
const handleChange = (e) =>{
// in the following line is the error.
const[value, setValue] = useState(0);
}
How can I solve this issue? I need to fix it, but how? I've tried all ways but unsuccessfully.
Any one knows how can I fix this issue? It's important.
The error is actually quite simple - hooks can be used only at the top level of functional components. In your concrete example, you cannot use useState inside of function useChange.
Instead, do something like:
const pricePicker = ({/*props go here*/
const [value,setValue] = useState(0);
// handler of input change
const onChange = e => setValue(e.target.value);
// the rest of the code can stay the same
return <div className='form-group'>
<label forname={input.name}>{label}</label> <br />
<NumPad.Number
{...rest}
step={0.1}
precision={2}
placeholder={!input.value ? 'Please, type a number' : input.value}
selected={input.value ? new NumPad.Number(input.value) : null}
onKeyDown={(changedVal) => handleKeypress(changedVal)}
onBlur={(changedVal) => handleBlur(changedVal)}
onChange={onChange} /* Here's we use the onChange handler */
className='form-control'
/>
<div className='text-danger' style={{ marginBottom: '20px' }}>
{touched && error}
</div>
</div>;
}

useEffect makes onChange and typing lag/slow

I am creating a website where the user can type what he is looking for in a search bar.
Initially, I had a problem with the fact that onChange was one character behind the user search. For example, if the user search for "banana", the search was "banan".
I understood that the problem comes form the fact that setState is asynchronous.
onChange is one character on delay - Hooks
To avoid this problem, I introduced the useEffect component in my code. It works.
However now, if the user types some words, the words he types are not displayed immediately inside the search bar. They are displayed after few moments, as if it were a delay between what the user types and what it is displayed.
My searchbar component
export default function SearchBar({handlerSearchBar}) {
const classes = useStyles();
const [searchBarQuery, setSearchBarQuery] = React.useState([""])
function handleChange(event) {
setSearchBarQuery(event.target.value);
console.log(searchBarQuery)
}
useEffect(() => {
console.log("Search message inside useEffect: ", searchBarQuery);
handlerSearchBar(searchBarQuery)
});
return (
<form className={classes.container} noValidate autoComplete="off">
<TextField
required
id="standard-full-width"
label="Searchbar"
style={{ marginLeft: 40, marginRight: 40 }}
placeholder="Write your query"
// helperText="The results will appear below!"
fullWidth
margin="normal"
InputLabelProps={{
shrink: true,
}}
onChange={handleChange}
/>
</form>
);
}
handlerSearchBar function
It a function that is passing the results from my search-bar component to its parents and then, to its grandparents (SearchForm).
The grandparents SearchForm is setting one of its state to what is passed via the searchbar handlerSearchBar function:
function SearchForm() {
const [searchBarQueryParent, setSearchBarQueryParent] = React.useState([""])
function handlerSearchBar(searchBarQuery) {
setSearchBarQueryParent(searchBarQuery)
console.log(searchBarQuery)
}
return (something)
}
My question is: why is the display of the search words so much delayed than the their actual typing?
I think what is happening is that useEffect is called for each key stroke, and that is what it is so slow.
I tried to call useEffect on onSubmit but there is no improvement.
Or it is my handlerSearchBar function that makes everything slow
I resolved it by changing from onChange to onBlur.
I am not totally sure why the change works, but it does work.
You might wanna try to make it so that the useEffect fires only when searchBarQuery has been updated. i.e. as a callback upon setSearchBarQuery. You might do;
//...
useEffect(() => {
console.log("Search message inside useEffect: ", searchBarQuery);
handlerSearchBar(searchBarQuery)
}, [searchBarQuery]);
// ...
Note the second argument, an array, in useEffect to make that only run when that item has changed.
I might be a bit late to the game here, but what I did was to use a debounce function. This will check the input values every 1 sec, and will remove the lag as well
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import { TextField } from '#esgian/esgianui';
function SomeComp(props) {
const [myValue, setMyValue] = useState(null);
const [fetchDataSwitch, setFetchDataSwitch] = useState(false);
// Limit the number of requests sent
const debouncedFetchData = debounce((cb) => {
cb(!fetchDataSwitch);
}, 1000);
useEffect(() => {
debouncedFetchData((res) => {
setFetchDataSwitch(res);
});
}, [myValue]);
useEffect(() => {
// check if input Is valid
// fire API request
return () => {
// Clean up
};
}, [fetchDataSwitch]);
return (
<TextField
InputProps={{
inputProps: { min: 0 }
}}
fullWidth
placeholder={'Max transit time...'}
type={'number'}
value={myValue || ''}
onChange={({ target }) => setMyValue(target.value)}
/>
);
}
SomeComp.propTypes = {};
SomeComp.defaultProps = {};
export default SomeComp;
<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>

Categories