how to use useRef from outside of the component - reactjs

what i am trying to achieve that i made a react custom component for an input field looks like this in a seperate folder with name input.js
export function Input({id,type,label,name}) {
return (
<div className="form_container">
<input type={type} id={id} className="form__input" name={name} autoComplete="" placeholder=" " />
<label htmlFor="email" className="form__label">{label}</label>
</div>
)
}
and i used it inside my contact.js file which i want to use multiple times like this
const fname = useRef(null);
<form action="" onSubmit={sendEmail}>
<Input ref={fname} id='inputName' type="text" name="fname" label="Name" />
<button type='submit'> Submit </button>
</form>
const sendEmail = (e) => {
e.preventDefault();
console.log(fname.current.value);
}
but i am getting this error while trying to refresh the page and i cannot access the input to get the value of it
react-dom.development.js:67 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

only the old class components can be given refs natively.
use need useImperativeHandle hook combined with forwardRef
https://reactjs.org/docs/hooks-reference.html#useimperativehandle

Related

React can not use conditionally rendering on a component due to variables being nested inside of my form

I want to conditionally render a component in this case if the user submits a wrong answer, the input is stored as a javascript object called data and gets converted to a string called userInput.
After looking around, I got recommended to create a conditional rendering with a state outside of the form, but the problem I ran across is that since the variables are initialized inside of my form, i can't use ternaries to conditionally render my component in so I'm a bit stuck in what to do.
<main class="gameSection">
<h1>Welcome to League of Wordle!</h1>
<form
onSubmit={handleSubmit((data) => {
let userInput = data.guess;
console.log(userInput);
const championList = Object.keys(champions);
if (userInput.valueOf().toUpperCase() !== correctChampion.valueOf().toUpperCase()) {
<Wrong text="Class" alt="wrong img" img={wrong} />
}
})
}
>
<input
{...register("guess")} class="guess_input" placeholder="Enter Champion Name Here" type="text" />
<input class="guess_input" type="submit" />
</form>
</main>
I suggest you create a state to track if any answers submitted is wrong.
const [isWrong, setIsWrong] = useState(false)
Now, after each submit, update isWrong's value based on input.
onSubmit={handleSubmit((data) => {
let userInput = data.guess;
console.log(userInput);
const championList = Object.keys(champions);
if (userInput.valueOf().toUpperCase() !== correctChampion.valueOf().toUpperCase()) {
setIsWrong(true)
}
else {setIsWrong(false) } // logic in case of correct answer
})
}
Finally, you can implement conditional rendering:
<main class="gameSection">
<h1>Welcome to League of Wordle!</h1>
<form
...
>
<input
{...register("guess")} class="guess_input" placeholder="Enter Champion Name Here" type="text" />
<input class="guess_input" type="submit" />
{isWrong && <Wrong text="Class" alt="wrong img" img={wrong} /> }
</form>
</main>

How to access state from components to the parent component

