input target value from child component - javascript

I've managed to get my input component to render onto the dom, however I'm having a bit of trouble accessing the props.
Functional input component
const InputField = props => {
const { inputValue, updateInputValue } = props
return (
<Input>
<input
type="text"
placeholder="Please specify"
value={inputValue}
onChange={updateInputValue}
/>
<hr />
<label>Other</label>
</Input>
)
}
The component is only rendered to the dom based on an object property inside of an array
const MultiChoiceQuestions = props => {
const { multiChoiceArray, handleClick } = props
return (
<ButtonContainer>
{multiChoiceArray.map(questionChoice => {
if (questionChoice.type === 'input') {
return <InputField />
}
return (
<Button type="button" key={questionChoice.id} onClick={() => handleClick(questionChoice)}>
{questionChoice.text}
</Button>
)
})}
</ButtonContainer>
)
}
The multiChoice component is imported once again to create a top-level component that the app consumes
const Question = props => {
let responses
switch (props.data.type) {
case 'multiChoice':
responses = (
<MultiChoiceQuestions
multiChoiceArray={props.data.choices}
handleClick={props.handleClick}
inputValue={props.inputValue}
updateInputValue={props.updateInputValue}
/>
)
break
default:
responses = <div>Error: no question type: `{props.data.type}`</div>
}
const { data } = props
return (
<AnimatedDiv key={data.id}>
<QuestionText>{data.text}</QuestionText>
{responses}
</AnimatedDiv>
)
}
And the final component looks like this
class Survey extends Component {
constructor(props) {
super(props)
this.state = {
currentQuestionId: 1,
userAnswers: [],
isActive: false,
inputValue: '',
}
this.selectAnswer = this.selectAnswer.bind(this)
this.test = this.test.bind(this)
}
selectAnswer = answer => {
this.setState(state => ({
currentQuestionId: state.currentQuestionId + 1,
userAnswers: state.userAnswers.concat([answer]),
isActive: !state.isActive,
}))
}
checkInput = event => {
this.setState({
inputValue: event.target.value,
})
}
test = event => {
console.log(event.target.value)
}
render() {
const { currentQuestionId, isActive, inputValue } = this.state
const { questions } = this.props
const currentPercentage = (currentQuestionId * 100) / questions.length
return (
<SurveyContainer>
<Question
data={questions.find(q => q.id === currentQuestionId)}
className={isActive ? 'active' : ''}
handleClick={this.selectAnswer}
value={inputValue}
onChange={this.test}
/>
</SurveyContainer>
)
}
}
The InputField component renders out just fine, however, the function for my onChange event is not firing...There's a mistake somewhere in the pipeline, probably inside the question component?

It looks like you haven't passed any props to <InputField /> in your MultiChoiceQuestions component.

I can not see where you pass props from
<MultiChoiceQuestions>
...
<InputFiled props={props} />
...
</MultiChoiceQuestions>
Probably pass only the props which are needed in InputField component, such as inputValue, updateInputValue:
<InputFiled
inputValue={inputValue}
updateInputValue={updateInputValue}
/>
const InputField = (inputValue, updateInputValue) => {
...
<input
type="text"
placeholder="Please specify"
value={inputValue}
onChange={(e) => updateInputValue(e)}
/>
...
}
Hope that will help.

Related

How to save input fields changes in React?

