I want to set a Error Message under my Input Fields, if the value isn't valid. The message will always be the same. But the message is shown on every Input field, if the value isn't valid. That's logic if you see the code. But how can I make it work?
And yes, I have to use the error state in the Parent component because I have lifted the state up. Because of style dependencies of the error prop. I'm using styled-components for that.
Parent component:
const List = (props) => {
const [error, setError] = useState('');
function handleChange(event) {
const currentValue = parseInt(event.target.value, 10);
if (currentValue > 10) {
setError('Error');
} else setError(null);
}
return (
{items.map(item => (
<MyInput
value={item.value}
key={item.input}
error={error}
handleChange={e => handleChange(e)}
/>
))}
);
};
export default List;
The child Component:
const MyInput = (props) => {
return (
<>
<input
defaultValue={props.value}
type="text"
placeholder={props.value}
onChange={e => props.handleChange(e)}
/>
{props.error ? {props.error} : null}
</>
);
};
export default MyInput;
Is it possible to use the state in the parent component?
I hope you can help me :)
Related
I have the code below.
How can I reset the stocksHelper, instatiating again when the component render on stocks's useState change?
I need this class to instantiate again to reset the variables inside the class instance, because when the stocks change a calculation needs to be done to render the stocks again. And if I get the instance of the last render with the old values this calculation will bug my entire aplication
export default function Heatmap() {
const [stocks, setStocks] = useState<IStock[]>([]);
const stocksHelper: StocksHelper = new StocksHelper(stocks);
return (
<main className={styles.main}>
<RegisterForm stocks={stocks} setStocks={setStocks} />
</main>
);
}
RegisterForm component below:
export default function RegisterForm(props: Props) {
const { stocks, setStocks } = props;
const [name, setName] = useState<string>('');
const [value, setValue] = useState<number>(0);
const [volume, setVolume] = useState<number>(0);
function storeStock(): void {
axios.post('any url', {
name: name,
value: value,
volume: volume
})
.then((res) => {
setStocks([...stocks, res.data]);
})
.catch((res) => console.log(res));
}
return (
<form className={styles.form} onSubmit={() => storeStock()}>
<fieldset>
<legend className={styles.title}>Cadastro</legend>
<input type="text" onChange={e => setName(e.target.value)} placeholder="Nome" />
<input type="number" onChange={e => setValue(parseFloat(e.target.value))} placeholder="Porcentagem" />
<input type="number" onChange={e => setVolume(parseInt(e.target.value))} placeholder="Volume" />
<button type='submit'>Cadastrar</button>
</fieldset>
</form>
);
}
#AmitMaraj's answer is perfectly fine but for a shorter and more concise method you should use useMemo:
const stocksHelper = useMemo(() => new StocksHelper(stocks), [stocks]);
Now a new StocksHelper will only be created when stocks changes.
Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
Link to documentation
If I'm understanding correctly, you might be able to achieve this with useEffect! See below for an example:
export default function Heatmap() {
const [stocks, setStocks] = useState<IStock[]>([]);
const [stocksHelper, setStockHelper] = useState<StocksHelper>(new StocksHelper(stocks));
useEffect(() => {
setStockHelper(new StocksHelper(stocks))
}, [stocks])
return (
<main className={styles.main}>
<RegisterForm stocks={stocks} setStocks={setStocks} />
</main>
);
}
I have a webpage with multiple forms. Here's a bare minimum example of the structure:
export default function Example() {
const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
{}
);
const [formA, setFormA] = useState({});
const [formB, setFormB] = useState({});
const router = useRouter();
useEffect(() => {
fetchFormFromWeb(router.query.id).then((previousForm) => {
console.log("fetched info from web");
setPreviousFormFetchedFromWeb(previousForm);
setFormA(previousForm.formA);
setFormB(previousForm.formB);
});
},[router.isReady]);
return (
<>
<FormA form={formA} />
<FormB form={formB} />
</>
);
}
function FormA({ form }) {
return (
<input
type="text"
name="field1"
id="field1"
value={formA.field1}
onChange={(e) => {
setFormA(e.target.value);
}}
/>
);
}
function FormB({ form }) {
return (
<input
type="text"
name="field2"
id="field2"
value={formB.field2}
onChange={(e) => {
setFormB(e.target.value);
}}
/>
);
}
I think this should be the encapsulated logic of my form page. The problem is that when the onChange event is called for a field of any form, all forms get re-rendered. I assumed that setState should re-render only the components with the affected dependency change. Am I missing something?
Any state change in Example component will trigger re render to its child components (FormA, FormB). If you want to avoid that. wrap FormA and FormB in React.memo. That way you can prevent unwanted re-renders
export default function Example() {
const [previousFormFetchedFromWeb, setPreviousFormFetchedFromWeb] = useState(
{}
);
const [formA, setFormA] = useState({});
const [formB, setFormB] = useState({});
const router = useRouter();
useEffect(() => {
fetchFormFromWeb(router.query.id).then((previousForm) => {
console.log("fetched info from web");
setPreviousFormFetchedFromWeb(previousForm);
setFormA(previousForm.formA);
setFormB(previousForm.formB);
});
},[router.isReady]);
return (
<>
<MemFormA form={formA} />
<MemFormB form={formB} />
</>
);
}
const MemFormA = React.memo(function FormA({ form }) {
return (
<input
type="text"
name="field1"
id="field1"
value={formA.field1}
onChange={(e) => {
setFormA(e.target.value);
}}
/>
);
})
const MemFormB = React.memo(function FormB({ form }) {
return (
<input
type="text"
name="field2"
id="field2"
value={formB.field2}
onChange={(e) => {
setFormB(e.target.value);
}}
/>
);
})
Anytime state of <Example /> is updated, <Example /> re-renders, which in turn also re-renders <FormA /> and <FormB />. This is expected.
You should look into using React.memo() for FormA and FormB if you want them to only re-render when the props passed to them is changed.
I want to create an Input component to be used to compose new form elements.
const Input = ({ value, children }) => {
const [currentValue, setCurrentValue] = useState();
return <div className='input'>
{children}
</div>
};
And my Text component would be:
const Text = (props) => {
return <Input {...props}>
<input
type='text'
value={/*how to bind to currentValue of Input*/}
onChange={/*how to call Input's setCurrentValue here*/}
/>
</Input>
}
I need to store currentValue state in parent, because I need to manage it for many different inputs.
Also I'm stuck at how to call parent's setCurrentValue on child's onChange method.
Any help is appreciated.
Update:
CodeSandbox
Update 2:
Real code from my repository
Solutions:
Context API
Pass props to children
Use children as funciton & pass relevant
Send that reference somehow using any method you see fit.
My preference: Composition with function
const Input = ({ value, children }) => {
const [currentValue, setCurrentValue] = useState();
const handlChange = (e) => {
setCurrentValue(e.target.value);
};
return <div className='input'>
{children(handlChange)}
</div>
};
const Text = (props) => {
return <Input {...props}>
{ (handleChange) => (
<input
type='text'
onChange = ( handleChange }
/>
) }
</Input>
}
Explanations:
How to pass props to {this.props.children}
https://victorofoegbu.com/notes/pass-props-to-react-children-faq
Please try like this.
// pass props to children.
const Input = ({ value, children }) => {
const [currentValue, setCurrentValue] = useState();
return <div className='input'>
{React.cloneElement(child, {onChange: setCurrentValue, value: currentValue}))}
</div>
};
// using props in childern.
const Text = (props) => {
return <Input {...props}>
{
({onChange, value})=> (
<input
type='text'
value={value}
onChange={(e)=>onChange(e.target.value)}
/>
)
}
</Input>
}
How to keep input focus when changing state inside onFocus event
seems like it's not happening with simple input
only when using customInput
const CustomInput = ({ onFocus, state }) => {
return (
<div>
<input onFocus={onFocus} />
</div>
);
};
const TestInput = () => {
const [state, setState] = React.useState(false);
return (
<FilledInput
inputComponent={(props) => {
return (
<CustomInput onFocus={props.onFocus} state={state} />
);
}}
onFocus={() => {
setState(true);
}}
/>
);
};
I'm trying to implement MVVM in React (requirement from the class I'm taking). I'm using functional components for the view and have typescript classes for the ViewModel. My components do not re-render when a property is updated in the ViewModel though.
Here's a simple example for a page that should toggle between a login and sign up form. The setCurrentForm gets called correctly and the value in the ViewModel does update, but it doesn't change the View.
// AuthView.tsx
const AuthView: React.FC = () => {
const VM = new AuthViewModel();
let form;
if (VM.currentForm === FORMS.SignUp) {
// Toggles the current form between FORMS.SignUp and FORMS.Login
form = <SignUpForm setCurrentForm={() => VM.setCurrentForm()} />
} else {
form = <LoginForm setCurrentForm={() => VM.setCurrentForm()} />
}
return (
<Container>
{/* Sign up card */}
<div className="mt-12">
{form}
</div>
</Container>
);
}
export default AuthView;
// AuthViewModel.tsx
export default class AuthViewModel {
email: string = "";
password: string = "";
currentForm: FORMS = FORMS.SignUp;
setCurrentForm() {
console.log("Setting form in VM");
if (this.currentForm === FORMS.SignUp) {
console.log("Switching to login")
this.currentForm = FORMS.Login;
} else if (this.currentForm === FORMS.Login) {
console.log("Switching to signup")
this.currentForm = FORMS.SignUp;
}
}
}
I could force the re-render with a hook by updating an arbitrary value, but I think there's a better way to do this. What are your thoughts?
You might be missunderstanding how react components re-render, just because you change some property in another object it has no bearing on the component itself, even if it has taken a property from this object.
Hooks are directly connected to the reacts render mechanism and can trigger render cycles, as such you should use something like this:
const AuthView: React.FC = () => {
// if you don't put this in a state a new VM will be created when the component rerenders
const [VM] = useState(new AuthViewModel());
useEffect(() => {
// Maybe some handler code is needed?
}, VM.currentForm);
let form;
if (VM.currentForm === FORMS.SignUp) {
// Toggles the current form between FORMS.SignUp and FORMS.Login
form = <SignUpForm setCurrentForm={() => VM.setCurrentForm()} />
} else {
form = <LoginForm setCurrentForm={() => VM.setCurrentForm()} />
}
return (
<Container>
{/* Sign up card */}
<div className="mt-12">
{form}
</div>
</Container>
);
}
export default AuthView;
I've never tried to observe a nested property via a hook, so not 100% this works.
EDIT: it doesn't work, but it makes sense, the rendering call gets triggered when you actually call the set function of the useState hook, not really sure how to implement this pattern with hooks and without something like redux or mobx, but here is my best approach:
class AuthViewModel() {
constructor(public readonly currentForm = 'LOGIN');
public setCurrentForm = () => {
if(this.currentForm === 'LOGIN')
return new AuthViewModel('SIGNUP')
else
return new AuthViewModel(); // will default to login
}
}
and then the component
const AuthView: React.FC = () => {
// if you don't put this in a state a new VM will be created when the component rerenders
const [VM, setVM] = useState(new AuthViewModel());
let form;
if (VM.currentForm === FORMS.SignUp) {
// Toggles the current form between FORMS.SignUp and FORMS.Login
form = <SignUpForm setCurrentForm={() => setVM(VM.setCurrentForm())} />
} else {
form = <LoginForm setCurrentForm={() => setVM(VM.setCurrentForm())} />
}
return (
<Container>
{/* Sign up card */}
<div className="mt-12">
{form}
</div>
</Container>
);
}
export default AuthView;
What you have here doesn't feel very React. For starters, I've only rarely seen classes used outside of class-based components. I'm just going to spitball a different solution here that might not exactly match what you need, but hopefully gets you going in a correct direction.
const Authenticate: FC = props => {
const [mode, setMode] = useState<"login" | "create">("login");
return (
<div>
{mode === "login" && <Login onLogin={({email, password}) => {/*login handler logic*/}}/>}
{mode === "create" && <CreateAccount onCreate={({email, password}) => {/*create handler logic*/}}/>}
<button
disabled={mode === "login"}
onClick={() => setMode("login")}
>
login
</button>
<button
disabled={mode === "create"}
onClick={() => setMode("create")}
>
sign up
</button>
</div>
)
}
const Login: FC<{onLogin: ({email: string, password: string}) => any}> = props => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { onLogin } = props;
return (
<form onSubmit={() => onLogin({email, password})}>
<input value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
<button type="submit">Login</button>
</form>
);
}
const CreateAccount: FC<{onCreate: ({email: string, password: string}) => any}> = props => {
return (
<div>... similar to <Login/> ... </div>
)
}