Can someone, please, point me in the right direction?
I have 3 components in my app.jsfile:
componentA
componentB
componentC
I guess they are siblings, no parent/child relationships here.
Components A and B are 2 input forms.
Both inputs are for numbers (3 digit numbers).
Component C is the button.
Once I click on the button I need to get input values from components A and B (so would be 2 numbers), do some if/else statements and return some result.
My question is what's the best way to pass data from components A and B into the C?
As the React doc says, if two components need to have access to the same state, the solution is to put the state in the closest common ancestor.
In the case of your form, the button needs to read the states from each input to submit the form. So each input's state should be in the parent of the button and the two inputs.
Here is one example :
const Form = () => {
const [value1, setValue1] = useState('')
const [value2, setValue2] = useState('')
return (
<>
<Input value={value1} onChange={event => setValue1(event.target.value)}/>
<Input value={value2} onChange={event => setValue2(event.target.value}}/>
<SubmitButton onClick={() => submit({value1, value2})}/>
</>
)
}
If you need to pass input values through too many props, you can use a context, a state management library (zustand, jotai, ...) or a form library (Formik, react-hook-form, ..).
Related
I have a single-selection Typeahead in my React app, and I'm trying to maintain the user's search input text after they select an option from the dropdown. Currently there may be multiple suggested results from their search input, but selecting one auto-updates the search input to this selection, eliminating the display of alternative options.
My original strategy was to store the search query string in a useState hook and update the Typeahead input with the old value as part of handleSelection, but that doesn't seem possible based on this answer.
const [searchQuery, setSearchQuery] = useState('');
const typeaheadRef = useRef(null);
const handleSelection = (selection:Object[]) => {
...do redux update on selection
typeaheadRef.current.value = searchQuery;
};
return(
<Typeahead
maxResults={50}
id="search-people"
options={filteredPeopleList}
labelKey={(option: Object) => option?.name ?? ''}
filterBy={['name']}
onChange={handleSelection}
ref={typeaheadRef}
onInputChange={query => setSearchQuery(query)}
/>)
Note: I am not passing a selected prop to the Typeahead.
I think what you're trying to do is possible, but there are a couple key pieces you need to update:
Omit the value being passed to the input internally so that it
simply maintains whatever the user has entered. You can control this
via the renderInput prop.
Change the default filterBy method so
that it filters based on the user's input and not the internal value
of the input. You can use the searchQuery string you're already
storing for this.
Code:
const [searchQuery, setSearchQuery] = useState('');
const typeaheadRef = useRef(null);
const handleSelection = (selection:Object[]) => {
...do redux update on selection
};
return (
<Typeahead
filterBy={(option) => (
// Filter by the user's input, not the internal state.
option.name.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1
)}
renderInput={({ value, ...inputProps }) => (
// Don't pass in `value` coming from internal state
<TypeaheadInputSingle {...inputProps} />
)}
maxResults={50}
id="search-people"
options={filteredPeopleList}
labelKey={(option: Object) => option?.name ?? ''}
filterBy={['name']}
onChange={handleSelection}
onInputChange={query => setSearchQuery(query)}
/>
);
Note: This isn't really how the component is intended to be used and it's possible the above code will result in buggy behavior. At first glance, it's also not a great user experience, since there's no indication a selection has been made, but maybe you're indicating that in some other way.
I have written a form which has more than 30 fields(material-ui components) using react-hook form.
All of the fields have different kinds of validation depending upon the type of the field.
The sample folder structure is as shown in this link.
folderStructure
detail_page.js is the root component. In this component useForm is initialized with defaultValues. And the form is wrapped with FormProvider(Context to pass to the deeply nested components). Also handleSave method is written in this component only. Upon clicking SAVE button, handleSubmit(handleSave) is called. From this component TopLevel(top_level.js) component is called.
top_level.js file groups fields(passed as props from detail_page.js) into sections.
A render method(renderSections) is written to display each section in mui-accordion, and all the fields within that section is passed to iterator.js file. the renderSections method is memoized with the dependency array of sectionobjects.
iterator.js file loops through each of the the fields passed as props from top_level.js and calls respective system_component based on the type of the field (Radio,checkbox,textbox,textarea,email,password,file_uploader,address,latlong,phone_number,list,date,date_range etc).
system_component is the actual component which wraps the material-ui's component using useController/Controller. Based on the type of field, respective system_component is called in iterator. for eg:-
SystemTextBox for type input
SystemEmail for type email in input
SystemDate for type date
and so on.
Some system component may be a combination of 2 or more mui's components. for eg
-SystemLatlong- combination of 4 or more text inputs and number input to display address components of the particular latlong.
SystemArray - any combination of rest of the system components(calling iterator again for this purpose)
and so on.
Now the problem is, whenever I click on save button, the save button freezes, In handleSubmit method I am setting MuiBackdrop component to display "Saving" text. This backdrop is called after 5+ seconds upon clicking save button. In detail page I have consoled isSubmitting, isValidating, isSubmitted state of formState. I figured out that, for almost 5+ seconds isSubmitting state holds true value, until which handleSave function is not triggered.
the reason for this is that react-hook-form uses context which is not optimized for performant re-renders. With context all consumers of that context will re-render regardless of if they care of the value that changed. With react hook form having one central context every input re-renders when isSubmitting is changed unfortunately.
I'm building a library that will solve this issues that is currently in beta called #zerry/react-formz. It guarantees isolated re-renders for inputs which means near constant performance no matter how many inputs you are rendering.
Its' in beta and currently being worked on but it might solve the issues you are having.
You can check it out here if you feel so inclined: https://react-formz.zerry.dev/docs/performance/
import {
Form,
TextField,
NumberField,
DependentTextField,
} from "#zerry/react-formz";
const NestedInput = () => {
return (
<TextField
required
name="name"
as={({ input }) => <input {...input} />}
/>
)
}
const MyForm = () => {
return (
<Form initialValues={{ name: "", age: "" }}>
<NestedInput />
<NumberField
required
name="age"
as={({ input }) => <input {...input} />}
/>
</Form>
);
};
when "Search" is clicked, next page is loaded by the below code.
import React from "react";
import { Link } from "react-router-dom";
function name(){
return(
<div className="search">
<input type="text"placeholder="city name"></input>
<input type="text"placeholder="number of people"></input>
<p><Link to="/nextpage">Search</Link</p>
</div>
)
}
I want to take data of these input fields to fetch api using that data to make cards on next page.
How to do it?
Here's the general idea of how you could accomplish this. You need to store the form input (in your case, the city and number of people) in application state. You can do that using the useState hook. Now this state can be managed by your first component (the one which renders the input fields) and accessed by the second component (the one that will display the values).
Because the values need to be accessed by both components, you should store the state in the parent component (see lifting state up). In my example, I used App which handles routing and renders FirstPage and SecondPage. The state values and methods to change it are passed as props.
Here's how you initialise the state in the App component:
const [city, setCity] = useState(null);
const [peopleCount, setPeopleCount] = useState(null);
city is the value, setCity is a function which enables you to modify the state. We will call setCity when the user makes a change in the input, like this:
export const FirstPage = ({ city, setCity, peopleCount, setPeopleCount }) => {
...
const handleCityChange = (e) => {
setCity(e.target.value);
};
...
<input
type="text"
placeholder="city name"
value={city}
onChange={handleCityChange}
/>
When a change in the input is made, the app will call the setCity function which will update the state in the parent component. The parent component can then update the SecondPage component with the value. We can do it simply by passing the value as a prop:
<NextPage city={city} peopleCount={peopleCount} />
Now you can do whatever you want with the value in your SecondPage component. Here's the full code for it, where it just displays both values:
export const NextPage = ({ city, peopleCount }) => {
return (
<div>
<p>city: {city}</p>
<p># of people: {peopleCount}</p>
</div>
);
};
And here's the full working example: https://stackblitz.com/edit/react-uccegh?file=src/App.js
Note that we don't have any field validation, and we have to manually write the change handlers for each input. In a real app you should avoid doing all this by yourself and instead use a library to help you build forms, such as Formik.
So we all know uncontrolled components are usually a bad thing, which is why we usually want to manage the state of an input (or group of inputs) at a higher-level component, usually some kind of container. For example, a <Form /> component manages state and passes down state as values to its <Input /> components. It also passes down functions such as handleChange() that allow the Input to update the state.
But while implementing my own <NumericInput /> component, it got me thinking that fundamentally this component is not self-reliant. It's reusable but requires a lot of repetition (opposite of DRY mentality) because everywhere in my app that I want to use this component, I have to implement these state values, a handleChange function, and in the case of my <NumericInput />, two additional functions to control the stepper arrows.
If I (or someone who took over my code) wanted to use this <NumericInput />, but they forget to run to a different container component and copy the stepUp() and stepDown() functions to pass down as props, then there will just be two non-functional arrows. I understand that this model allows our components to be flexible, but they also seem to be more error-prone and dependent on other components elsewhere. Again, it's also repetitive. Am I thinking about this incorrectly, or is there a better way of managing this?
I recognize this is more of a theory/design question, but I'm including my code below for reference:
NumericInput:
const NumericInput = ({label, stepUp, stepDown, ...props}) => (
<>
{label && <Label>{label}</Label>}
<InputContainer>
<Input type={props.type || "number"} {...props} />
<StepUp onClick={stepUp}>
//icon will go here
</StepUp>
<StepDown onClick={stepDown}>
//icon will go here
</StepDown>
</InputContainer>
</>
);
Form.js
const Form = (props) => {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
}
const stepUp = () => {
setValue(value++);
}
const stepDown = () => {
setValue(value--);
}
return (
<NumericInput
stepUp={stepUp}
stepDown={stepDown}
handleChange={handleChange}
label="Numeric"
)
}
Let's try to boil down your questions a bit:
[NumericInput] is reusable but requires a lot of repetition (opposite of DRY mentality) because everywhere in my app that I want to use this component, I have to implement these state values, a handleChange function, and in the case of my , two additional functions to control the stepper arrows.
The only repetition you have is to define a value and a handleChange callback property for <NumericInput />. The value prop contains your numeric input value and is passed in from the parent container component owning that state. You need the callback to trigger a change request from the child (controlled component). That is the ususal approach, when you need to lift state up or divide your components in container and presentational components.
stepUp/stepDown methods are an implementation detail of NumericInput, so you can remove them from Form. Form just wants to know, if a value change has been triggered and what the new changed numeric value is.
Concerning where to store the state: There is probably a reason, why you want to save the state in the Form component. The user enters some input in (possibly) multiple fields before pressing the submit button. In this moment your form needs all the state of its child fields to trigger further processing based on the given input. And the idiomatic React way is to lift state up to the form to have the information.
I understand that this model allows our components to be flexible, but they also seem to be more error-prone and dependent on other components elsewhere. Again, it's also repetitive.
A presentational component is rather less error prone and is independent, because it doesn't care about state! For sure, you write a bit more boilerplate code by exposing callbacks and value props in child. The big advantage is, that it makes the stateless components far more predictable and testable and you can shift focus on complex components with state, e.g. when debugging.
In order to ease up props passing from Form to multiple child components, you also could consolidate all event handlers to a single one and pass state for all inputs. Here is a simple example, how you could handle the components:
NumericInput.js:
const NumericInput = ({ label, value, onValueChanged }) => {
const handleStepUp = () => {
onValueChanged(value + 1);
};
const handleStepDown = () => {
onValueChanged(value - 1);
};
return (
<>
...
<Input value={this.state.value} />
<StepUp onClick={handleStepUp}></StepUp>
<StepDown onClick={handleStepDown}></StepDown>
</>
);
};
Form.js:
const Form = (props) => {
const [value, setValue] = useState(0);
const handleValueChanged = (value) => {
setValue(value);
}
return (
<NumericInput
onValueChanged={handleValueChanged}
value={value}
label="Numeric"
)
}
Move the setValue function to the NumericInput component
Manage your state through the component.
return (
<NumericInput
setValue={setValue}
label="Numeric"
/>
)
I would recommend you use hooks
recompose has this function called withHandlers that lets you define an event handler while keeping your component pure.
e.g. if you had something like this inside you render() method:
<TextInput value={value} onChange={ev => this.handleChange(ev.target.value)} />
it wouldn't be pure because every time your component renders, it'd by passing a different onChange function to your TextInput component.
This is great, but how can this be extended to support arrays of inputs? Or otherwise provide auxiliary data?
e.g. taking their example and extending it a bit:
const enhance = compose(
withState('values', 'updateValue', ''),
withHandlers({
onChange: props => inputName => event => {
props.updateValue(inputName, event.target.value)
},
onSubmit: props => event => {
event.preventDefault();
submitForm(props.value)
}
})
)
const Form = enhance(({ value, onChange, onSubmit, inputs }) =>
<form onSubmit={onSubmit}>
<label>Value
{inputs.map(input => (
<TextInput value={value} onChange={onChange(input)} />
))}
</label>
</form>
)
I've fudged the details a bit, but pretend inputs comes in as an array of input names. e.g. ["firstName","lastName"] would render two textboxes, one for each.
I want to store the values for each of these in my state, but I don't want to define separate updater functions for each. Thus I need to attach some metadata to the onChange={...} prop so that I know which field I'm updating in my state.
How can I do that?
In my example I wrote onChange(input) and added an extra 'level' to the withHandlers.onChange function to accept the extra argument, but withHandlers doesn't actually work that way. Is there some way to do this -- i.e., ensure that each TextInput receives the same function instance every time <Form> is rendered?
That's a typical case where you need to define the change handle directly inside your TextInput component.
You'd need to pass updateValue function as a prop to TextInput components.