I have a form in which all input, select tags are separate components and each component but the submit button is in the form it self like-
<form>
<InputComp1 />
<InputComp2 />
<Select1 />
<Select2 />
<button type='submit' value='Register'/>
</form>
So how do I collect all state from various components and when user clicks on the submit the values get submitted.?
Is this approach while dealing with forms right? or should I manage state of all tags in the same component?
Manage the state of all inputs/selects in this component. You can pass values and handler functions to the inputs using props.
There is no "right" approach, the answer depends on the context.
You can have form as a controlled component, where you manage the state of all tags (while passing callbacks down the tree) as you suggested and as mentioned in docs.
Or, you can have it as uncontrolled component, for example:
const Input = ({ name }) => {
return <input name={name} />;
};
const Component = () => {
return (
<>
<form
onSubmit={(e) => {
e.preventDefault();
const data = new FormData(e.target);
const entries = data.entries();
for (let entry of entries) {
console.log(entry);
}
}}
>
<Input name="username" />
<Input name="email" />
<input type="submit" value="Submit" />
</form>
</>
);
};
See controlled vs uncontrolled components.
Yes you should manage the state in the parent component itself and pass the onchange handler and value of that field as props inside the child components to update the value of the form fields.
Solution #1 would be "manage state react way". With this you should store state you need to share between components somewhere in their common ancestor. In your case it would be component that holds Form
Solution #2 applicable only if you use real form and form controls. Handle 'submit' event from the form and get all you need to submit from form data.
Solution #3 applicable only if you use some sort of "state manager". Follow instructions and best practices of the library you use.
In some cases you can mix and match that solutions. For example I still recommend to handle 'submit' event on form regardless of solution.
there is a concept of state lifting in react:
create a controlled form here and for every child, component pass a function to get the data from child components to parent one. by doing this you can submit all the values once.
here is the example
import React, {useState} from 'react';
const ChildInput = ({onChange, id}) => {
return(
<input
key={id}
type="text"
placeholder="enter name"
onChange={onChange}
/>
)
}
const Parent = () => {
const [name, setName] = useState('');
const onSubmit =(e)=>{
e.preventDefault();
// append your all data here just like child component
data = {name}
}
return(
<form onSubmit={onSubbmit}>
<ChildInput onChange={()=>setName(e.target.value)} id="name" />
<button type="submit" value="submit"/>
</form>
)}
for more information check this one: https://reactjs.org/docs/glossary.html#controlled-vs-uncontrolled-components

In this basic react application, why does the custom component `TextInput` allow me to type stuff in even though the regular `input` field doesn't?

I have a basic react form that I am testing out currently. It's still incomplete, but I have discovered unexpected behaviour.
The regular input field doesn't allow me to type in anything, as I am not updating the state yet. However, my custom TextInput component does allow me to type stuff in... Surprising, as I said before, I am not using setValues to update the state yet.
import React, { useState } from 'react';
const App = () => {
const [values, setValues] = useState({
firstName: '',
lastName: ''
});
return (
<div>
<form>
{/* [EXPECTED] this doesn't allow anything to be typed in at the front-end... which is expected...
... as I am not using `setValues` to update the state yet */}
<input
type="text"
id="first-name"
name="firstName"
value={values.firstName}
/>
{/* [NOT EXPECTED] this does allow stuff to be typed in at the front-end... which is strange...
... as I am not using `setValues` to update the state yet */}
<TextInput
id="last-name"
name="lastName"
value={values.lastName}
/>
<button type="submit">
Register
</button>
</form>
</div>
);
};
const TextInput = props => {
return (
<input
type="text"
id={props.id}
name={props.name}
/>
/* <span id={props.id + '-error'}>{props.title}</span> */
);
};
export default App;
Can anybody help me to explain why the difference?
Your first input is controlled - it has a value prop which is used to determine what the value of the element should be when rendered:
<input
type="text"
id="first-name"
name="firstName"
value={values.firstName}
/>
No matter what you type into it, since it's "controlled", the value that exists in it will always be what's currently in state as values.firstName.
In contrast, your second input is uncontrolled. It has no value prop:
<input
type="text"
id={props.id}
name={props.name}
/>
So, since you're not giving React any directives on what its value should be while being rendered, you can type whatever you want into it, and it won't be in conflict with React state.

Radio ref on react

