Switch does not change based on React.useState - reactjs

In this simple "demo" in CodeSandbox: https://codesandbox.io/s/cool-fast-fi426k?file=/src/App.tsx
you can see that checked actually changes, based on an external condition (inputted text length), but this does not "physically"change the Switch
import "./styles.css";
import * as React from "react";
import { Switch } from "antd";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
let [checked, setChecked] = React.useState(false);
const onSubmit = (data: string) => {
console.log("data.text: ", data.text);
let length = data.text.length;
console.log("data.text.length: ", length);
if (length > 5) {
console.log("'checked' variable has to be set as TRUE");
setChecked((checked) => true);
} else {
console.log("'checked' variable has to be set as FALSE");
setChecked((checked) => false);
}
};
const AntdOnChange = (checked) => {
console.log(`switch to ${checked}`);
};
React.useEffect(() => {
AntdOnChange(checked);
}, [checked]);
return (
<div className="App">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="text">Text</label>
<input id="text" placeholder="text" {...register("text")} />
</div>
<button type="submit">Submit</button>
</form>
<Switch
checkedChildren="ON"
unCheckedChildren="OFF"
defaultChecked
onChange={AntdOnChange}
/>
</div>
);
}
How to pass the correctly changed value of checked variable to the Switch state ?

You can achieve this by doing three things
Remove the useEffect that updates the checked state. If you pass the state into the switch component with that, it will cause multiple renders which will cause an error
Do this in your switch component. I have added the checked prop
<Switch
checkedChildren="ON"
unCheckedChildren="OFF"
defaultChecked
checked={checked}
onChange={AntdOnChange}
/>
In your AntdOnChange function, do this. This function will work independently of whatever is added in the input
const AntdOnChange = (checked) => {
setChecked((checked) => !checked);
};

Related

React function in a component

I am working in React with components, but I am not getting functions working in the Input component.
I have tried searching on component and onChange or onKeyPress but they do not show what I need. Maybe I'm searching wrong? I did get the component Button working, just not Input.
The code for the component is simple.
import React from 'react';
import styles from './Input.module.css';
function Input({inputText}) {
return <input type='number'
placeholder={inputText}
className={styles.input}
/>
}
export default Input;
The full code I want to use for component Input is as follows.
import React, {useState} from 'react';
import styles from './Food.module.css';
import axios from 'axios';
import Nutrients from '../../components/nutrients/Nutrients'
import {ReactComponent as LoadingIcon} from '../../assets/loading.svg'
import Button from "../../components/button/Button";
import Input from "../../components/input/Input";
function Food() {
const [food, setFood] = useState(null);
const [calories, setCalories] = useState('');
const [disabled, setDisabled] = useState(false);
const [error, setError] = useState('');
const [loading, toggleLoading] = useState('');
async function foodData() {
setError('');
toggleLoading('true')
try {
const result = await axios.get(`https://api.spoonacular.com/mealplanner/generate?apiKey=${process.env.REACT_APP_API_KEY}&timeFrame=day&targetCalories=${calories}`);
setFood(result.data);
} catch (error) {
setError('Oops... something went wrong. Please try again.');
console.log('Something went wrong while retrieving the data.', error);
}
toggleLoading(false);
}
function handleChange(e) {
setCalories(e.target.value);
}
function handleKeyPress(e) {
if (e.key === 'Enter') {
if (disabled) {
return;
}
foodData();
setDisabled(true);
}
}
return (
<>
{error && <p className={styles.error}>{error}</p>}
<div>
<section className={styles.food}>
<Input
inputText='Enter caloriessss'
onChange= {handleChange}
onKeyPress={handleKeyPress}
/>
<Button
onClick={() => {
foodData();
setDisabled(true);
}}
buttonText='Click for your daily meals'
/>
</section>
{loading && <LoadingIcon className={styles.loader}/>}
{food && <Nutrients mealsData={food}/>}
</div>
</>
);
}
export default Food;
The thing I want is for the two functions onChange and onKeyPress to work in the Input component.
Anyone has any ideas? I also tried using () and ''.
Because you don't use props onChange and onKeyPress in Input component. Just add it like this:
function Input({ inputText, onChange, onKeyPress }) {
return (
<input
type="number"
placeholder={inputText}
className={styles.input}
onChange={onChange}
onKeyPress={onKeyPress}
/>
);
}
Or shorter way:
function Input({ inputText, ...props }) {
return (
<input
type="number"
placeholder={inputText}
className={styles.input}
{...props}
/>
);
}
React has no way of knowing the onChange and onKeyPress props should be passed to the input inside of your Input component.
You must pass them along explicitly.
// Add them to your destructured props
function Input({inputText, onChange, onKeyPress}) {
return <input type='number'
onChange={onChange} // Pass them to the input
onKeyPress={onKeyPress} // Pass them to the input
placeholder={inputText}
className={styles.input}
/>
}
In the component Input, your return should be: <input></input>
Because JSX needs to closed tags.

React hook form v7 Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()

