I use react-select and I'm new.I have a component called Example
import React from "react";
import Select from "react-select";
class Example extends React.Component {
state = {
selectedOption: null
};
render() {
const { onHandleChange, options } = this.props;
return <Select onChange={onHandleChange} options={options} isMulti />;
}
}
export default Example;
In another file we have a functional Component
import React, { useState } from "react";
import Example from "./Example";
import { regionOptions, ageOptions, bordOptions } from "./Options";
export default function UserProfile() {
const [selectedOption, setSelectedOption] = useState({
region: "",
age: "",
bord: ""
});
const handleChange = (key, selectedOption) => {
setSelectedOption(prev => ({ ...prev, [key]: selectedOption }));
};
console.log(Object.values(selectedOption));
return (
<div>
<Example
id="region"
onHandleChange={value => handleChange("region", value)}
selectedOption={selectedOption.region}
options={regionOptions}
/>
<Example
id="age"
onHandleChange={value => handleChange("age", value)}
selectedOption={selectedOption.age}
options={ageOptions}
/>
<Example
id="bord"
onHandleChange={value => handleChange("bord", value)}
selectedOption={selectedOption.bord}
options={bordOptions}
/>
</div>
);
}
I display the values in the console by the handChange event.
But when the options increase, I can't say which one belongs to which .
I want the console.log instead of the
[Array[n], Array[n], Array[n]]
Something like this will be displayed
[Region[n], age[n], bord[n]]
You can see my code here
https://codesandbox.io/s/upbeat-night-tqsk7?file=/src/UserProfile.js:0-1040
just use
console.log(selectedOption);
instead of
console.log(Object.values(selectedOption));
What you can do is a create a custom hook and make the following changes.
// custom hook
function useFormInput(initial) {
const [value, setValue] = useState(initial);
const handleOnChange = e => {
setValue(e);
};
return {
selectedOptions: value,
onChange: handleOnChange
};
}
then in the code
export default function UserProfile() {
const region = useFormInput(""); // return { selectedOption, onChange }
const age = useFormInput("");
const bord = useFormInput("");
// NB {...region} pass deconstructed return values from custom hook to the component
return (
<div>
<Example id="region" {...region} options={regionOptions} />
<Example id="age" {...age} options={ageOptions} />
<Example id="bord" {...bord} options={bordOptions} />
{JSON.stringify(region.selectedOptions)}
{JSON.stringify(age.selectedOptions)}
{JSON.stringify(bord.selectedOptions)}
</div>
);
}
// your UI component
render() {
const { onChange, options } = this.props;
return <Select onChange={onChange} options={options} isMulti />;
}
working example
https://codesandbox.io/s/optimistic-napier-fx30b?
Related
I have the following example where the toggleComponent.js is working perfectly.
The problem here is that I don't want to render the <ContentComponent/> inside the toggle, rather I want the opposite, I want to toggle the <ContentComponent/> that will be called in another component depending on the state of the toggle.
So the <ContentComponent/> is outside the toggleComponent.js, but they are linked together. So I can display it externally using the toggle.
An image to give you an idea:
Link to funtional code:
https://stackblitz.com/edit/react-fwn3rn?file=src/App.js
import React, { Component } from "react";
import ToggleComponent from "./toggleComponent";
import ContentComponent from "./content";
export default class App extends React.Component {
render() {
return (
<div>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && <h1>test</h1>}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
<ToggleComponent
render={({ isShowBody, checkbox }) => (
<div>
{isShowBody && (
<h1>
<ContentComponent />
</h1>
)}
<button onClick={checkbox}>Show</button>
</div>
)}
/>
</div>
);
}
}
Bit tweaked your source.
Modified ToggleComponent
import React from "react";
export default class ToggleComponent extends React.Component {
constructor() {
super();
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
this.setState({ checked: !this.state.checked });
this.props.toggled(!this.state.checked);
};
checkbox = () => {
return (
<div>
<label>Toggle</label>
<span className="switch switch-sm">
<label>
<input type="checkbox" name="select" onClick={this.handleClick} />
<span />
</label>
</span>
</div>
);
};
render() {
return this.checkbox();
}
}
Added OtherComponent with ContentComponent inside.
import React, { Component } from "react";
import ContentComponent from "./content";
export default class OtherComponent extends React.Component {
render() {
return <div>{this.props.show ? <ContentComponent /> : null}</div>;
}
}
Separated as per your requirement.
Modified App
import React, { Component, PropTypes } from "react";
import ToggleComponent from "./toggleComponent";
import OtherComponent from "./otherComponent";
export default class App extends React.Component {
constructor() {
super();
this.toggled = this.toggled.bind(this);
this.state = { show: false };
}
toggled(value) {
this.setState({ show: value });
}
render() {
return (
<div>
<ToggleComponent toggled={this.toggled} />
<OtherComponent show={this.state.show} />
</div>
);
}
}
Working demo at StackBlitz.
If you want to share states across components a good way to do that is to use callbacks and states. I will use below some functional components but the same principle can be applied with class based components and their setState function.
You can see this example running here, I've tried to reproduce a bit what you showed in your question.
import React, { useState, useEffect, useCallback } from "react";
import "./style.css";
const ToggleComponent = props => {
const { label: labelText, checked, onClick } = props;
return (
<label>
<input type="checkbox" checked={checked} onClick={onClick} />
{labelText}
</label>
);
};
const ContentComponent = props => {
const { label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Holder = () => {
return (
<div>
<ContentComponent label="First">
{checked => (
<h1>First content ({checked ? "checked" : "unchecked"})</h1>
)}
</ContentComponent>
<ContentComponent
label="Second"
render={checked => (checked ? <h1>Second content</h1> : null)}
/>
</div>
);
};
PS: A good rule of thumb concerning state management is to try to avoid bi-directional state handling. For instance here in my example I don't use an internal state in ToggleComponent because it would require to update it if given checked property has changed. If you want to have this kind of shared state changes then you need to use useEffect on functional component.
const ContentComponent = props => {
const { checked: checkedFromProps, label, children, render: renderFromProps, onChange } = props;
const [checked, setChecked] = useState(checkedFromProps || false);
const defaultRender = () => null;
const render = renderFromProps || children || defaultRender;
// onChange callback
useEffect(() => {
if (onChange) {
onChange(checked);
}
}, [ checked, onChange ]);
// update from props
useEffect(() => {
setChecked(checkedFromProps);
}, [ checkedFromProps, setChecked ]);
return (
<div>
<ToggleComponent
label={label}
checked={checked}
onClick={() => {
setChecked(previousChecked => !previousChecked);
}}
/>
{render(checked)}
</div>
);
};
const Other = () => {
const [ checked, setChecked ] = useState(true);
return (
<div>
{ checked ? "Checked" : "Unchecked" }
<ContentComponent checked={checked} onChange={setChecked} />
</div>
);
};
I'm trying to practice React forms, and I can't seem to solve this error. I've developed a multi-input form and I need the value to update the parent's state. When I start typing in the input field it triggers the Switch case default. I'm not sure if maybe I'm typing the 'handleChange' function wrong or if I should have a separate state for the state.data. Any advice would be much appreciated
import React, { useState } from 'react';
import Form from './Form';
import Results from './Results';
function App() {
const [state, setState] = useState({
data: 1,
Name: '',
Email: '',
City: ''
});
const nextStep = () => {
setState({
data: state.data + 1
});
};
const handleChange = e => {
let field = e.target.name;
let val = e.target.value;
setState({ [field]: val });
};
switch (state.data) {
case 1:
return (
<div className='App-container'>
<Form
button='Next'
nextStep={nextStep}
name='Name'
state={state.name}
handleChange={handleChange}
/>
</div>
);
case 2:
return (
<div className='App-container'>
<Form
button='Next'
nextStep={nextStep}
name='Email'
state={state.email}
handleChange={handleChange}
/>
</div>
);
case 3:
return (
<div className='App-container'>
<Form
button='Submit'
nextStep={nextStep}
name='City'
state={state.city}
handleChange={handleChange}
/>
</div>
);
case 4:
return (
<div className='App-container'>
<Results data={state} />
</div>
);
default:
return 'Error';
}
}
export default App;
import React from 'react';
const Form = ({ button, nextStep, name, state, handleChange }) => {
const handleSubmit = e => {
e.preventDefault();
nextStep();
};
return (
<div className='Form-container'>
<form onSubmit={handleSubmit}>
<input
type='text'
placeholder={name}
name={name}
value={state}
onChange={handleChange}
/>
<input type='submit' value={button} />
</form>
</div>
);
};
export default Form;
import React from 'react';
const Results = ({ data }) => {
return (
<div>
<h1>Info</h1>
<p>{data.name}</p>
<p>{data.email}</p>
<p>{data.city}</p>
</div>
);
};
export default Results;
You losing state on handleChange while expecting it to merge with the current one, but it is not how useState works in contradiction to this.setState in class components:
// Results new state: { data };
setState({ data: state.data + 1 });
// Instead update the copy of the state.
// Results new state: { data, Name, Email, City }
setState({ ...state, data: state.data + 1 });
Check useState note in docs:
Note
Unlike the setState method found in class components, useState does
not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax.
Another option is useReducer,
which is more suited for managing state objects that contain multiple
sub-values.
I recommend using a custom hook for form inputs.like follows
//useForm.js
const useForm = defaultValues => {
const [values, setValues] = useState(defaultValues);
const handleChange = ({ name, value }) => {
setValues(prevState => ({ ...values, [name]: value }));
};
const reset = () => {
setValues(null);
};
return [values, handleChange, reset];
};
inside component
const [formValues, handleChange, resetForm] = useForm();
return (
<>
<Input
value={formValues.name}
onChange: str => handleChange({ name: "name", value: str })
/>
<Input
value={formValues.email}
onChange: str => handleChange({ name: "email", value: str })
/>
</>
)
You need to spared old state
const handleChange = e => {
let field = e.target.name;
let val = e.target.value;
setState({ ...state, [field]: val });
};
this is setState({ ...state, [field]: val });
I have this wrapper class that is used because I am using Formik and the FieldArray
import React, { Component } from "react";
import { ReactDOM } from "react-dom";
import Select from "react-select";
import { observer } from "mobx-react";
import { axiosInstance } from "../stores/AxiosInstance";
#observer
export default class CountryStateSelectComponent extends React.Component {
constructor(props) {
super(props);
this.state = { stateOptions: [] };
}
handleCountryChange = value => {
const that = this;
axiosInstance
.get(`/States?countryId=${value.value}`)
.then(function(response) {
that.props.onChange(that.props.countryName, value);
that.props.onChange(that.props.stateName, null);
const states = response.data.map(state => {
return { label: state.name, value: state.id };
});
// if I move out state select code then won't need to update state here but don't know how to call something like updateState(record)
that.setState({
stateOptions: states
});
});
};
handleStateChange = value => {
console.log(this.props.stateName, value)
this.props.onChange(this.props.stateName, value);
};
handleCountryBlur = () => {
this.props.onBlur(this.props.countryName, true);
};
handleStateBlur = () => {
this.props.onChange(this.props.stateName, true);
};
render() {
const props = this.props;
return (
<React.Fragment>
<div className="field">
<label className="label">Country</label>
<div className="control">
<Select
options={props.options}
isMulti={props.isMulti}
onChange={this.handleCountryChange}
onBlur={this.handleCountryBlur}
closeMenuOnSelect={props.closeMenuOnSelect}
/>
{this.props.CountryError}
</div>
</div>
<div className="field">
<label className="label">State/Province</label>
<div className="control">
<Select
options={this.state.stateOptions}
onChange={this.handleStateChange}
onBlur={this.handleStateBlur}
/>
{this.props.StateError}
</div>
</div>
</React.Fragment>
);
}
}
However what I found is that when the State gets selected the value does not get stored in Formik(it gets stored as undefined and sometimes true).
So now I am thinking maybe moving out the State Zip out and making it's own wrapper or something but I don't know how to get the "states" that came back and populate the correct state box as they can generate many.
#inject("AccountSetupStore")
#observer
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = { records: [this.generateRecord(1, true, true)] };
}
componentDidMount() {
const accountSetupStore = this.props.AccountSetupStore;
accountSetupStore.getCountries();
}
updateState(record) {
// would like to call this method that can somehow update the record
// propblem is I don't seem to have access to props when this function is being called from the CountryStateSelectComponent
}
render() {
const props = this.props;
const accountSetupStore = props.AccountSetupStore;
const countries = [];
for (const country of accountSetupStore.countries) {
countries.push({ value: country.id, label: country.name });
}
return (
<section className="accordions">
<Formik
initialValues={{
records: this.state.records
}}
onSubmit={(
values,
{ setSubmitting, setErrors}
) => {
console.log(values,"values");
}}
validationSchema={Yup.object().shape({
branches: Yup.array()
.of(
Yup.object().shape({
})
)
})}
render={({
values,
setFieldValue,
setFieldTouched,
}) => (
<FieldArray
name="records"
render={arrayHelpers => (
<Form>
{values.records.map((record, index) => {
return (<article}>
<CountryStateSelectComponent options={countries}
onChange={setFieldValue}
countryName={`records[${index}].selectedCountry`}
stateName={`records[0].selectedState`}
onBlur={setFieldTouched}
isMulti={false}
index = {index}
closeMenuOnSelect={true}
CountryError = {<ErrorMessage name={`records[${index}].selectedCountry`}/>}
StateError= {<ErrorMessage name={`records[${index}].selectedState`}/>}
/>
</article>)
})}
</Form>
)}
/>
)}
/>
</section>
);
}
}
React Select onChange sends the value to the method supplied
const onStateChange = (selectedOption, {action}) => {
//do something with the selectedOption according to the action
}
<Select onChange={onStateChange} />
See the documentation for the onChange in the Props documentation.
I'm doing a React refresher. I set state in App.js and created an event called handleUserNameChange() to change usernames in the state object. Each input from UserInput.js should change it's relative UserOutput component's username in state that's set in App.js. How can I make so that when I type text into one input it only changes one index in my users array in state? Do I need to change my handleUserNameChange event?
App.js
import React, { Component } from 'react';
//Components
import UserInput from './UserInput/UserInput';
import UserOutput from './UserOutput/UserOutput';
class App extends Component {
state = {
users: [
{user: 'Debbie'},
{user: 'Ronald'}
]
};
handleUserNameChange = (event) => {
this.setState({
users: [
{user: event.target.value},
{user: event.target.value}
]
});
};
render() {
return (
<div className="App">
<UserOutput
username = {this.state.users[0].user}
/>
<UserInput
nameChange={this.handleUserNameChange} />
<UserOutput
username={this.state.users[1].user}
/>
<UserInput
nameChange={this.handleUserNameChange}
/>
</div>
);
}
}
export default App;
UserOuput.js
import React from 'react';
const UserOutput =(props) => {
return (
<div>
<h3>{props.username}</h3>
</div>
);
}
export default UserOutput;
UserInput.js
import React from 'react';
const UserInput = (props) => {
return (
<div>
<input type="text"
onChange={props.nameChange}
/>
</div>
);
}
export default UserInput;
In App.js:
<UserInput
nameChange={this.handleUserNameChange(0)}//0 for first, 1 for second
/>
handleUserNameChange = (index) => (event) => {
this.setState({
users: this.state.users.map(
(user,i)=>
(i===index)
? event.target.value
: user
)
});
};
It would probably be better to not hardcode user 0 and user 1 but just map the state to react modules.
render() {
const userInput = index =>
<UserInput
nameChange={this.handleUserNameChange(index)} />;
const UserOutput = user =>
<UserOutput
username = {user}/>;
return (
<div className="App">
this.state.users.map(
(user,index)=>
<div>{userInput(index)}{UserOutput(user)}</div>
)
</div>
);
}
pass the index value in handleUserNameChange function from render function and use double arrow in handleUserNameChange to get the index value.
handleUserNameChange = index => event => {
this.setState(prevState => {
const users = [...prevState.users];
users[index].user = event.target.value;
return { users };
});
};
render() {
return (
<div className="App">
{this.state.users.map((user, index) => (
<React.Fragment>
<UserOutput username={user} />
<UserInput nameChange={this.handleUserNameChange(index)} />
</React.Fragment>
))}
</div>
);
}
Novice.
I have a redux form component called Client. There is a sub component in that form called Address. Address is called in client as follows:
<FormSection name="Address">
<Address />
</FormSection>
Address is in the form of AddressContainer and Address.
I am setting initial values for the Client redux form as follows:
let ClientForm = reduxForm({
form: CLIENT_FORM_NAME
})(Client);
let ClientForm2 = connect(
(state, ownProps) => ({
initialValues: ownProps.isEditMode ? state.editClient : {}, // pull initial values from client reducer
enableReinitialize: true
}),
{ reducer } // bind client loading action creator
)(ClientForm);
export default ClientForm2;
However the Address component does not get any of the initial state set in it.
How do you, in redux form, pass the initial state down from the main form to the its sub components - in this case Address?
AddressContainer:
import React, { Component, PropTypes } from "react";
import { connect } from "react-redux";
import { Field, change } from "redux-form";
import { Col, Panel, Row } from "react-bootstrap";
import Select from "react-select";
import Address from "./address";
import { getSuburbs, ensureStateData } from "./actions";
import FormField from "../formComponents/formField";
import TextField from "../formComponents/textField";
import StaticText from "../formComponents/staticText";
import { CLIENT_FORM_NAME } from "../clients/client/client";
export class AddressContainer extends Component {
static contextTypes = {
_reduxForm: PropTypes.object.isRequired
};
constructor(props, context) {
super(props, context);
this.state = {
selectedSuburb: null
};
}
// Manage Select.Async for new data request - for suburbs.
handleSuburbSearch = query => {
const { addressData } = this.props;
const companyStateId = addressData.companyStateId;
if (!query || query.trim().length < 2) {
return Promise.resolve({ options: [] });
}
const queryString = {
query: query,
companyStateId: companyStateId
};
return getSuburbs(queryString).then(data => {
return { options: data };
});
};
// Update the current state with the selected suburb.
onSuburbStateChange = value => {
this.setState({ selectedSuburb: value });
};
componentDidMount() {
this.props.ensureStateData();
}
render() {
const { initialValues, addressData, updatePostcodeValue } = this.props;
const { value } = this.state;
const sectionPrefix = this.context._reduxForm.sectionPrefix;
if (addressData.isLoading || !addressData.states.length) {
return <p>Loading</p>;
}
if (addressData.error) {
return <p>Error loading data</p>;
}
console.group("InitalValues Address");
const companyStateId = addressData.companyStateId;
console.log("companyStateId:", companyStateId);
initialValues.Address = {
...initialValues.Address,
state: addressData.states.find(
option => option.stateId === companyStateId
)
};
console.log(
"addressData.states.Find: ",
addressData.states.find(option => option.stateId === companyStateId)
);
console.log("addressData.states: ", initialValues.Address.state);
console.log("initialValues.Address: ", initialValues.Address);
console.log("initialValues: ", initialValues);
console.groupEnd();
return (
<Address
initialValues={initialValues}
handleSuburbSearch={this.handleSuburbSearch}
onSuburbStateChange={this.onSuburbStateChange}
updatePostcodeValue={updatePostcodeValue}
addressData={addressData}
sectionPrefix={sectionPrefix}
/>
);
}
}
const mapStateToProps = state => ({
initialValues: state.editClient,
companyStateId: state.companyStateId,
addressData: state.addressData
});
const mapDispatchToProps = dispatch => ({
ensureStateData: () => dispatch(ensureStateData()),
getSuburbs: values => dispatch(getSuburbs(values)),
updatePostcodeValue: (postcode, sectionPrefix) =>
dispatch(
change(
CLIENT_FORM_NAME,
`${sectionPrefix ? sectionPrefix + "." : ""}postcode`,
postcode
)
)
});
export default connect(mapStateToProps, mapDispatchToProps)(AddressContainer);
Address:
import React, { Component, PropTypes } from "react";
import { connect } from "react-redux";
import { Field, change, reduxForm } from "redux-form";
import { Col, Panel, Row } from "react-bootstrap";
import Select from "react-select";
import { getSuburbs } from "./actions";
import FormField from "../formComponents/formField";
import TextField from "../formComponents/textField";
import StaticText from "../formComponents/staticText";
import reducer from "../address/reducer";
export const Address = props => {
const {
initialValues,
handleSuburbSearch,
companyValue,
onSuburbStateChange,
updatePostcodeValue,
addressData,
sectionPrefix
} = props;
const { reset } = props;
{
console.log("PROPS ADDRESS DATA: ", props.stateData);
console.log("PROPS INITIALVALIES: ", props.initialValues);
console.log("COMPANY VALUE: ", companyValue);
}
return (
<Panel header={<h3>Client - Address Details</h3>}>
<Row>
<Field
component={TextField}
name="address1"
id="address1"
type="text"
label="Address Line 1"
placeholder="Enter street 1st line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
<Field
component={TextField}
name="address2"
id="address2"
type="text"
label="Address Line 2"
placeholder="Enter street 2nd line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props;
const {
fieldCols,
labelCols,
controlCols,
label,
inputClass
} = props;
// The props we want the inner Select textbox to have.
const { name, onChange } = input;
const onStateChange = state => {
console.log("onStateChange", state);
onChange(state);
};
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select
name={name}
onChange={onStateChange}
placeholder="Select state"
valueKey="id"
options={addressData.states}
labelKey="stateLabel"
optionRenderer={option =>
`${option.stateShortName} (${option.stateName})`}
value={input.value}
selectValue={
Array.isArray(input.value) ? input.value : undefined
}
/>
</FormField>
);
}}
name="state"
id="state"
label="State."
fieldCols={6}
labelCols={3}
controlCols={6}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props;
const {
fieldCols,
labelCols,
controlCols,
label,
inputClass
} = props;
// The props we want the inner Select.Async textbox to have.
const { name, value, onChange, onBlur, onFocus } = input;
// A suburb was selected so "onChange" is updated along
//with the static form component "Postcode".
const onSuburbChange = value => {
onSuburbStateChange(value);
input.onChange(value);
updatePostcodeValue(value ? value.postcode : null, sectionPrefix);
};
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select.Async
{...input}
onChange={onSuburbChange}
placeholder="Select suburb"
valueKey="id"
labelKey="suburbName"
value={input.value}
loadOptions={handleSuburbSearch}
backspaceRemoves={true}
/>
</FormField>
);
}}
name="suburb"
id="suburb"
label="Suburb."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field
component={StaticText}
name="postcode"
id="postcode"
label="Postcode."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
</Panel>
);
};
Address.propTypes = {
handleSuburbSearch: PropTypes.func.isRequired,
onSuburbStateChange: PropTypes.func.isRequired
};
const AddressForm = reduxForm({
form: "Address"
})(Address);
let AddressForm2 = connect(
(state, ownProps) => ({
initialValues: state.editClient, // pull initial values from client reducer
enableReinitialize: true
}),
{ reducer } // bind client loading action creator
)(AddressForm);
export default Address;
state.editClient from the Redux tool in Chrome:
Note that address1, suburb and postcode all have values and its these that are not being displayed in the Address component...