useRef in <Form /> Component | Informed | PWA Studio | React - reactjs

I need to execute the form submit using useRef(). I'm trying to reference the component provided by 'informed' (PWA Studio forms lib), but the reference doesn't work.The reference isn't work to informed Form but I don't know why. I tried the same reference using normal html form <form ref={formRef}>... and in this case worked.
const formRef = useRef(null);
...
// 'informed Form'
<Form ref={formRef}>...</Form>
// It does't submit the form after function executed
const handleSubmit = () => {
formRef.current && formRef.current.submit();
};

In informed it is really not possible to refer to it through the useRef simple way. To achieve your goal you can use the getApi={formRef.current} prop in the Form component
// You can create your useRef() in this way
const formApiRef = useRef(null);
const setFormApi = useCallback(api => (formApiRef.current = api), []);
// In your form component set the apiForm
<Form getApi={setFormApi}> ... </Form>
// In your handleSubmit function in order to submit you can do this
const handleSubmit = useCallback(() => {
const { current: formApi } = formApiRef;
if (formApi) {
formApi.submitForm();
}
}, [applyCoupon, cartId]);

Related

React with Firestore autocomplete

Using ReactJS, Firestore - Firebase v9.
I have an autocomplete search bar on my web, built with just pure React. The question that I am trying to solve for at least one month is, if I can make this autocomplete input work with Firestore - (user type E.g 'elepha', auto suggestion appears with offer with word elephant - this is what I coded, and with same case, user will click on the suggestion of elephant, and this word elephant will be send to Firestore.)
Cuz there is not any solution on internet, I wonder, if my question is even possible to make, or not.
My simple autocomplete bar code - (animals_data stands for file with animals names)
and I tried to add onClick={pleaseSend} which is basic addDoc function, but when I click on the suggestion, in Firestore will only appear blank input "".
<SearchBar data={animals_data} />
And the filtering code:
function SearchBar({ placeholder, data }) {
const [filteredData, setFilteredData] = useState([]);
const [wordEntered, setWordEntered] = useState("");
const [newAnswer, setAnswer] = useState("")
const [users, setUsers] = useState([]);
const usersCollectionRef = collection(db, "Answers")
const createUser = async () => {
await addDoc(usersCollectionRef, {name: newAnswer}).then(()=>{
window.location.reload()
}).catch((err)=>{
console.log(err)
})
};
const handleFilter = (event) => {
const searchWord = event.target.value;
setWordEntered(searchWord);
const newFilter = data.filter((value) => {
return value.full_name.toLowerCase().includes(searchWord.toLowerCase());
});
if (searchWord === "") {
setFilteredData([]);
} else {
setFilteredData(newFilter);
}
};
const clearInput = () => {
setFilteredData([]);
setWordEntered("");
};
return (
<div className="search">
<div className="searchInputs">
<input
type="text"
placeholder={placeholder}
value={wordEntered}
onChange={handleFilter}
/>
</div>
{filteredData.length !== 0 && (
<div className="dataResult">
{filteredData.slice(0, 15).map((value, key) => {
return (
<a className="dataItem" onClick={createUser} target="_blank">
<p>{value.full_name} </p>
</a>
);
})}
</div>
)}
</div>
);
}
export default SearchBar;
EDIT 1: added code screenshots
web:
Thank you very much for reading.
after analyzing your code, I notice that you don't update the value newAnswer using its setter. Therefore, you should use the setter to update the state on user click and then add the firestorm document. You can do that by either using a button/unmodifiable input field in instead of an anchor tag to store the value of each option, then use this value inside the click handler to update the state and then use a useEffect to update firestore every time the state changes. Let me know if you need help with some code. Please post it below your original question as an edit without changing it.

forwardRef with custom component and custom hook