I created an update page in my react app.
To sum up; when I click a div it shows the data in input fields. For example, when I click the first field, type in there something, and click another div, the changes I made disappear. I want that if I make a change in there, It should stay there before save it. How can I do that?
<div className="detailsPage-panel-right">
{
this.state.activeFields?.fields?.map(field => {
const config = this.config.fields.find(fieldConfig =>
fieldConfig.key === field.key)
const inputConfig = {
type: config?.dataType.type,
id: config?.key,
label: config?.displayName,
required: false,
autofocus: false,
value: field.value
};
const inputBindings: ITextInputBindings = {}
return (
<div key={`${this.state.activeFields.key}-${field.key}`}>
<TextInput config={inputConfig} bindings={inputBindings}></TextInput>
</div>
)
})
}
</div>
Text input component
import "./text-field.scss";
import { Form } from "react-bootstrap";
import { Component } from "../../utils/stateless-component";
export interface ITextInputBindings {
}
export interface ITextInputConfig {
type: "text" | "dateTime" | "enumeration" | "guid" | undefined,
id: string | undefined,
label: string | undefined,
placeholder?: string,
required: boolean,
autofocus?: boolean,
value?: string
}
class TextInput extends Component<ITextInputConfig,ITextInputBindings> {
render() {
return (
<div className="textInput">
<Form.Group className="mb-3 textInput-group">
<Form.Label htmlFor={this.config.id}>{this.config.label}</Form.Label>
<Form.Control type={this.config.type}
placeholder={this.config.placeholder}
required={this.config.required}
id={this.config.id}
autoFocus={this.config.autofocus}
defaultValue={this.config.value} />
</Form.Group>
</div>
);
}
}
export default TextInput;
I think I should use onChange method but I don't know how.
key prop
Remember to check re-render when your activeFields.field changes, because you had set the key in your TextInput.
This will result in the TextInput component be unmount and create a new one
// 📌 check this state. Do not mutate to prevent re-render
this.state.activeFields?.fields?.map(field => {
const config = this.config.fields.find(fieldConfig =>
fieldConfig.key === field.key)
const inputConfig = {
type: config?.dataType.type,
id: config?.key,
label: config?.displayName,
required: false,
autofocus: false,
value: field.value
};
const inputBindings: ITextInputBindings = {}
return (
// 📌 if key be mutated from state, it will create a new component intead of old one
<div key={`${this.state.activeFields.key}-${field.key}`}>
<TextInput config={inputConfig} bindings={inputBindings}></TextInput>
</div>
)
})
Save Input value
And if you want to save the input value in TextInput, it is depends on which component you want to save the input value by state.
Save in the child component (In your case the TextInput)
Add a onChange event and a state in your TextInput component
Then add props because you are give props to it.
like this example edited from your code (maybe can not run, but the concept should work)
class TextInput extends Component<ITextInputConfig,ITextInputBindings> {
constructor(props) {
super(props);
this.state = { ...this.props }
}
// set state
const handleChange = (e) => {
this.setState({...this.state,
config: { ...this.state.config, value: e.target.value }
})
}
render() {
return (
<div className="textInput">
<Form.Group className="mb-3 textInput-group">
<Form.Label htmlFor={this.config.id}>{this.config.label}</Form.Label>
<Form.Control type={this.config.type}
placeholder={this.config.placeholder}
required={this.config.required}
id={this.config.id}
autoFocus={this.config.autofocus}
defaultValue={this.config.value}
// 📌 add onChange event on Form.Control
onChange={handleChange}
/>
</Form.Group>
</div>
);
}
}
Save in parent component
And if you need control or save state changes from parent component
add a state and a changeState function in your parent component, and give changeState to TextInput's props and let the
changeState prop mutate parent's value in child's input onChange event
example:
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = { inputValue: undefined }
}
const handleChange = (e) =>{
if(e.target)
this.setState({...this.state, inputValue: e.target.value});
}
render(){
return (
<div className="detailsPage-panel-right">
{
this.state.activeFields?.fields?.map(field => {
const config =
this.config.fields.find(fieldConfig =>
fieldConfig.key === field.key)
const inputConfig = {
type: config?.dataType.type,
id: config?.key,
label: config?.displayName,
required: false,
autofocus: false,
value: field.value
};
const inputBindings: ITextInputBindings = {}
return (
<div key=
{`${this.state.activeFields.key}-${field.key}`}
>
<TextInput
config={inputConfig}
bindings={inputBindings}
onChange={handleChange}>
</TextInput>
</div>
)
})
}
</div>
)
}
}
// TextInput
class TextInput extends Component<ITextInputConfig,ITextInputBindings> {
constructor(props) {
super(props);
this.state = { ...this.props }
}
const handleChange = (e) => {
this.props.onChange(e);
}
render() {
return (
<div className="textInput">
<Form.Group className="mb-3 textInput-group">
<Form.Label htmlFor={this.config.id}>{this.config.label} </Form.Label>
<Form.Control
type={this.config.type}
placeholder={this.config.placeholder}
required={this.config.required}
id={this.config.id}
autoFocus={this.config.autofocus}
defaultValue={this.config.value}
onChange={handleChange}/>
</Form.Group>
</div>
);
}
}
Code snippet example
a example that how child mutate parent's value, and how does the component destroyed when key changes. (written by functional component)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
function App () {
const [keys, setKeys] = React.useState([1, 2]);
const [inputValue, setInputValue] = React.useState(``);
const [inputValue2, setInputValue2] = React.useState(``);
const handleKeys = () =>{
let temp = [...keys];
temp[0] = temp[0] + 2;
temp[1] = temp[1] + 2;
setKeys([...temp])
}
return <div>
<div><button>Click this still remain the changes you had made</button></div>
<div><button onClick={handleKeys}>Click this to change keys, and will refresh the 'Number' prefix input component</button></div>
<br />
{
keys.map((key)=>{
if (key % 2 === 0) {
return <div key={key}>Number {key}: <Child setInputValue={setInputValue2}></Child></div>
}
else {
return <div key={key}>Number {key}: <Child setInputValue={setInputValue}></Child></div>
}
})
}
<br />
<div>child components that do not have key</div>
<div>First Child's Input: <Child setInputValue={setInputValue}></Child></div>
<div>Second Child's Input: <Child setInputValue={setInputValue2}></Child></div>
<br />
<div>inputValue(in parent from first child): {inputValue}</div>
<div>inputValue2(in parent from second child): {inputValue2}</div>
</div>
}
function Child ({ setInputValue }) {
const handleChange = (e) => {
if(setInputValue)
setInputValue(e.target.value);
}
return <input onChange={handleChange}></input>
}
</script>
<script type="text/babel">
ReactDOM.render(
<App></App>
, document.getElementById("root"));
</script>
Dynamically mutate and save input value by state
I guess you need save value dynamically by this.state.activeFields?.fields.
Create a state object for recording your active input value.
And add a handleChange function which can change value by e.target.id
// In your TextInput's parent
constructor(props) {
super(props);
this.state = { inputValues: {} }
}
const handleChange = (e)=>{
const changeField = this.state.activeFields?.fields.find(x=>x.key === e.target.key);
if(changeField) {
this.setState({...this.state.inputValues, changeField.key: e.target.value})
}
}
this.state.activeFields?.fields?.map( (field) => {
return (
<TextInput
config={inputConfig}
bindings={inputBindings}
// add onChange event
onChange={handleChange}
>
</TextInput>
)
})
more refernece:
Lifting State Up
Other
According to react-bootstrap's Form.Control API doc, should use the value intead of defaultValue

