I've created a custom hook to use with a form I've built using semantic-ui-react. The code for the hook looks like this (taken from here )
import React, { useState } from 'react'
const useForm = callback => {
const [inputs, setInputs] = useState({})
const handleSubmit = event => {
if (event) {
event.preventDefault()
}
}
const handleInputChange = event => {
event.persist()
setInputs(inputs => ({
...inputs,
[event.target.name]: event.target.value,
}))
}
return {
handleSubmit,
handleInputChange,
inputs,
}
}
export default useForm
It works perfectly well with all of my text based inputs at the moment, but I've added some radio buttons like this:
<Form.Group inline>
<label>Number of Hours</label>
<Form.Radio
label="<3 Hours"
name="hours"
onChange={handleInputChange}
value="1"
checked={inputs.hours === 1}
/>
<Form.Radio
label="3+ Hours"
name="hours"
onChange={handleInputChange}
value="2"
checked={inputs.hours === 2}
/>
</Form.Group>
But the hook doesn't work properly. I've done some digging and it looks like it's because the onChange (I've tried onClick too) seems to fire on the label in semantic-ui-react, so the event doesn't contain the proper target name or value. The only workaround I can think of is to write some custom handler that creates a fake event that looks for the hidden radio input :before the label, but it seems like there should be a cleaner way.
Updated Workaround
I created a custom handler for radios as a temporary workaround and also adjusted the checked part to put quotes around the value. It works, but if anyone knows a better way, please share.
This is the custom handler.
const radioHandleInputChange = e => {
let { value, name } = e.target.previousSibling
e.target.name = name
e.target.value = value
handleInputChange(e)
}
Related
export default function Form() {
const [user, setUser] = useState({
name: "",
numOfQs: 0
})
console.log(user)
function handleUserDataChange(event) {
setUser(prevUser => {
return {
...prevUser,
[event.target.name]: event.target.value
}
})
}
return (
<>
<input
type="text"
placeholder="username"
name="name"
value={user.name}
onChange={handleUserDataChange} />
<input
type="number"
name="numOfQs"
value={user.numOfQs}
onChange={handleUserDataChange} />
</>
)}
I was trying to build my form using react, and when I tried to use input[type: number] on the form field it was giving me this error, don't know why. I was reading through react docs about forms, and everything from the checkbox, radio buttons, textarea was all working fine. but when I used an input element of the type number, I got the following error.
*!Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property target on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist(). See fb.me/react-event-pooling for more information.
so, the problem only arises when an input of type "number" is introduced. when I remove it all of my other form elements work fine.
I'm still in the learning phase of react. please help me out.
This happened because the event that passed into the function is used as an asynchronous event.
To fix this, decompose the event object
function handleUserDataChange(event) {
const { name, value } = event.target;
setUser(prevUser => {
return {
...prevUser,
[name]: value
}
})
}
Having such simple React (with the react-hook-form library) form
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, formState: { errors } } = useForm();
const firstNameRef1 = useRef(null);
const onSubmit = data => {
console.log("...onSubmit")
};
const { ref, ...rest } = register('firstName', {
required: " is required!",
minLength: {
value: 5,
message: "min length is <5"
}
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<hr/>
<input {...rest} name="firstName" ref={(e) => {
console.log("...ref")
ref(e)
firstNameRef1.current = e // you can still assign to ref
}} />
{errors.firstName && <span>This field is required!</span>}
<button>Submit</button>
</form>
);
}
I'm getting:
...ref
...ref
...onSubmit
...ref
...ref
in the console output after the Submit button click.
My question is why is there so many ...ref re-calls? Should't it just be the one and only ref call at all?
P.S.
When I've removed the formState: { errors } from the useForm destructuring line above the problem disappears - I'm getting only one ...ref in the console output as expected.
It is happening because you are calling ref(e) in your input tag. That's why it is calling it repeatedly. Try removing if from the function and then try again.
<input {...rest} name="firstName" ref={(e) => {
console.log("...ref")
ref(e) // <-- ****Remove this line of code*****
firstNameRef1.current = e // you can still assign to ref
}} />
My question is why is there so many ...ref re-calls?
This is happening because every time you render, you are creating a new function and passing it into the ref. It may have the same text as the previous function, but it's a new function. React sees this, assumes something has changed, and so calls your new function with the element.
Most of the time, getting ref callbacks on every render makes little difference. Ref callbacks tend to be pretty light-weight, just assigning to a variable. So unless it's causing you problems, i'd just leave it. But if you do need to reduce the callbacks, you can use a memoized function:
const example = useCallback((e) => {
console.log("...ref")
ref(e);
firstNameRef1.current = e
}, [])
// ...
<input {...rest} name="firstName" ref={example} />
I'm trying to use a redux store state value in an input, but I have a custom useInput hook, that I can't figure how to make them work together.
I built a react custom useInput hook that handles the value, and change/blur events. Used like:
const {
value: titleValue,
error: titleError,
inputChangedHandler: titleChangedHandler,
inputBlurHandler: titleBlurHandler,
setValue: setTitleValue,
} = useInput(validateTitle);
<input
error={titleError ?? false}
id="title"
name="title"
type="text"
placeholder="Stream Title"
autoComplete="off"
value={titleValue}
onChange={titleChangedHandler}
onBlur={titleBlurHandler}
/>
my problem is that if I want to use it in an 'edit' components, which I want to fetch the initial values from an existing state, I cannot do it, because the input value is bound to the useInput value property.
so I can't do this, with my useInput custom hook:
const selectedItem = useSelector((state) => state.items.selectedItem);
<input
error={titleError ?? false}
id="title"
name="title"
type="text"
placeholder="Stream Title"
autoComplete="off"
value={selectedItem.title} <-- use the state selectedItem value
onChange={titleChangedHandler}
onBlur={titleBlurHandler}
/>
my useInput customer hook is just in charge of validation of the input value. It would work well if I could initially set the value to the store value, but my component is using useEffect to call an API getById(id) so the first time the component loads there is still no selectedItem, so I cannot initially set the useInput to the selectedItem.title.
this is my useInput custom hook code:
import { useState } from 'react';
const useInput = (validate) => {
console.log('in useInput');
const [value, setValue] = useState('');
const [isTouched, setIsTouched] = useState(false);
const validationResult = validate(value);
const error = !validationResult.isValid && isTouched && validationResult.message;
const inputChangedHandler = (event) => {
setIsTouched(true);
setValue(event.target.value);
};
const inputBlurHandler = () => {
setIsTouched(true);
};
return { value, error, inputChangedHandler, inputBlurHandler, setValue };
};
export default useInput;
How can I fix it?
React Code:
import { useState } from "react";
export default function IndexPage() {
const [text, setText] = useState("");
const autoComplete = (e) => {
setText({ value: e.target.value });
};
return (
<form>
<input type="text" placeholder="search here" onChange={autoComplete} />
<button type="submit" disabled={!text}> find </button>
</form>
);
}
SandBox Link: https://codesandbox.io/s/practical-panini-slpll
Problem:
when Page Loads, the find button is disabled at first because the input field is empty which is good. But,
Now, when I start typing something. then delete them all with Backspace key. the find button is not disabling again.
I want:
I just want to have my find button disabled if the input field is empty
Where I'm doing wrong?
It's a common mistake coming from Class Components, state setter in hooks does not merge state value as in classes.
You initialized your state as a string (useState('')) and then assigned it as an object
const autoComplete = (e) => {
setText({ value: e.target.value });
};
// State is an object { value: 'some text' }
Fix your set state call:
const autoComplete = (e) => {
setText(e.target.value);
};
// State is a string 'some text'
Then to be more verbose you want to check if the string is empty:
<button disabled={text === ''}>find</button>
disabled={!text.value}
Should do the trick.
With this function
const autoComplete = (e) => {
setText({ value: e.target.value });
};
you are writing your input in text.value, not in text.
use this code for check text
<button type="submit" disabled={text==''}> find </button>
I am playing around with the new React-Redux Hooks library
I have an react component that has two input fields that update to the react store using useState() - desc and amount. In order to update changes to the the redux store when field has been edited I use onBlur event and call dispatch to the redux store. That works fine.
When I want to clear the fields from another component I would like this to work in same manner as for class based functions via connect & map State to Props, however to to this with functional component I need to utilise useSelector(). I cannot do this as the identifiers desc and amount are already used by useState()
What am I missing here?
import { useDispatch, useSelector } from "react-redux"
import { defineItem, clearItem } from "../store/actions"
const ItemDef = props => {
const dispatch = useDispatch()
const [desc, setDesc] = useState(itemDef.desc)
const [amount, setAmount] = useState(itemDef.amount)
//MAPSTATETOPROPS
//I WANT TO HAVE THESE VALUES UPDATED WHEN REDUX STORE CHANGES FROM ANOTHER COMPONENT
//THESE LINES WILL CAUSE ERROR to effect - identifier has already been declared
const desc = useSelector(state => state.pendingItem.desc)
const amount = useSelector(state => state.pendingItem.amount)
return (
<div>
<p>Define new items to be added below - before clicking Add Item</p>
<input
value={desc}
type="text"
name="desc"
placeholder="Description of Item"
onChange={e => setDesc(e.target.value)}
//Use onBlur Event so that changes are only submitted to store when field loses focus
onBlur={e => dispatch(defineItem(desc, amount))}
/>
<input
value={amount}
type="number"
name="amount"
placeholder="Amount"
onChange={e => setAmount(e.target.value)}
//Use onBlur Event so that changes are only submitted to store when field loses focus
onBlur={e => {
dispatch(defineItem(desc, amount))
}}
/>
</div>
)
}
export default ItemDef
SOLUTION - WITH FULL CODE IN REPOSITORY
I worked out a solution by using useSelector (to map pendingItem part of redux state to itemDef) and the setEffect hook to apply useState to either state item (from input) or itemDef (from Redux State - this happens when redux is updated by another component or through the ADD ITEM TO INPUT button)
I have posted the working component below. I have also posted this small application to demonstrate how to use reacdt-redux libraries with both class based components and fuinctional components using hooks
The repository is https://github.com/Intelliflex/hiresystem
//**************************************************************************************************
//***** ITEMDEF COMPONENT - Allow entry of new Items (dispatched from button in HireList Table) ****
//**************************************************************************************************
import React, { useState, useEffect, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { defineItem, clearItem } from '../store/actions'
import _ from 'lodash'
const ItemDef = props => {
//BRING IN DISPATCH FROM REDUX STORE
const dispatch = useDispatch()
//DEFINE SELECTOR - EQUIV TO MAPSTATETOPROPS
const { itemDef } = useSelector(state => ({
itemDef: state.pendingItem
}))
const [item, setItem] = useState({ desc: '', amount: 0 })
const onChange = e => {
setItem({
...item,
[e.target.name]: e.target.value
})
}
const prevItem = useRef(item)
useEffect(() => {
//WE NEED TO CONDITIONALLY UPDATE BASED ON EITHER STORE BEING CHANGED DIRECTLY OR INPUT FORM CHANGING
if (!_.isEqual(item, prevItem.current)) {
//INPUT HAS CHANGED
setItem(item)
} else if (!_.isEqual(item, itemDef)) {
//REDUX STATE HAS CHANGED
setItem(itemDef)
}
prevItem.current = item
}, [item, itemDef]) //Note: item and ItemDef are passed in as second argument in order to use setItem
const clearIt = e => {
dispatch(clearItem())
}
const addIt = e => {
dispatch(defineItem({ desc: 'MY NEW ITEM', amount: 222 }))
}
return (
<div>
<p>Define new items to be added below - before clicking Add Item</p>
<input
value={item.desc}
type='text'
name='desc'
placeholder='Description of Item'
onChange={onChange}
//Use onBlur Event so that changes are only submitted to store when field loses focus
onBlur={e => dispatch(defineItem(item))}
/>
<input
value={item.amount}
type='number'
name='amount'
placeholder='Amount'
onChange={onChange}
//Use onBlur Event so that changes are only submitted to store when field loses focus
onBlur={e => dispatch(defineItem(item))}
/>
<button onClick={clearIt}>CLEAR ITEM</button>
<button onClick={addIt}>ADD ITEM TO INPUT</button>
</div>
)
}
export default ItemDef