Edit: Small changes for readability.
I'm new to react and I may be in at the deep end here but I'll go ahead anyway..
I have a Login component in which I want to give the users feedback when the input elements lose focus and/or when the user clicks submit.
I am aware that I achieve a similar bahavior with useState but for the sake of education I'm trying with useRef.
I'm getting a TypeError for undefined reading of inputRef in LoginForm.js. So inputRef is not assigned a value when validateInput is called. Can anyone help me make sense of why that is and whether there is a solution to it?
LoginForm.js:
import useInput from '../../hooks/use-input';
import Input from '../../UI/Input/Input';
const LoginForm = () => {
const { inputRef, isValid } = useInput(value =>
value.includes('#')
);
return <Input ref={inputRef} />;
};
use-input.js (custom hook):
const useInput = validateInput => {
const inputRef = useRef();
const isValid = validateInput(inputRef.current.value);
return {
inputRef,
isValid,
};
};
Input.js (custom element component):
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props.input}></input>;
});
One issue that I'm seeing is that in the Input component, you're using props.input, why?
const Input = forwardRef((props, ref) => {
return <input ref={ref} {...props}></input>;
});
You want exactly the props that you're sending to be assigned to the component.
Next up, you're doing value.includes('#'), but are you sure that value is not undefined?
const { inputRef, isValid } = useInput(value =>
value && value.includes('#')
);
This would eliminate the possibility of that error.
Solving the issue with the inputRef is undefined is not hard to fix.
Afterward, you're going to face another issue. The fact that you're using useRef (uncontrolled) will not cause a rerender, such that, if you update the input content, the isValid won't update its value.
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. (React Docs)
This is a personal note, but I find uncontrolled components in general hard to maintain/scale/..., and also refs are not usually meant to do this kind of stuff. (yes, yes you have react-form-hook which provides a way of creating forms with uncontrolled components, and yes, it's performant).
In the meantime, while I'm looking into this a little more, I can provide you a solution using useState.
const useInput = (validationRule, initialValue='') => {
const [value, setValue] = useState(initialValue)
const onChange = (e) => setValue(e.target.value)
const isValid = validationRule && validationRule(value)
return {
inputProps: {
value,
onChange
},
isValid
}
}
So, right here we're having a function that has 2 parameters, validationRule and initialValue(which is optional and will default to text if nothing is provided).
We're doing the basic value / onChange stuff, and then we're returning those 2 as inputProps. Besides, we're just calling the validationRule (beforehand, we check that it exists and it's sent as parameter).
How to use:
export default function SomeForm() {
const { inputProps, isValid } = useInput((value) => value.includes('#'));
return <Input {...inputProps}/>;
}
The following part is something that I strongly discourage.
This is bad but currently, the only way of seeing it implemented with refs is using an useReducer that would force an update onChange.
Eg:
const useInput = (validationRule) => {
const [, forceUpdate] = useReducer((p) => !p, true);
const inputRef = useRef();
const onChange = () => forceUpdate();
const isValid = validationRule && validationRule(inputRef.current?.value);
return {
inputRef,
isValid,
onChange
};
};
Then, used as:
export default function SomeForm() {
const { inputRef, onChange, isValid } = useInput((value) =>
value && value.includes("#")
);
console.log(isValid);
return <Input ref={inputRef} onChange={onChange} />;
}

Is there a way to have 2 onSubmit events in React?

I was wondering if anyone could explain how I can have 2 onSubmit events in React? I have a button for clicking a form on submission, and it has a thank you message popping up, but I'm trying to clear the localStorage at the same the form is submitted. I have tried this:
const handleSubmit = (e) => {
e.preventDefault();
alert(`Thank you for your order! ^_^ Please check your texts for updates!`);
toggle()
};
const clearCartStorage = () => {
localStorage.clear();
}
<Form onSubmit={handleSubmit && clearCartStorage()}>
These work separately, but when I have the && only the clearCartStorage function will run without the handleSubmit pop up.
Make a new function which calls those other functions:
<Form onSubmit={(e) => {
handleSubmit(e);
clearCartStorage();
})>
Careful, you are invoking clearCartStorage when you create the Form component there.
<Form onSubmit={handleSubmit && clearCartStorage()}> ❌❌❌
It should take one function which can call multiple.
I would set it up like this. It's more common to keep the function definition out of the returned JSX.
const MyComponent = () => {
const handleSubmit = (e) => {
e.preventDefault();
clearCartStorage()
alertUser()
};
const clearCartStorage = () => {
localStorage.clear();
}
const alertUser = () => {
alert(`Thank you for your order! ^_^ Please check your texts for updates!`);
toggle()
}
return <Form onSubmit={handleSubmit}>
}
Neither of the responses worked. I should have posted my whole code, but what I did to fix it was create a separate component with the new function that runs the alert and clearStorage functions. Having everything on the same component within a modal div was not working.
So I basically have:
<Modal>
<FormComponent /> // my new function in here
</Modal>