How to get search value into api string

So I have stated learning react and tried to make a project that renders data from an api. I have 2 components, a Search bar and a component that renders the weather.
What I'm trying to do is to get the value from the search bar and concatenate into the api string. I have tried doing this by settings a prop but I am unable accessing it in the weather component.
My questions is: How can I access the search value in a different component
/components/Search.js
class Search extends Component {
state = {
title: '',
};
onChange = (e) => {
this.setState({ title: e.target.value });
};
onSubmit = (e) => {
// e.preventDefault();
this.props.searchValue(this.state.title);
this.setState({ title: '' });
};
render() {
return (
<Mui.Container>
<CssBaseline />
<form
onSubmit={this.onSubmit}
autoComplete='off'
>
<Mui.Input
placeholder='enter place'
value={this.state.title}
onChange={this.onChange}
/>
</form>
</Mui.Container>
);
}
}
Search.propTypes = {
searchValue: PropTypes.func,
};
/components/Weather.js
class Weather extends Component {
state = {
videos: [],
};
componentDidMount = () => {
axios
.get(
'<weather api here>'
)
.then((res) => {
const videosArr = res.data.videos.map((item) => {
return item;
});
this.setState({ videos: videosArr });
});
};
render() {
return (
{this.state.videos.map((video, index) => {
return (
<React.Fragment key={video.id}>
<Mui.Grid item>
<Mui.Paper>
<div>
<img src='./190x107.png' alt='placeholder' />
<div>
<a href={video.url}>{video.title}</a>
</div>
</div>
</Mui.Paper>
</Mui.Grid>
</React.Fragment>
);
})}
);
}
}
I assume there will be a parent component for <Weather /> and <Search />?
If yes then the parent component can have state, and you pass your setState function into the search component, and then you pass the current state into the weather component.
<Weather searchValue="current state from parent" />
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
videos: []
};
}
componentDidMount = () => {
axios
.get(`URL?${this.props.searchValue}`)
.then((res) => {
const videosArr = res.data.videos.map((item) => {
return item;
});
this.setState({ videos: videosArr });
});
};
render() {
return (
{this.state.videos.map((video, index) => {
return (
<React.Fragment key={video.id}>
<Mui.Grid item>
<Mui.Paper>
<div>
<img src='./190x107.png' alt='placeholder' />
<div>
<a href={video.url}>{video.title}</a>
</div>
</div>
</Mui.Paper>
</Mui.Grid>
</React.Fragment>
);
})}
);
}
}

