I came across the following two form designing approaches in react-hook-form documentation.
Connect Form ref
When we are building forms, there are times when our input lives inside of deeply nested component trees, and that's when FormContext comes in handy. However, we can further improve the Developer Experience by creating a ConnectForm component and leveraging React's renderProps. The benefit is you can connect your input with React Hook Form much easier.
export const ConnectForm = ({ children }) => {
const methods = useFormContext();
return children({ ...methods });
};
export const DeepNest = () => (
<ConnectForm>
{({ register }) => <input {...register("deepNestedInput")} />}
</ConnectForm>
);
export const App = () => {
const methods = useForm();
return (
<FormProvider {...methods} >
<form>
<DeepNest />
</form>
</FormProvider>
);
}
Somehow, I am not able to get how this "improves developer experience"? Without ConnectForm, we would have required only two lines which are inside ConnectForm in above example. But with ConnectForm, we are requiring extra lines to define ConnectForm and two extra lines for <ConnectForm>','</ConnectForm>. So the number of lines have increased.
I guess I am not getting the idea from the above example, and maybe for a more complex or large form, it might result in a lesser number of lines. Q1. Is it so?
Q2. Or is it that the doc wants to say "<ConnectForm>...</ConnectForm> looks more elegant" by "improving Developer Experience"?
Related
Background
So I have a simple example, a Form component (Parent) and multiple Input components (Children). Each individual Input component will have an useState hook to initialize and manage its value's state.
Issue
As with most forms, I would like to submit all of the data to a backend for processing. However, the issue is that I cannot retrieve the state of value from each Child Input component.
// app.jsx
import Form from "./Form";
export default function App() {
return <Form />;
}
// Form.jsx
import React from "react";
import Input from "./Input";
const handleSubmit = (e) => {
e.preventDefault();
console.log("Wait, how do I retreive values from Children Inputs?");
};
const Form = () => {
console.log("Form render");
return (
<form onSubmit={handleSubmit}>
Sample Form
<Input initial="username" name="user" />
<Input initial="email" name="email" />
<button type="submit">Submit</button>
</form>
);
};
export default Form;
// Input.jsx
import React from "react";
import useInputValue from "./useInputValue";
const Input = ({ name, initial }) => {
const inputState = useInputValue(initial);
console.log(`${name}'s value: ${inputState.value}`);
return <input {...inputState} />;
};
export default Input;
Plausible Solution
Of course, I can lift the Input states up to the Form component, perhaps in an obj name values. However, if I do that, every time I change the Inputs, the Form will re-render, along with all of the Inputs.
To me, that is an undesirable side-effect. As my Form component gets bigger, this will be more costly to re-render all inputs (inside the form) every time one input changes.
Because of that, I would like to stick with my decision of having each individual input manage its own state, that way if one input changes, not all other input will re-render along with the Parent component.
Question
If each of the Child components manages its own state, could the Parent component access the value of that state and do actions (like form submission)?
Update
Many answers and comments mentioned that this is premature optimization and the root of all known evil; which I agree with, but to clarify, I am asking this question with a simple example because I wanted to find a viable solution to my current and more complex project. My web app has a huge form (where all states are lifted to the form level), which is getting re-rendered at every change in its inputs. Which seemed unnecessary, since only one input is changed at a time.
Update #2
Here is a codesandbox example of the issue I am having. There are two forms, one with all states managed by individual Child input components, and the other has all states lifted up in the form level. Try entering some values and check the console for log messages. One can see that with all states lifted to the form level, every change will cause both inputs to be re-rendered.
I think yes, you can share state. Also there are 3 options:
I recommend you to use such library as Formik. It will help you in your case.
You can share state using useState() hook as props.
Use such tools as Redux Toolkit (if we are speaking about memoisation), useContext() and etc.
If the thing you want is getting final values from input, assign ref to each input and access using emailRef.current.value in the submit function.
import { useState, useRef, forwardRef } from 'React';
const Input = forwardRef((props, ref) => {
const [value, setValue] = useState('');
return <input ref={ref} value={value} onChange={(e) => {setValue(e.target.value)}} {...props} />;
});
const App = () => {
const emailRef = useRef(null);
const submit = () => {
const emailString = emailRef.current.value
};
return (
<>
<Input ref={emailRef} />
<button onClick={submit}>Submit</button>
</>
);
};
If the parent needs to know about the childs state, you can
move the state up. This is resolved by passing down a setState function that the child calls (the states data is available in the parent)
use a context https://reactjs.org/docs/context.html
use a state management library e.g. redux
In your simple case I'd go with 1)
Today I was practicing with React and found two ways to get input values when submitting form.
First:
Using hooks, for example:
...
const [name, setName] = useState('');
...
return <input onChange={(value) => setName(value)} value={name} />
Second:
Get by using event.target.children[i].value, for example:
const handleSubmit = (event: BaseSyntheticEvent) => {
event.preventDefault();
const formInputs = event.target.children;
for (const input in formInputs) {
console.log(formInputs[input].value);
}
};
The question is, which one should I use and why? Is it optional, or I don't know, it does have some performance impact.
Don't use vanilla DOM manipulation in React when you can avoid it, within reason. The first approach is far better in most situations.
Working with the DOM is slow. State and the virtual DOM help a lot.
In the React paradigm, the appearance and functionality of the application should ideally stem from state as much as possible. Non-stateful side-effects can make things difficult to reason about and hard to work with given how re-rendering works and should be avoided.
If the reason you're tempted to go with the second approach is that you have a whole lot of inputs and making a separate state for each is tedious, one option is to make the state be a single object (or Map) instead.
const MyInput = ({ name, state, onChange }) => (
<Input name={name} onChange={onChange} value={state[name]} />
);
const onChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value });
};
<MyInput name="name" state={state} onChange={onChange}/>
I have a simple dictionary app I'm making that returns search results. A search is queried on every onChange of the search field using a lokiJS in-memory database for the dictionary, so the results come very quick.
It is important for me to optimize the rendering of the results, so that the 50 or so filtered search results keep flowing as the user types and the queries happen on each key stroke.
Here's what I've been doing so far: (this works but is it the best/fastest way?)
Each (sync) query of the database returns an array of objects, which I then map with something like this:
queryDB(query) {
const results = queryLokiJsDB(query);
const resultsMapped = results.map((mpd) =>
<dl key={mpd["$loki"]} onClick={() => this.clickFunction(mpd.p, mpd.f)}>
<dt>{mpd.p} - {mpd.f}</dt> <dd>{mpd.e} <em>{mpd.c}</em></dd>
</dl>);
this.setState({ results: (
<div>
{resultsMapped}
</div>
)});
}
Then, once I have the results mapped like that, I add the mapped components to the state, and then the screen gets rendered with the new results.
render() {
return (
<div>
<SearchBarStuff />
<div className="results-container">
{this.state.results}
</div>
</div>
);
}
I made this when I was just learning React and I understand that people consider it really bad practice to store components in the state.
I was wondering what the best practice in terms of performance optimization would be (and code cleanness). Should I store the results array in state then render then map them into components in the render function? Would that cause any performance hits?
Another answer said that "A function in the render method will be created each render which is a slight performance hit."
And why is it so bad to store components in state? Is it just for code clarity? Keeping the state small? I appreciate your help and patience.
I'm not quite sure but I am thinking the same problem as #larz explained in comments. Also, as you mentioned the state will be clearer. So here what would I do if I were you.
First, set your state just with the results:
queryDB(query) {
const results = queryLokiJsDB(query);
this.setState({ results });
}
Then, map through the results but instead of creating your JSX immediately I would use a separate component. Pass it mpd (the element) and onClick function with its reference.
render() {
return (
<div>
<SearchBarStuff />
<div className="results-container">
{this.state.results.map( mpd => (
<Item key={mpd["$loki"]} mpd={mpd} onClick={this.clickFunction} />
) )}
</div>
</div>
);
}
Use the Item like that:
const Item = ( props ) => {
const { mpd, onClick } = props;
const handleClick = () => onClick( mpd.p, mpd.f );
return (
<dl onClick={handleClick}>
<dt>{mpd.p} - {mpd.f}</dt> <dd>{mpd.e} <em>{mpd.c}</em></dd>
</dl>
);
}
In this way, you are not using an arrow function in your onClick handler, so this function will not be recreated in every render since we use the reference.
I’ve been told that it’s a best practice to use classes only for containers and to use functions for components. So containers have state and components are dumb functions that only recieve and send props to and from the containers.
The problem I’m finding with this is that it leads to really bloated containers. Not only that but if a container includes many different components then the methods in these containers are a mixture of many different unrelated functionalities.
This goes against the idea of keeping everything very modular. For example if I have a “submit comment” component in my container I would expect the submitCommentHandler method to also be in the relevant component not mixed together in the Post container with a ton of other handlers for unrelated functionalities like being next to the ratePostHandler and the userLoginHandler.
I’m new to react so maybe I’m missing something but how to reconcile this “best practice” with all the other issues it presents?
There are a couple of misconceptions in your post, possibly stemming from misconceptions in whatever best practices article you are reading.
When the core idea of containers + components started surfacing many examples were not doing it correctly.
// DO NOT DO THIS
let FormComponent = ({ data, handleSubmit }) =>
<form onSubmit={handleSubmit}>
{...something with data...}
</form>
class FormContainer extends React.Component {
state = { data: [] }
submitForm = formData => {
api.post(formData).then(data => this.setState({ data }))
}
render() {
return (
<div>
<FormComponent
data={this.state.data}
handleSubmit={this.submitForm}
/>
</div>
)
}
}
This is a pretty classic container + component example, however it's 100% useless and goes against the entire idea. In order for containers to be modular they need to be completely agnostic about render logic. If you hardcode the presentation in the container, then it's all just one big component split into two sections, somewhat arbitrarily I might add.
You can read about higher order components, but I'm going to focus on standard that's gaining traction: render props.
class FormContainer extends React.Component {
state = { data: [] }
submitForm = formData => {
api.post(formData).then(data => this.setState({ data }))
}
render() {
return this.props.children({
data: this.state.data,
submitForm: this.submitForm
})
}
}
Now you have a container that does one thing, that can be used repeatedly anywhere in your app, with any presentational component like so:
let SomewhereInYourApp = () => (
<FormContainer>
{({ data, submitForm }) => (
<div>
{/* put whatever you want here.. it's modular! */}
<FormComponent data={data} handleSubmit={submitForm} />
</div>
)}
</FormContainer>
)
Then you can make as many containers as you need which only do the specific business logic that's important to it and nest them however it makes sense to do so. I don't think any best practices say to combine everything into a single container. Nesting containers is perfectly acceptable.
If you have many containers and the nesting gets a little too pyramidy, consider using HoCs or a utility like react-adopt to compose containers that use render props.
Screenshot of composed containers from react-adopt:
I have a Modal component which is a popup window. I need to set different components to the bodyContainer.
<Modal
show={this.props.first.showModal}
size={this.props.first.sizeModal}
bodyContainer={this.props.first.modalBodyContainer}
/* bodyContainer={<Mail {...this.props}/>} */
onClose={this.props.firstActions.onCloseModalHome}
/>
As I understand the redux philosophy I can do it like this
// Search Patient actions
export const onSearchPatient = (ur, token) => dispatch => (
callToApiWithToken(`patient/by/ur/${ur}`, token)
.then(response =>
((response.data === null) ? dispatch(onSearchPatientNotFound(ur)) : dispatch(onSearchPatientFound(response.data))),
)
.catch(error => ({ type: psActions.ON_SUBMIT_ERROR }))
);
export const onSearchPatientFound = createAction(psActions.ON_SEARCH_PATIENT_FOUND);// data
export const onSearchPatientNotFound = createAction(psActions.ON_SEARCH_PATIENT_NOT_FOUND);
//Reducer
case psActions.ON_SEARCH_PATIENT_FOUND:
return {
...state,
showModal: true,
modalBodyContainer: <PatientDetails {...action.payload} />,
};
case psActions.ON_SEARCH_PATIENT_NOT_FOUND:
return {
...state,
error: true,
errorMsg: <div>The Patient <strong>{action.payload}</strong> is not in the system</div>,
};
But my colleague arguing that this is a bad practice. Specifically I'm talking about
modalBodyContainer: <PatientDetails {...action.payload} />
It is possible to relocate render logic to the Modal but in this case I need to create a switch for all possible components.
What is the right way to do this?
Edited
I have two ideas how to do this. What is the best to use?
//Action
export const setModalBody = createAction(psActions.SET_MODAL_BODY);//component
//Reducer
case psActions.SET_MODAL_BODY:
return {
...state,
showModal: true,
modalBodyContainer: action.payload />,
};
//Usage
onClick={() => searchPatient.length > 0 && onSearch(searchPatient, token).then(patientFound && setModalBody(<Patient {...props} />)
OR
const Modal = ({...}) => (
{{
//Something like this
//switch based on prop.bodyComponentType
//in this case I need to import all possible components to the Modal
sectionA: (
<SectionAComponent />
),
sectionB: (
<SectionBComponent />
),
sectionC: (
<SectionCComponent />
)
}[section]}
)
Edited 2
It is not about redux actions, it's about a component rendering inside hoc related to actions.
Your coworker is right.
The philosophy of Redux/React is largely around separation of concerns and making things loosely coupled but highly cohesive. For this situation, that means that your action should not know anything about the component it will cause to render (loosely coupled), but will be a complete definition of the result of your api call (highly cohesive).
I would have both of your reducer functions modify the same attribute of the redux state. Afterall, they represent the same piece of data, just with different values. One represents a success, and the other a failure. So I would have them both modify patientFoundStatus and provide it a value (perhaps using http codes, or a success/failure constant defined elsewhere). Then use this value to determine the output of your modal.
So why is this better?
Well for a couple of reasons. The most important is to consider how your code would need to be modified if, later on, this action needed to produce a different (additional) result in the view. Let's say that it is now used to pre-populate a form with the patient's information. How would your code need to change?
Given the code you have above, it would need to be entirely rewritten or duplicated. The modal body components would be completely irrelevant to this new feature, and the "success/failure" information (and patient information) needed would need to be added.
But consider if these actions only returned the data relevant to the view. Well now, what you do with that data is up to you. The reducers and actions could remain entirely unchanged, and you could merely use that pre-existing slice of state to impact a new section of your app.
That's one of the biggest advantages to react/redux when used correctly. Over time, what you end up creating is a flexible toolbox that makes adding new features trivial, because all the plumbing is well in place and wholly distinct from how it is utilized. Low coupling. High cohesion.