React getting state

I am currently creating my own mini-bbcode editor for my ReactJS application. I use functional components. In the TextEditor.jsx file I have the following code that handles the textarea.
const [bbcodeText, setBbcodeText] = useState("");
const update = (e) => {
setSelection(null);
setBbcodeText(e.target.value);
if (typeof props.onChange == 'function') {
props.onChange(e.target.value);
}
}
<textarea className="form-input forum__body-input" ref={textAreaRef} value={bbcodeText} onChange={update} />
The above works correctly, however I installed [emoji-mart] 1 to have access to the option to place emojis in the textarea, for which I use the following code:
const handleEmoji = (emoji) => {
let newText = bbcodeText + " " + emoji.native;
setBbcodeText(newText);
}
<Picker title="Emojis" emoji="grinning" set='apple' style={{width: '100%'}} onSelect={handleEmoji} />
The textarea updates correctly, the problem is that this TextEditor component accesses it from a parent component of the form:
const [bbcode, setBBcode] = useState();
const update = (v) => {
setBBcode(v);
};
const handlePost = async() => {
console.log(bbcode)
}
<TextEditor onChange={update} onSelect={handleEmoji} />
<button className="button purple" onClick={handlePost}>Agregar respuesta</button>
The problem occurs if I select an emoji and try to access the "bbcode" state, the result is an empty string, but if I write something after it, the emoji is already shown with the text at the end, that is, I need to fire the onChange to display the updated data.
What I need to know is a guide on how to get the updated status, if I only want to insert emojis in the textarea, for example.
In this case bbcode returns undefined if I only use "emojis" but if I write text it works correctly.
I'd suggest moving the onChange callback logic to an useEffect hook with a dependency on the bbcodeText state. This way anything that updates the bbcodeText state value will trigger the effect to invoke the onChange handler to update anything in the parent component.
const [bbcodeText, setBbcodeText] = useState("");
const update = (e) => {
setSelection(null);
setBbcodeText(e.target.value);
}
const handleEmoji = (emoji) => {
setBbcodeText(bbcodeText => bbcodeText + " " + emoji.native);
}
useEffect(() => {
if (typeof props.onChange == 'function') {
props.onChange(bbcodeText);
}
}, [bbcodeText]);

UI not re-rendering on state update using React Hooks and form submission

I'm trying to update a UI using React Hooks and a form. I have a state set to monitor the value on the form and when I click submit, I want to add this value to an array (held in state) and display it on the UI. My problem is that when I submit the value, although it is added to the array (and state is updated), the UI only updates when I change the value in the input.
My Component is as follows:
const PushToArrayUpdateState = () => {
const [array, setArray] = useState([]);
const [formVal, setFormVal] = useState(``);
const handleSubmit = event => {
event.preventDefault();
let updateArray = array;
updateArray.push(formVal);
console.log(updateArray);
setArray(updateArray);
};
return (
<div>
<form onSubmit={handleSubmit}>
<input type="text" name="arrayVal" onChange={e => setFormVal(e.target.value)} />
<input type="submit" value="Submit" />
</form>
<div>
{array.map((val, index) => <p key={index}>{val}</p>)}
</div>
</div>
);
};
You can also see this [not] working at:
https://codesandbox.io/s/p3n327zn3q
Has anyone got any suggestions as to why the setArray in the handleSubmit function is not automatically causing the component to re-render?
Instead of
let updateArray = array;
Try this:
const updateArray = [...array];
https://codesandbox.io/embed/qxk4k3zmzq
Because arrays in JS are reference values, so when you try to copy it using the = it will only copy the reference to the original array.
A similar bug can happen with the same manifestation:
const [bought, setBought] = useState([])
...
bought.push(newItem)
setBought(bought)
To fix this you need to use
const newBought = [...bought, newItem] <- new reference
setBought(newBought)

Resources