Getting the error in browser Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()
My code:
import { yupResolver } from '#hookform/resolvers/yup'
import { useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { contactSchema } from 'schemas/schemas'
import { InputFloatLabel } from './components/Inputs/InputFloatLabel'
type TypeFormInput = {
name: string
email: string
textarea: string
}
export const Register = () => {
const [isLoading, setIsLoading] = useState(false)
const {
register,
handleSubmit,
formState: { errors },
} = useForm<TypeFormInput>({ resolver: yupResolver(contactSchema) })
const onSubmit: SubmitHandler<TypeFormInput> = async ({ name, email }) => {
console.log('🚀 ~ file: Register.tsx ~ line 25 ~ email', email)
console.log('🚀 ~ file: Register.tsx ~ line 25 ~ name', name)
}
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<InputFloatLabel
type="text"
placeholder="Name"
{...register('name')}
/>
<button type="submit">{isLoading ? 'Loading' : 'Send Mail'}</button>
</div>
</form>
</div>
)
}
And the Input comp:
import { useState } from 'react'
type typeInput = {
placeholder: string
type?: string
}
export const InputFloatLabel: React.FC<typeInput> = ({ type, placeholder, ...props }) => {
const [isActive, setIsActive] = useState(false)
const handleTextChange = (text: string) => {
if (text !== '') setIsActive(true)
else setIsActive(false)
}
return (
<div>
<input
{...props}
id={placeholder}
type={placeholder ? placeholder : 'text'}
onChange={(e) => handleTextChange(e.target.value)}
/>
<label htmlFor={placeholder}>
{placeholder ? placeholder : 'Placeholder'}
</label>
</div>
)
}
I don't have this issue with ChakraUI that I've built but now just doing plain input as a separate component getting that issue.
I have tried some suggestions from here, but still can't fix it: https://github.com/react-hook-form/react-hook-form/issues/85
So the issue is that I think that the {...register("name"}} line actually includes a ref property. You could console.log that out to verify; this is what I found to be true when using {...field} with the ControlledComponent. A very quick fix to get rid of the console error is to just, after the line with the spread, to add a ref={null} to override this ref that is being passed in from the library.
You forgot to forward the ref in your InputFloatLabel. See https://reactjs.org/docs/forwarding-refs.html
In your case it would look like this:
export const InputFloatLabel: React.FC<typeInput> =
// Use React.forwardRef
React.forwardRef(({type, placeholder, ...props}, ref) => {
const [isActive, setIsActive] = useState(false)
const handleTextChange = (text: string) => {
if (text !== '') setIsActive(true)
else setIsActive(false)
}
return (
<div>
<input
ref={ref /* Pass ref */}
{...props}
id={placeholder}
type={placeholder ? placeholder : 'text'}
onChange={(e) => handleTextChange(e.target.value)}
/>
<label htmlFor={placeholder}>
{placeholder ? placeholder : 'Placeholder'}
</label>
</div>
)
})
In https://react-hook-form.com/faqs, scroll to "How to share ref usage?" may help?
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const firstNameRef = useRef(null);
const onSubmit = data => console.log(data);
const { ref, ...rest } = register('firstName');
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...rest} name="firstName" ref={(e) => {
ref(e)
firstNameRef.current = e // you can still assign to ref
}} />
<button>Submit</button>
</form>
);
}
the field element and register function pass a ref to the element. If you define a custom React Component and try to use it within a controller or with register, funky things can happen. I found using React.forwardRef() solves the problem when using Custom Components.
CustomSwitchComponent.tsx
import React from 'react';
import {FormControlLabel, Switch, SxProps, Theme} from "#mui/material";
const TradingStrategyEditFormInstructionsInputSwitch:
// Note this type allows us to do React.forwardRef(Component)
React.ForwardRefRenderFunction<HTMLButtonElement, {
label: string,
checked: boolean,
onBlur: React.FocusEventHandler<HTMLButtonElement>
}> = ({label, ...rest}) => {
// Spreading ...rest here will pass the ref :)
return <FormControlLabel control={<Switch {...rest} />}
labelPlacement={"top"}
label={label}
/>;
};
// *Huzzah!*
export default React.forwardRef(TradingStrategyEditFormInstructionsInputSwitch);
CustomSwitchController.tsx
<Controller
control={formCtx.control}
name={fieldPath}
key={type + side + key}
render={({field}) => {
return <TradingStrategyEditFormInstructionsInputField
{...field}
label={key}
checked={field.value}
onBlur={() => {
field.onBlur()
handleOnBlur(key)
}}
/>
}}/>
Idk if we're allowed to link YT Videos, but techsith has a great video on using forwardRef with useRef that should clear things up.
https://www.youtube.com/watch?v=ScT4ElKd6eo
Your input component does not export ref as props since it is a functional component.
React.useEffect(() => {
register('name', { required: true });
}, []);
<InputFloatLabel
type="text"
placeholder="Name"
name="name"
// Remove the register from here
/>

Edit/Update input field with React

