So I decided to give React Hooks a try.
I have a component which contains a bunch of logics like form validation and form submission as shown below:
const MyAwesomeComponent: React.FC = ()=> {
const [firstName, setFirstName]=useState('')
const [lastName, setLastName]=useState('')
const [formValidation, setFormValidation] = useState({
firstName: true,
lastName: true
})
const handleSubmitForm = (evt: React.FormEvent)=> {
evt.preventDefault();
handleSubmitButton()
}
const handleSubmitButton = ()=> {
let formBody = {}
if(!firstName) {
setFormValidation({...formValidation, firstName: false})
} else {
formBody = {...formBody, {firstName}}
}
if(!lastName) {
setFormValidation({...formValidation, lastName: false})
} else {
formBody = {...formBody, {lastName}}
}
// Nice proceed and submit the form to the backend
}
return (
<form onSubmit={(evt)=>handleSubmitForm(evt)}>
{/* form inputs go here */}
<button onClick={()=>handleSubmitButton()}></button>
</form>
)
}
export default MyAwesomeComponent
The code above feels a bit bloated and a bit difficult to maintain in my opinion. Is there a way to improve the handleSubmitButton function in order to abstract some of its code into a separate function out of the MyAwesomeComponent component?
For one thing, you could do
const handleSubmitButton = ()=> {
let formBody = {firstName, lastName}
setFormValidation({firstName: !!firstName, lastName: !!lastName});
// Nice proceed and submit the form to the backend
}
In other words, why do it in two separate steps? BTW if you haven't seen it, the !! is "not not" which converts a truthy or falsy value into an actual true or false.
Related
In a React app, I have a class component that acts as a form, and each different element of the form calls the same reusable function to set state in order to save data to state until the form is finished, then a callback to pass the data around elsewhere, and when that form is finished, it writes the data in state to a MongoDB. In calling the function, I pass a parameter into it that determines which property of state is set.
I'm trying to convert this component to a functional component, but can't come up with as elegant of a way to handle this using useState.
The function:
handleChange = (e, t, l) => {
if (l) {
var lang = this.state[l]
lang[t] = e.target.value
this.setState({[l] : lang}, () => { this.setData() })
} else {
this.setState({[t]:e.target.value}, () => { this.setData() })
}
}
And the implementation:
onChange={(e) => this.handleChange(e, 'eng')}
onChange={(e) => this.handleChange(e, 'span')}
onChange={(e) => this.handleChange(e, 'type')}
Those occur on multiple inputs and there are a few other places, those are just 3 to give example.
I'd guess the best way to handle this is to use a switch, and each case is setEng, setSpan, setType, but I wanted to see if there were any other ways to replicate this that people know of? This seems like a bit of a shortcoming of useState that setState handled really nicely.
Edit:
As requested, state as it was in class component:
this.state = {
id: this.props.count,
collapsed: false,
type: '',
answer: null,
english: {
title: '',
options: [],
},
spanish: {
title: '',
options: [],
}
}
And in functional component:
const [id, setId] = useState(props.count)
const [collapsed, setCollapsed] = useState(false)
const [type, setType] = useState('')
const [answer, setAnswer] = useState(null)
const [english, setEnglish] = useState({ title: '', options: [] })
const [spanish, setSpanish] = useState({ title: '', options: [] })
I am new to typescript and migrating a react frontend to learn. I am currently having trouble with my login/registration component - a ternary operator is assigned to an onClick handler which dictates whether the login or signup mutation are executed. I implemented a workaround where I wrapped the mutations in functions, but it looks dirty.
Here is a barebones example, which returns a type assignment error because the values in the ternary do not share attributes with the onClick handler. I can provide a more robust, reproduceable example if necessary.
const Auth = () => {
const history = useHistory();
const [formState, setFormState] = useState({
login: true,
firstName: '',
lastName: '',
username: '',
password: '',
email: ''
});
const [loginMutation] = useMutation(LOGIN_MUTATION, {
onCompleted: (response) => {
//...
}
});
const [signupMutation] = useMutation(SIGNUP_MUTATION, {
variables: {
//...
},
onCompleted: (response) => {
// ...
}
});
return (
<>
{!formState.login && (
display extra inputs for registration form
)}
username and password fields
<button onClick={formState.login ? login : signup}>
{formstate.login ? login: signup}
</button>
</>
)
}
As you correctly guessed onClick has signature (e:MouseEvent<HTMLButtonElement, MouseEvent>) => void while mutation has a different signature (something along the lines of (options?: MutationFunctionOptions<T>) => Promise<Result>). So, when you say, onClick={login} you are essentially doing this: onClick={e => login(e)} which is wrong.
One way to fix it is to write an inline function
<button onClick={() => formState.login ? login() : signup()}>
{formstate.login ? login: signup}
</button>
I'm trying to pass the table row object to Redux-Form to Edit the object values.
here some contents of ViewStudents.js file
handleEdit(e, student) {
e.preventDefault();
const { dispatch } = this.props;
dispatch(allActions.editStudents(this.state));
this.setState({
firstName: student.firstName,
lastName: student.lastName
});
this.props.history.push("/EditStudents");
}
<button onClick={e => this.handleEdit(e, student)}>Edit</button>
function mapStateToProps(state) {
const { students } = state.viewStudents;
return {
students
};
}
export default connect(mapStateToProps)(ViewStudents);
here some contents of EditStudents.js
constructor(student) {
super(student);
this.state = {
firstName: student.firstName,
lastName: student.lastName
};
}
handleSubmit(e) {
e.preventDefault();
const { dispatch } = this.props;
dispatch(allActions.editStudents(this.state));
this.setState({
firstName: "",
lastName: ""
});
this.props.history.push("/ViewStudents");
}
function mapStateToProps(state) {
const { student } = state.addStudents;
return {
initialValues: {
firstName: state.student.firstName,
lastName: state.student.lastName
}
};
}
export default reduxForm({
form: "EditForm",
validate,
mapStateToProps
})(EditStudents);
Problem is, this object values not passing to edit form, though I bind in mapStateToProps, initialValues and passed this object in constructor
how to bind this and pass properly the clicking object in a table row and edit/save that object
Few small issues I can see with this.
You shouldn't use state to select what item you wish to edit. Always think that a user will refresh the page at any item. Therefore you should use React Router Splat to pass a unique value/ID.
http://localhost:3000/EditStudents/1
To do this you need to add a unique id for each student when adding and then use the ID with the route.
<Route path="/EditStudents/:studentId" component={EditStudents} />
You can then read the ID and load on componentDidMount
componentDidMount() {
const { dispatch } = this.props;
var {studentId } = props.match.params;
dispatch( { type: "LOAD_STUDENT", studentId });
}
The use of refreshing the state (request, success) has the effect of clearing the data. Therefore any initial state is lost.
Note, you are also using a Service to load in data.
Really you should be using an async thunk to load data into redux.
You should only get data from and to redux and then use middleware to persist the data.
https://github.com/reduxjs/redux-thunk
https://codesandbox.io/s/MQnD536Km
I am a newbie in react-native. I have a folder structure like below:
-screens
-page1.js
-page2.js
-page3.js
-page4.js
-App.js
In page1.js, I have a function to store data to localStorage
let obj = {
name: 'John Doe',
email: 'test#email.com',
city: 'Singapore'
}
AsyncStorage.setItem('user', JSON.stringify(obj));
Now I have to display these data in few of my other pages. This is my code.
class Page2 extends Component {
state = {
username: false
};
async componentDidMount() {
const usernameGet = await AsyncStorage.getItem('user');
let parsed = JSON.parse(usernameGet);
if (parsed) {
this.setState({
username: parsed.name,
email: parsed.email
});
} else {
this.setState({
username: false,
email: false
});
}
}
render() {
return (
<View style={styles.container}>
<Text style={styles.saved}>
{this.state.username}
</Text>
</View>
);
}
}
export default Page2;
This is how I display data in page2. I may need to show these in other page too.
I dont want to repeat these codes in each page.
Any suggestions how to do it in react-native?
You can extract the data you need to display into it's own component and re-use it in any page that you need to display it in.
Another option is to use a higher-order component, that way you can wrap it around any components that need the data and it'll be passed down as a prop.
You can make your Constant.js where you can put all your common required utils and constants, reusable anywhere n your app.
In your Constant.js:
export const USER_DATA = {
set: ({ user}) => {
localStorage.setItem('user', JSON.stringify(obj));
},
remove: () => {
localStorage.removeItem('user');
localStorage.removeItem('refresh_token');
},
get: () => ({
user: localStorage.getItem('user'),
}),
}
in your any component, you can import it and use like this :
import { USER_DATA } from './Constants';
let user = {
name: 'John Doe',
email: 'test#email.com',
city: 'Singapore'
}
// set LocalStorage
USER_DATA.set(user);
// get LocalStorage
USER_DATA.get().user
That's you can make Constant common file and reuse them anywhere to avoid writing redundant code.
Simplified Reusable approach of localStorage
export const localData = {
add(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
remove(key, value) {
localStorage.removeItem(key);
},
load(key) {
const stored = localStorage.getItem(key);
return stored == null ? undefined : JSON.parse(stored);
},
};
localData.add("user_name", "serialCoder")
console.log( "After set 👉", localData.load("user_name") )
localData.remove("user_name")
console.log( "After remove 👉", localData.load("user_name") )
I'm wrapping my forms to provide automatic validation (I don't want to use redux-form).
I want to pass an onSubmit handler which must be fired after every input in form is validated: but how do I wait for form.valid property to turn into true && after wrapping submit was fired? I'm missing some logic here!
//in my Form.js hoc wrapping the forms
#autobind
submit(event) {
event.preventDefault();
this.props.dispatch(syncValidateForm({ formName: this.props.formName, form: this.props.form }));
// ==> what should I do here? Here I know submit button was pressed but state is not updated yet with last dispatch result reduced!
//if(this.props.form.valid)
// this.props.submit();
}
render() {
return (
<form name={this.props.formName} onSubmit={this.submit}>
{ this.props.children }
</form>
);
//action.js validating given input
export const syncValidateInput = ({ formName, input, name, value }) => {
let errors = {<computed object with errors>};
return { type: INPUT_ERRORS, formName, input, name, value: errors };
};
//action.js validating every input in the form
export const syncValidateForm = ({ formName, form }) => {
return dispatch => {
for(let name in form.inputs) {
let input = form.inputs[name];
dispatch(syncValidateInput({ formName, input, name: input.name, value: input.value }));
}
};
};
//in my reducer I have
case INPUT_ERRORS:
let errors = value;
let valid = true;
let errorText = '';
_.each(errors, (value, key) => {
if(value) {
valid = false;
errorText = `${errorText}${key}\n`;
}
});
form.inputs[name].errors = errors;
form.inputs[name].valid = valid;
form.inputs[name].errorText = errorText;
_.each(form.inputs, (input) => form.valid = !!(form.valid && input.valid));
return state;
Help!
Depending on your build config you could use Async/Await for your submit function. Something like
async submit(event) {
event.preventDefault();
const actionResponse = await this.props.dispatch(syncValidateForm({ formName: this.props.formName, form: this.props.form }));
if (actionResponse && this.props.form.valid) { //for example
// finish submission
}
}
And I think you will need to update your syncValidateForm slightly but this should put you on the right path.