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

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>;
}

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)
};

Get text Input Value React

I'm trying to track the text input value using states but "e.target.value" doesn't seem to work(maybe because my component is declared as a function). Is there any other way I can do it?
const UncontrolledDiagram = ({ sentence }) => {
// create diagrams schema
const [schema, { onChange, addNode, connect, removeNode }] = useSchema(initialSchema);
const [selected, setSelected] = useState([]);
const [textInput,setInput]=useState('')
const handleTextChange=(e)=>{
setInput(e.target.value);
console.log(textInput);
}
This is the input field I am tracking:
const conditionalDisplay=(id)=>{
const nodeToCheck=schema.nodes.find(node=>node.id=== id);
if(nodeToCheck.form===null){
return(
<div>
<label>Form: </label><input style={{ width: '25px', height: '12px' }} onChange={handleTextChange} type='text'></input>
<button className='buttonInputSubmit'>+</button>
</div>
)
}
else{
return(
<div style={{display: 'flex',margin: '0'}}>
<label>Form: </label><p style={{color: 'yellow', marginLeft:'2px'}}>{nodeToCheck.form}</p>
</div>
)
}
}
It works, your console.log(textInput) still has the old state because state is set asynchronously. It will set the state as soon as your function has been fully executed.
const handleTextChange=(e)=>{
setInput(e.target.value);
}
useEffect(() => {
console.log(textInput);
}, [textInput])

Conditionally render part of object onClick inside a map (REACT.js)

I am trying to conditionally render part of an object (user comment) onClick of button.
The objects are being pulled from a Firebase Database.
I have multiple objects and want to only render comments for the Result component I click on.
The user comment is stored in the same object as all the other information such as name, date and ratings.
My original approach was to set a boolean value of false to each Result component and try to change this value to false but cannot seem to get it working.
Code and images attached below, any help would be greatly appreciated.
{
accumRating: 3.7
adheranceRating: 4
cleanRating: 2
date: "2020-10-10"
place: "PYGMALIAN"
staffRating: 5
timestamp: t {seconds: 1603315308, nanoseconds: 772000000}
userComment: "Bad"
viewComment: false
}
const results = props.data.map((item, index) => {
return (
<div className='Results' key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={'read-only'}
value={item.accumRating}
style={{
width: 'auto',
alignItems: 'center',
}}
/>
<button>i</button>
{/* <span>{item.userComment}</span> */}
</div >
)
})
You have to track individual state of each button toggle in that case.
The solution I think of is not the best but you could create a click handler for the button and adding a classname for the span then check if that class exists. If it exists then, just hide the comment.
Just make sure that the next sibling of the button is the target you want to hide/show
const toggleComment = (e) => {
const sibling = e.target.nextElementSibling;
sibling.classList.toggle('is-visible');
if (sibling.classList.contains('is-visible')) {
sibling.style.display = 'none'; // or set visibility to hidden
} else {
sibling.style.display = 'inline-block'; // or set visibility to visible
}
}
<button onClick={toggleComment}>i</button>
<span>{item.userComment}</span>
You can try like this:
const [backendData, setBackendData] = useState([]);
...
const showCommentsHandler = (viewComment, index) => {
let clonedBackendData = [...this.state.backendData];
clonedBackendData[index].viewComment = !viewComment;
setBackendData(clonedBackendData);
}
....
return(
<div>
....
<button onClick={() => showCommentsHandler(item.viewComment, index)}>i</button>
{item.viewComment && item.userComment}
<div>
You can store an array with that places which are clicked, for example:
const [ selectedItems, setSelectedItems] = React.useState([]);
const onClick = (el) => {
if (selectedItems.includes(el.place)) {
setSelectedItems(selectedItems.filter(e => e.place !== el.place));
} else {
setSelectedItems(selectedItems.concat(el));
}
}
and in your render function
const results = props.data.map((item, index) => {
return (
<div className='Results' key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={'read-only'}
value={item.accumRating}
style={{
width: 'auto',
alignItems: 'center',
}}
/>
<button onClick={() => onClick(item)}>i</button>
{ /* HERE */ }
{ selectedItems.includes(item.place) && <span>{item.userComment}</span> }
</div >
)
})
You need to use useState or your component won't update even if you change the property from false to true.
In order to do so you need an id since you might have more than one post.
(Actually you have a timestamp already, you can use that instead of an id.)
const [posts, setPosts] = useState([
{
id: 1,
accumRating: 3.7,
adheranceRating: 4,
cleanRating: 2,
date: "2020-10-10",
place: "PYGMALIAN",
staffRating: 5,
timestamp: { seconds: 1603315308, nanoseconds: 772000000 },
userComment: "Bad",
viewComment: false
}
]);
Create a function that updates the single property and then updates the state.
const handleClick = (id) => {
const singlePost = posts.findIndex((post) => post.id === id);
const newPosts = [...posts];
newPosts[singlePost] = {
...newPosts[singlePost],
viewComment: !newPosts[singlePost].viewComment
};
setPosts(newPosts);
};
Then you can conditionally render the comment.
return (
<div className="Results" key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={"read-only"}
value={item.accumRating}
style={{
width: "auto",
alignItems: "center"
}}
/>
<button onClick={() => handleClick(item.id)}>i</button>
{item.viewComment && <span>{item.userComment}</span>}
</div>
);
Check this codesandbox to see how it works.

Problem converting state in class to function in react

I have been converting all my classes to functions to update my react projects. I am getting a:
'ESLint has encountered a parsing error' in my code. If I remove ONE line, the error disappears. Here's the code
import React, { useState } from "react";
import orderBy from "lodash/orderBy";
import SelectField from "#material-ui/core";
import MenuItem from "#material-ui/core";
import TextField from "#material-ui/core";
import Table from "./Table";
const invertDirection = {
asc: "desc",
desc: "asc"
};
const Patients = props =>
{
var handleRemove = (i) => {
setPatientsData(patientsData.filter((row, j) => j !== i));
};
var handleSelect = (i) =>
{
setEditIdx(i);
}
const [editIdx, setEditIdx] = useState(-1);
const [columnToSort, setColumnToSort] = useState('');
const [sortDirection, setSortDirection] = useState('asc');
const [patientsData, setPatientsData] = useState([]);
const [columnToQuery, setColumnToQuery] = useState('');
const [columnQueryValue, setColumnQueryValue] = useState('');
var handleSort = columnName =>
{
if (columnName === columnToSort) {
setSortDirection(invertDirection[sortDirection]);
}
else {
setSortDirection("asc");
}
setColumnToSort(columnName);
};
var handleQueryChange = (value) =>
{
setColumnQueryValue({ value });
}
const lowerCaseQuery = this.state.query.toLowerCase();
return (
<div>
<div style={{ display: "flex" }}>
<div style={{ display: "flex", margin: "auto" }}>
<TextField
hintText="Query"
floatingLabelText="Query"
value={columnQueryValue}
onChange={(event) => setColumnQueryValue({event.target.value })} --->>>ERROR
floatingLabelFixed
/>
<SelectField
style={{ marginLeft: "1em" }}
floatingLabelText="Select a column"
value={columnToQuery}
onChange={(event, index, value) => setColumnToQuery({value }) }
>
<MenuItem value="firstName" primaryText="First Name" />
<MenuItem value="lastName" primaryText="Last Name" />
<MenuItem value="username" primaryText="Username" />
<MenuItem value="email" primaryText="Email" />
</SelectField>
</div>
</div>
<Table
handleSort={handleSort}
handleRemove={handleRemove}
editIdx={editIdx}
handleSelect={handleSelect}
columnToSort={columnToSort}
sortDirection={sortDirection}
data={orderBy(
columnQueryValue
? patientsData.filter(x =>
x[columnToQuery]
.toLowerCase()
.includes(lowerCaseQuery)
)
: patientsData,
columnToSort,
sortDirection
)}
header={[
{
name: "First name",
prop: "firstName"
},
{
name: "Last name",
prop: "lastName"
},
{
name: "Username",
prop: "username"
},
{
name: "Email",
prop: "email"
}
]}
/>
</div>
);
}
export default Patients;
Specifically, this line:
onChange={(event) => setColumnQueryValue({event.target.value })}
I've spent 24 hours trying to fix it, tried everything, but still get the error. Any help would be greatly appreciated!
ADDED: I fixed a typo that was not causing the problem. Specifically, the interpreter complains about the 'event.target.value' I am passing to the setColumnQueryValue which is the state setter for ColumnQueryValue, which is a string. Which is SUPPOSED to be what a text field returns. (event.target.value). That is the code that is the issue and that I cannot figure out why it's complaining.
Firstly, You don't have any setQueryValue function in your code, I think you misinterpreted with setColumnQueryValue. Secondly, you're passing an object and didn't define a key for the event.target.value. As I see that you initialized columnQueryValue with empty string so you should pass a string value in function.
onChange={(event) => setColumnQueryValue(event.target.value)}
Ok, well separating the onChange event into a called function made the error go away, but I still don't understand what I did wrong.
Added:
var handleOnChange = event =>
{
setColumnQueryValue(event.target.value);
};
Changed:
onChange={(event) => setColumnQueryValue({event.target.value })}
to
onChange={handleOnChange}
No difference I can detect in the actual code, but one worked, the other didn't. I hate javascript. ;-)

React Material UI - Make Slider update state

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.

Categories