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.
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 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?
it is my first React App
i want create simple typeahead(autocomplete)
i want when i click on searched list of item, this item will show in value of my Parent input
now my click doesnt work, working only search by name
it is my parent
`
import React, { Component } from 'react';
import logo from './logo.svg';
import './Search.css';
import Sugg from './Sugg';
class Search extends Component {
constructor(props) {
super(props);
this.onSearch = this.onSearch.bind(this);
this.handleClickedItem = this.handleClickedItem.bind(this);
this.onClick = this.onClick.bind(this);
this.state = {
companies: [],
searchedList: [],
value: ''
}
}
componentDidMount() {
this.fetchApi();
console.log(this.state.companies);
}
fetchApi = ()=> {
const url = 'https://autocomplete.clearbit.com/v1/companies/suggest?query={companyName}';
fetch(url)
.then( (response) => {
let myData = response.json()
return myData;
})
.then((value) => {
let companies = value.map((company, i) => {
this.setState({
companies: [...this.state.companies, company]
})
})
console.log(this.state.companies);
});
}
onSearch(arr){
// this.setState({companies: arr});
};
handleInputChange = () => {
console.log(this.search.value);
let searched = [];
this.state.companies.map((company, i) => {
console.log(company.name);
console.log(company.domain);
const tempName = company.name.toLowerCase();
const tempDomain = company.domain.toLowerCase();
if(tempName.includes(this.search.value.toLowerCase()) || tempDomain.includes(this.search.value.toLowerCase())) {
searched.push(company);
}
})
console.log(searched);
this.setState({
searchedList: searched
})
if(this.search.value == '') {
this.setState({
searchedList: []
})
}
}
handleClickedItem(data) {
console.log(data);
}
onClick = e => {
console.log(e.target.value)
this.setState({ value: e.target.value});
};
render() {
return (
<div className="Search">
<header className="Search-header">
<img src={logo} className="Search-logo" alt="logo" />
<h1 className="Search-title">Welcome to React</h1>
</header>
<form>
<input
placeholder="Search for..."
ref={input => this.search = input}
onChange={this.handleInputChange}
/>
<Sugg searchedList={this.state.searchedList} onClick={this.onClick.bind(this)} />
</form>
</div>
);
}
}
export default Search;
`
and here my child component
i dont know how call correctly click event
import React from 'react';
const Sugg = (props) => {
console.log(props);
const options = props.searchedList.map((company, i) => (
<div key={i} >
<p onClick={() => this.props.onClick(this.props)}>{company.name}</p>
</div>
))
console.log(options);
return <div >{options}</div>
}
export default Sugg;
please help me who knows how it works
thanks a lot
In the parent you could modify your code:
onClick = company => {
console.log('company', company);
this.setState({ value: company.name});
};
and you don't need to bind this because onClick is an arrow function
<Sugg searchedList={this.state.searchedList} onClick={this.onClick} />
and in the child component, you need to use props from the parameters, not from the this context:
<p onClick={() =>props.onClick(company)}>{company.name}</p>
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>
);
}
I'm trying to update some immutable data. However, I am doing it incorrectly and it doesn't work.
My understanding, is I need to pass it a unique it (eg the key) so it knows which item in state to update. But this may be wrong, and I would like to do this the proper way. I tried the immutability helper, but had no success.
I will also want to update/add/remove nested items in an immutable array.
I put this in a codeSandbox for ease https://codesandbox.io/s/mz5WnOXn
class App extends React.Component {
...
editTitle = (e,changeKey) => {
this.setState({
movies: update(this.state.movies, {changeKey: {name: {$set: e.target.value}}})
})
console.log('Edit title ran on', key)
};
...
render() {
...
return (
<div>
{movies.map(e => {
return (
<div>
<input key={e.pk} onChange={this.editTitle} value={e.name} changeKey={e.pk} type="text" />
With genres:
{e.genres.map(x => {
return (
<span key={x.pk}>
<input onChange={this.editGenres} value={x.name} changeKey={x.pk} type="text" />
</span>
);
})}
</div>
);
})}
</div>
);
}
}
Couple of things,
1. By looking at your data i don't see any pk property maybe its a typo (are you trying to use the id property instead?).
2. The key prop should be on the root element that you return on each iteration of your map function.
3. You can't just add none native attributes to a native Html element instead you can use the data-* like data-changeKey and access it via e.target.getAttribute('data-changeKey')
Something like this:
import { render } from 'react-dom';
import React from 'react';
import { reject, pluck, prop, pipe, filter, flatten, uniqBy } from 'ramda';
import { smallData } from './components/data.js';
import './index.css';
import Result from './components/Result';
import update from 'react-addons-update';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: smallData.movies,
selectedFilters: {},
searchQuery: '',
};
}
editTitle = (e) => {
const attr = e.target.getAttribute('data-changeKey');
this.setState({
movies: update(this.state.movies, {
// what are you trying to do here?
//changeKey: { name: { $set: e.target.value } },
}),
});
console.log('edit title ran on', attr);
};
onSearch = e => {
this.setState({
searchQuery: e.target.value,
});
};
render() {
const { movies } = this.state;
return (
<div>
{movies.map((e, i) => {
return (
<div key={i}>
<input
onChange={this.editTitle}
value={e.name}
data-changeKey={e.id}
type="text"
/>
With genres:
{e.genres.map((x,y) => {
return (
<span key={y}>
<input
onChange={this.editTitle}
value={x.name}
data-changeKey={x.id}
type="text"
/>
</span>
);
})}
Add new genre:
<input type="text" />
</div>
);
})}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Index is what update needs in order to know which data to change.
Firstly, map sure your map is creating an index:
{movies.map((e,index) => {
return (
<input key={e.pk} onChange={e => this.editTitle(e, index)} value={e.name} >
...
Then pass the index to the immutability-helper update like so:
to:
editTitle = (e, index) => {
this.setState({
movies: update(this.state.movies, {[index]: {name: {$set: e.target.value}}})
})