Link to original question: How to setState of computed property using hooks
I'm basically trying to update an old project to use hooks and functional components where possible. I need to update the state of a computed property using hooks in the handleChange function. How would I do this?
I've removed the irrelevant code in the following snippet:
import React, { useState } from 'react'
import AuthenticationService from '../service/AuthenticationService';
export const LoginComponent1 = () => {
const [userName, setUserName] = useState('Neil');
const [password, setPassword] = useState('');
const [loginStatus, setLoginStatus] = useState(false);
const handleChange = (event) => {
this.setState(
{
[event.target.name]: event.target.value
}
);
}
const loginClicked = () => {
...
}
const enterPressed = (event) => {
...
}
return (
<div className="container">
<h1>Login</h1>
<div className="container">
User Name: <input className= "userNameBox" type="text" placeholder="Neil" name="username" value={userName} onChange={handleChange} onKeyPress={enterPressed}></input>
Password: <input className= "userNameBox" type="password" id="myInput" name="password" value={password} onChange={handleChange} onKeyPress={enterPressed}></input>
<hr></hr>
</div>
</div>
)
}
export default LoginComponent1
Thanks!
While declaring a state in functional component the second parameter you are passing is a method used to set the state. this.setState is available only in class components. See here
In your example for setting the state password, you need to call the method setPassword with value.
Eg: setPassword('this is my password')
this.setState exists only in class components, not functional ones.
For updating state with useState hooks, simply call the corresponding setter. In your example, call setUserName to update the username, setPassword to update the password and so on.
if you want to create a generic handler like before you had at react based component with multiple state handlers you could create an object handler after your useState hooks and refactor your handleChange like:
const handler = {
username: setUserName,
password: setPassword
}
const handleChange = (event) => handler[event.target.name](event.target.value)
Related
I have built a login page where on filling the username and password and hitting the submit button, it dispatches the action which basically goes to the backend and checks whether the username and password is valid. I am using React and redux and this is my login page below
login.js (simplified)
import LoginHandle from '../login/LoginHandle'
function userLogin(event) {
event.preventDefault()
const username = document.getElementById("username").value
const password = document.getElementById("password").value
LoginHandle(username, password)
}
const Login = () => {
return (
<div className="flex-row align-items-center">
<form onSubmit={(e) => userLogin(e)}>
<h1>Login</h1>
<input id="username" type="email" placeholder="Username" required/>
<input id="password" type="password" placeholder="Password" required/>
<button type="submit" className="px-4">Login</button>
</form>
</div>
)
}
export default Login
LoginHandle.js (simplified)
import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'
const LoginHandle = (username, password) => {
const dispatch = useDispatch()
dispatch(getLoginStatus(username, password))
const loginState = useSelector(state=> state.loginStatus)
if(loginState.loading) {
console.log("please wait")
}
// .... rest of the logic
}
export default LoginHandle
As you can see I try to dispatch the action and then check the state to get confirmation from the backend. But I get this error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:...
What am I doing wrong?
import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'
const useLoginHandler = (username, password, submitted) => {
const dispatch = useDispatch();
const loginState = useSelector(state=> state.loginStatus)
// it is better to put the dispatch inside a useEffect rather than outside
useEffect(() => {
if(!submitted) {return};
dispatch(getLoginStatus(username, password))
}, [username, password, submitted]);
if(loginState.loading) {
console.log("please wait")
}
// .... rest of the logic
}
export default useLoginHandler
And your usage of the hook is wrong. it should not be inside a callback function.
it should be at the top level. Something like below. Also you should not access dom elements directly, instead use useRef or useState to get value
const Login = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('')
const [submitted, setSubmitted] = useState(false);
useLoginHandler(username, password, submitted); //custom hook should be used at this level
return (
<form onSubmit={(e) => setSubmitted(true)}>
<input id="username" type="email" placeholder="Username" required onChange={e => setUsername(e.target.value)}/>
</form>
)
}
You are using 2 hooks in this implementation useDispatch & useSelector and calling them inside a function, which is invalid usage. Hooks can only be called inside a functional component. So changing your above implementation as below should work:
Login.js
import {useDispatch, useSelector} from 'react-redux'
import {getLoginStatus} from '../actions/loginAction'
const Login = () => {
const dispatch = useDispatch()
const loginState = useSelector(state=> state.loginStatus)
function userLogin(event) {
event.preventDefault()
const username = document.getElementById("username").value
const password = document.getElementById("password").value
dispatch(getLoginStatus(username, password))
}
return (
<div className="flex-row align-items-center">
<form onSubmit={(e) => userLogin(e)}>
<h1>Login</h1>
<input id="username" type="email" placeholder="Username" required/>
<input id="password" type="password" placeholder="Password" required/>
<button type="submit" className="px-4">{loginState.loading ? "Please wait..." : "Login"}</button>
</form>
</div>
)
}
export default Login
Things to note:
Called useDispatch directly in functional component and not inside a function of functional component. Then we can use the result of useDispatch inside a function
Called useSelector inside functional component and then used the output to show loading in place of text in the button
i found this quite interesting pattern where you enable function based on ternary approach applied on the use state itself but i got this problem where it says
React Hook "useState" is called conditionally. React Hooks must be
called in the exact same order in every component render
import React, { useState } from "react";
const App = () => {
const [enableFirstName, setEnableFirstName] = useState(false);
const [name, setName] = enableFirstName ? useState("") : ["", () => {}]; #Error is here
const [lastName, setLastName] = useState("");
const handleChangeName = (e) => {
setName(e.target.value);
};
const handleChangeLastName = (e) => {
setLastName(e.target.value);
};
const handleEnableChange = (evt) => {
setEnableFirstName(!enableFirstName);
};
return (
<div>
<h1>My name is: {enableFirstName ? name : ''} {lastName}</h1>
<input type="checkbox" value={enableFirstName} onChange={handleEnableChange} />
<input type="text" value={name} onChange={handleChangeName} />
<input type="text" value={lastName} onChange={handleChangeLastName} />
</div>
);
};
export default App;
React doesn't allow you to call hooks conditionally.
Remove the condition in the second useState:
const [enableFirstName, setEnableFirstName] = useState(false);
const [name, setName] = useState("");
const [lastName, setLastName] = useState("");
Even if you don't need the name because enableFirstName is false - simply ignore it, leave empty string and handle conditional logic in the component methods if necessary.
How do you pass / use refs inside a handler function inside a functional component?
import React, { useRef } from 'react';
function RegisterUser() {
const emailInput = useRef(null);
const passwordInput = useRef(null);
const handleClickRegister = () => {
RegisterPersonMutation(email, password, callbackValue => {
emailInput.current.value = ''; // <---------------------this guy
passwordInput.current.value = ''; // <------------------and his friend
});
};
return (
<div className="register-wrap">
<form>
<input type="text" ref={emailInput} />
<input type="password" ref={passwordInput} />
</form>
<button onClick={() => handleClickRegister()}>Register</button>
</div>
);
}
export default RegisterUser;
I would still like to know how to create and use refs inside nested functions using useRef... but in this case, Frank was right in that I should just use useState.
I'm already using it in my real component so it's like this:
function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
handleClick(foo, bar, callback => {
setEmail('');
setPassword('');
});
<input value={email} />
<input value={password} />
}
I'm trying to set up a simple react-redux flow where an input updates state and a form submits the value in the component's state to a redux action function. However, whenever the form submits, the page reloads and when I add e.preventDefault() to the submit function, I get
TypeError: e.preventDefault is not a function
I've tried adding e.preventDefault() to the submitToRedux function but when I add do, I get TypeError: e.preventDefault is not a function
Here is my Child1.js:
import React, { useState } from "react";
import { changeName } from "../redux/name/name.actions";
import { connect } from "react-redux";
function Child1(state) {
const [name, setName] = useState("");
const changeHandler = e => {
e.preventDefault();
setName(e.target.value);
};
const submitToRedux = e => {
// e.preventDefault();
changeName(name);
};
return (
<div>
<h2>CHILD ONE</h2>
<form onSubmit={submitToRedux(name)}>
<input type="text" onChange={changeHandler} />
<button type="submit">SUBMIT</button>
<h2>name in Child1 state: {name}</h2>
<h2>name in redux: {state.name.name}</h2>
</form>
</div>
);
}
const mapStateToProps = state => ({
name: state.name
});
export default connect(mapStateToProps)(Child1);
App.js:
import React from "react";
import Child1 from "./components/Child1";
function App() {
return (
<div className="App">
<Child1 />
</div>
);
}
export default App;
root-reducer.js:
import { combineReducers } from "redux";
import nameReducer from "./name/nameReducer";
export default combineReducers({
name: nameReducer
});
and nameReducer.js:
import NameActionTypes from "./name.types";
const INITIAL_STATE = {
name: "Mike"
};
const nameReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case NameActionTypes.CHANGE_NAME:
return {
...state,
name: action.payload
};
default:
return state;
}
};
export default nameReducer;
I expect it to update the state.name.name value in Child1.js to whatever is submitted from the state in the Child1.js component when the form is submitted but instead it just reloads the page and since I'm not persisting it to local storage it just remains blank. When I add e.preventDefault() I expect it to stop reloading the page when the form submits but it then says that
e.preventDefault is not a function
It's because you are not passing the submit event to submitToRedux function.
You should pass it to your function like this:
<form onSubmit={(e) => submitToRedux(e, name)}>
and then you handle it in you function like this:
const submitToRedux = (e, name) => {
e.preventDefault();
changeName(name);
};
Here is how child1.js will be:
import React, { useState } from "react";
import { changeName } from "../redux/name/name.actions";
import { connect } from "react-redux";
function Child1(state) {
const [name, setName] = useState("");
const changeHandler = e => {
e.preventDefault();
setName(e.target.value);
};
const submitToRedux = (e, name) => {
e.preventDefault();
changeName(name);
};
return (
<div>
<h2>CHILD ONE</h2>
<form onSubmit={(e) => submitToRedux(e, name)}>
<input type="text" onChange={changeHandler} />
<button type="submit">SUBMIT</button>
<h2>name in Child1 state: {name}</h2>
<h2>name in redux: {state.name.name}</h2>
</form>
</div>
);
}
const mapStateToProps = state => ({
name: state.name
});
export default connect(mapStateToProps)(Child1);
Multiple issue's with your code,
First, you are writing state as argument to Child1 component
function Child1(state) {
which should be,
function Child1(props) {
You should set this props to your state,
const [name, setName] = useState(props.name);
Your input should be controlled,
<input type="text" onChange={changeHandler} value={name} />
You should print name like this,
<h2>name in Child1 state: {name}</h2>
<h2>name in redux: {props.name}</h2>
Your form submit method should be like this,
<form onSubmit={submitToRedux}>
And finally your submitToRedux function,
const submitToRedux = e => {
e.preventDefault(); //Now this will work
changeName(name); //As we have controlled input, we direclty take name from state
};
You just need to pass the function that will get called once the form is submitted.
<form onSubmit={submitToRedux}>
But instead you are actually calling it right away:
<form onSubmit={submitToRedux(name)}>
When you just pass the function, the form will take care of calling it with a submit event as parameter.
In your code the error says the parameter e should contain a function preventDefault, which clearly is not defined in the variable you are passing in as parameter when you do: submitToRedux(name)
I believe it is because it binds the value back to the property inputText but just want to make sure I'm stating this correctly.
import React, { useState } from "react";
const InputElement = () => {
const [inputText, setInputText] = useState("");
return (
<div>
<input
placeholder="Enter Some Text"
onChange={e => {
setInputText(e.target.value);
}}
/>
</div>
);
};
export default InputElement;
This is a good example of 2 way data binding because when you update the state, the UI changes, and when the UI changes, the state changes. Just need to remind you to set the value prop on the <input> element to inputText so that it's a controlled component.
import React, { useState } from "react";
const InputElement = () => {
const [inputText, setInputText] = useState("");
return (
<div>
<input
placeholder="Enter Some Text"
onChange={e => {
setInputText(e.target.value);
}}
value={inputText}
/>
</div>
);
};
export default InputElement;