i cant transfer data from react child to parent ang during click on child set value of input in parent

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>

How to Update This Reactjs Select

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.

React: Add/Change text input base on a selected option

I am trying to display a new text input based on the selected option. I am able to do that as below but the old value entered is always present no matter what I change the new select option to.
What might be a better way to achieve this? Appreciate any suggestions.
class loadComponent extends React.Component {
static propTypes = {
......
};
static defaultProps = {
....
};
constructor() {
super();
this.state = {
value: ""
};
}
state = {
...
};
reset = (selected) => {
this.setState({
selectedInputName: selected.target[selected.target.selectedIndex].text,
selectedInputId: selected.target.value
});
};
makeTextInput = () => {
return (
<TextInput
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
value={this.state.value}
/>
);
};
render() {
let newInputText = '';
if (this.state.selectedInputId !== '') {
newInputText = this.makeTextInput();
}
return (
<Select
label="What would you like to search with?"
options={this.props.searchOptions}
onChange={selected => this.reset(selected)}
/>
<div className="search margin_bottom_large">
{newInputText}
);
makeTextInput function creates a new object, but from react's perspective it's the same component because react distinguishes them by looking at their type and key. To make react recreate an element, you have to change one of those values.
This code changes type of NewInputText element each time it renders (because NewInputText always refers to a new function):
reset = (selected) => {
this.setState({
selectedInputName: selected.target[selected.target.selectedIndex].text,
selectedInputId: selected.target.value
});
};
makeTextInput = () => {
return (
<TextInput
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
/>
);
};
render() {
let NewInputText = () => '';
if (this.state.selectedInputId !== '') {
NewInputText = () => this.makeTextInput();
}
return (
<Select
label="What would you like to search with?"
options={this.props.searchOptions}
onChange={selected => this.reset(selected)}
/>
<div className="search margin_bottom_large">
<NewInputText />
);
This code assigns different key to TextInput each time:
reset = (selected) => {
this.setState({
selectedInputName: selected.target[selected.target.selectedIndex].text,
selectedInputId: selected.target.value
});
};
makeTextInput = () => {
return (
<TextInput
key={Math.random()}
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
/>
);
};
render() {
let newInputText = '';
if (this.state.selectedInputId !== '') {
newInputText = this.makeTextInput();
}
return (
<Select
label="What would you like to search with?"
options={this.props.searchOptions}
onChange={selected => this.reset(selected)}
/>
<div className="search margin_bottom_large">
{newInputText}
);
Is there a better way to do this?
I think using the controlled component design pattern would be ideal in this situation.
class SomeInput extends Component {
constructor() {
super();
this.state = {
value: "" //Keep value state here
};
}
render() {
/* Doing something like the following will allow you to clear
the input value simply by doing the following..
this.setState({ value: '' });
*/
return (
<Input
type="text"
onChange={e => this.setState({ value: e.target.value })} // set value state to entered text
value={this.state.value} // set value of input to value piece of state
/>
);
}
}
This will give you full access to the current value of the input, thereby allowing you to set it to anything or clear it at anytime or for any event simply by doing the following this.setState({ value: '' }).
Don't know the rest of your code which could be handy but you can try:
makeTextInput = () => (
<TextInput
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
/>
);
change = (event) => {
this.setState({
selectedInputName: event.target.value
});
}
render() {
return (
<Select
label="What would you like to search with?"
options={this.props.searchOptions}
onChange={this.change}
/>
<div className="search margin_bottom_large">
{this.makeTextInput()}
);
What you need to do is only setState properly. Each time you change a state the component will be re-rendered which means that the makeTextInput method will be triggered.
EDIT:
by the way, it is good idea to use getter for returning component in render method, in this case:
get textInput() {
return (
<TextInput
label={this.state.selectedInputName}
placeholder={`Please enter ${this.state.selectedInputName} here!`}
onBlur={event => this.setState({[this.state.selectedInputId]: event.target.value})}
showClear
/>
);
}
and then in render method, just use {this.textInput}

Categories

Resources