I have radio input use want to use ref to show the result on console.
I always get second value even when I have choose the first one.
This is the constructor
this.inputKelamin = React.createRef();
and rendered like this
<div>
Jenis Kelamin :
<input name="kelamin" type="radio" value="laki - laki" ref={this.inputKelamin}/>
Laki - laki
<input name="kelamin" type="radio" value="perempuan" ref={this.inputKelamin}/>
Perempuan
</div>
onSubmit, I put it like this via console :
alamat : ${this.inputAlamat.current.value}
the result is always "perempuan"
This is not for production, just a learning purpose, thank you
You're using same ref for both the element, second ref={this.inputKelamin} overrides the first one and it always points to the second radio button.
From official docs
Refs provide a way to access DOM nodes or React elements created in the render method.
You should create 2 different refs for both inputs.
And you're checking the wrong property here
this.inputAlamat.current.value
value will always be the attribute value you gave value="perempuan".
In case of radio you should look at the checked property, which tells you whether it was selected
this.inputKelamin.current.checked
Also, you might want to look at controlled and un-conntrolled components
In order to get just one data from multiple radio you cam simply do this :
Example with typescript :
import React, { useRef } from "react";
function Form() {
const inputKelamin = useRef() as React.MutableRefObject<HTMLInputElement>;
const inputLaki = useRef() as React.MutableRefObject<HTMLInputElement>;
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const selectedRadio = inputKelamin.current.checked ? inputKelamin.current.value : inputLaki.current.value
console.log(selectedRadio);
}
return (
<form className="form" onSubmit={handleSubmit}>
Jenis Kelamin :
Laki - laki <input type="radio" value="laki - laki" ref={inputKelamin} /> <br />
Perempuan <input type="radio" value="perempuan" ref={inputLaki} /> <br />
<button type="submit">Submit</button>
</form>
);
}
export default Form;

How to make react semantic UI TextArea with default value, but still allows user to change the value?

I have tried using defaultValue and value in TextArea but it wont allow user to change the value.
The parent div of the textarea is redux-form Field. And try to pass the value stored in redux state to the textarea
<Field
name="message"
component={renderField}
...
onChange={ (e: any) =>
triggerHoldResponse(e.target.value, conversation.id)
}
/>
const renderField = ({input, type, meta: { error }}: any) => (
<div>
<TextArea
className={styles.textarea}
{...input}
placeholder={placeholder}
type={type}
/>
<div className={styles.errorSignal}>
{error}
</div>
</div>
);
SUI TextArea is base class for Form.TextArea, and both uses the same prop value for setting the default Text Value for the textarea.
Following Code works for me:
import React from 'react'
import { Form, Input, TextArea, Button } from 'semantic-ui-react'
const FormTextAreaExample = () => (
<Form
<Form.TextArea
autoHeight
onChange={this.handleMessageChange}
label="Message"
value={mesg}
placeholder="Enter your request message"
rows={3}
/>
</Form>
)
export default FormTextAreaExample;
Where value={mesg}, sets the default state of textarea (is set).
If you are using Semantic UI react you can use a Form.Field tag
You can set a default value and do what you are asking by using
"defaultValue='default text you are trying to display'
You can see a Form.Field Example below.
<Form>
<Form.Field
name="description"
required control={TextArea}
width={8}
onChange={this.handleChange}
label="Event Description"
defaultValue="Default text..."
type="text"
placeholder="Describe your event!"/>
</Form>
Note: defaultValue will override your placeholder and the defaultValue will not be removed when the click on textarea.
If you want to just display info of what the textarea is for I would use placeholder.
You can link the value of the Textarea input to that of a value from state such that you can set the default text for the text but also allow for the state to be updated in turn updating the value. You can accomplish this using linkstate.
Also if you already have the date in in your store you can set it up on the ComponentDidMount lifecycle event. to set the value of the linked state value therefore setting a default value.
Example usage from the docs:
import linkState from 'linkstate';
class Foo extends Component {
state = {
text: ''
};
render(props, state) {
return (
<input
value={state.text}
onInput={linkState(this, 'text')}
/>
);
}
}
https://github.com/developit/linkstate
An alternative way is to use the textarea tag since I was not able to find proper solution
example:
<label className="wps-label">Message</label>
<textarea
name="message"
placeholder='Your message'
onChange={(e)=> setMessage(e.target.value)}
>
{message}
</textarea>
<Form.Checkbox
name='show_tooltip'
label='Show popup or tooltip when hovering over the spoiler'
value='yes'
onChange={onChange}
defaultChecked={data?.show_tooltip}
/>
or via react-final-form
<Form
initialValues={{
title: modalView === 'add' ? '' : data?.title?.rendered,
content: modalView === 'add' ? '' : data?.content?.rendered
}}
...

Resources