I'm new to react and I'm trying to edit an input field after I prefilled its value with an object value from my database, so what should I put on onChange if value is value={data.info}? because I cannot type or change the initial value. I've watched a lot of tutorials but this. and props are very confusing to me
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import useAsync from '../useAsync';
export default function Details() {
const url = 'https://..';
const { index } = useParams();
const { data } = useAsync(url + index);
const [state, setState] = useState(false);
const showForm = () => {
return (
<div>
<form>
<input type="text" value={data.info} onChange={} />
</form>
</div>
)
}
return (
<div className="details" >
{data && <p key={index}>{data.info}</p>}
<button onClick={() => setState({ showForm: true })}>Edit</button>
{state.showForm ? showForm() : null}
</div>
)
}
You can add "default value" to your state. So you can move the data value to your useState(false) so useState(data)
import React, { useState } from "react";
const App = () => {
const [formInput, setFormInput] = useState(""); // You can add your "data" as default value
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>formInput Value {formInput}</h2>
<input
type="text"
value={formInput}
onChange={(e) => setFormInput(e.target.value)} // You need to set the state with the onchange value from the input
/>
</div>
);
};
export default App;
Link to CodeSandbox

reset React select when options change

How to reset a react-select when the options is changed, this happen because im using chaining select, so my second select options will change based on my first select, what im trying to do is reset back the select to "please select" when my second option already picked before, im using react-select with react-hook-form
import React, { useState, useEffect } from 'react';
import { default as ReactSelect } from 'react-select';
import { FormGroup, Label } from 'reactstrap';
import { useFormContext, Controller } from 'react-hook-form';
import { ErrorMessage } from '#hookform/error-message';
export default function Select(props) {
const {
label,
isMulti,
note,
// isDisabled,
// withDefaultValue,
options,
isClearable,
name,
placeholder = 'Pilihan'
} = props;
const rhfContext = useFormContext(); // retrieve all hook methods
const { control, errors } = rhfContext || {};
const [elOptions, setElOptions] = useState([]);
useEffect(() => {
setElOptions(options);
}, [options]);
return (
<FormGroup>
{label && <Label htmlFor={name || ''}>{label}</Label>}
<Controller
as={ReactSelect}
name={name}
control={control}
options={elOptions}
placeholder={placeholder}
styles={customStyles}
{...(isMulti ? { isMulti: true } : {})}
{...(isClearable ? { isClearable: true } : {})}
classNamePrefix="react-select-pw"
className="react-select-container"
/>
{note && <span>{note}</span>}
<ErrorMessage
name={name}
errors={errors}
render={() => {
return <p className="err-msg">pilih salah satu</p>;
}}
/>
</FormGroup>
);
}
Basically you need to handle the onChange of your react-select
const funcComponent = () => {
const [firstOptions, setFirstOptions] = useState({});
const [secondOptions, setSecondOptions] = useState({});
useEffect(() => {
//Here dispatch your defined actions to load first select options
setFirstOptions(response-data)
})
const handleFirstOptions = selectedVal => {
//Here dispatch your defined action to load second select options
setSecondOptions(response-data)
}
const handleSecondOptions = selectedVal => {
//Your action to perform
}
return (
<Label>First Option Field</Label>
<Select
options={firstOptions}
onChange={handleFirstOptions}
/>
Label>Second Option Field</Label>
<Select
options={secondOptions}
onChange={handleSecondOptions}
/>
)}

useEffect with local variable

I am trying to call useEffect funtion onchange of local variable, but its not working is only works if i use it with useState variable, I know there might be some basic thing here that I am not aware of.
sandbox link: https://codesandbox.io/s/affectionate-gareth-igyv7?file=/src/demo.js
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function Demo() {
const [value, setValue] = useState("");
let valueOne, valueTwo;
const setValueOne = (value) => {
valueOne = value;
};
useEffect(() => {
console.log(value);
console.log(valueOne);
}, [value, valueOne]);
return (
<div>
<h1>Demo</h1>
<input
placeholder="useState"
onChange={(e) => setValue(e.target.value)}
/>
<input
placeholder="function"
onChange={(e) => setValueOne(e.target.value)}
/>
{/* {console.log(valueOne)} */}
</div>
);
}
setValueOne will not rerender your component, If you want to fire a re-render, useEffect function needs to have a useState which basically hold state between re-renders.
You can try managing your state like below, its more readable and it will work too.
import React, { useState } from "react";
import "./styles.css";
export default function Demo() {
const [valueOne, setValueOne] = useState("");
const [valueTwo, setValueTwo] = useState("");
const handleValueOne = (e) => {
setValueOne(e.target.value);
};
const handleValueTwo = (e) => {
setValueTwo(e.target.value);
};
return (
<div>
<h1>Demo</h1>
<input
value={valueOne}
placeholder="useState"
onChange={handleValueOne}
/>
<input
value={valueTwo}
placeholder="function"
onChange={handleValueTwo}
/>
{/* {console.log(valueOne)} */}
</div>
);
}

Resources