React - selected value from list is undefined - reactjs

I have a React component SelectObj which gets data from my api and passes it to a component DynamicSelect. The DynamicSelect creates a selection list. When the user selects the option, the selected value should be returned to SelectObj.
DynamicSelect.js:
export class DynamicSelect extends Component {
constructor(props) {
super(props);
}
handleChange = event => {
let selectedValue = event.value;
this.props.onSelectChange(selectedValue);
};
render() {
let arrayOfData = this.props.arrayOfData;
let options = arrayOfData.map(data => (
<option key={data.id} value={data.objective_name}>
{data.objective_name}
</option>
));
//console.log(options);
return (
<select
name="customSearch"
className="custom-search-select"
onChange={this.handleChange}
>
<option>Select item</option>
{options}
{console.log(options)}
</select>
);
}
}
export default DynamicSelect;
SelectObj.js
import { getObjectives } from "../../actions/assessments";
import { DynamicSelect } from "./DynamicSelect";
export class SelectObj extends Component {
static PropTypes = {
objectives: PropTypes.array.isRequired,
getObjectives: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.state = {
selectedValue: "Nothing selected"
};
}
handleSelectChange = selectedValue => {
this.setState({
selectedValue: selectedValue
});
};
componentDidMount() {
this.props.getObjectives();
}
render() {
let arrayOfData = this.props.objectives;
return (
<Fragment>
<DynamicSelect
arrayOfData={arrayOfData}
onSelectChange={this.handleSelectChange}
/>
{console.log(this.state.selectedValue)}
<div>Selected Value: {this.state.selectedValue}</div>
</Fragment>
);
}
}
const mapStateToProps = state => ({
objectives: state.objectives.objectives
});
export default connect(mapStateToProps, { getObjectives })(SelectObj);
The console.log in DynamicSelect prints out the options that I expect, whereas the console.log in SelectObj is undefined. How do I get this selected value from DynamicSelect?
I thought I maybe have to bind my handleChange with this.handleChange = this.handleChange.bind.(this) but that didn't fix this problem (maybe I still should be binding the handleChange?).

<DynamicSelect
arrayOfData={arrayOfData}
onSelectChange={(e) => this.handleSelectChange(e)}
/>
Try to pass your function like this and see whether the result is getting updated.

Related

React not rerendering the component when prop changes

I have a picklist component that render some children based on the selected option. The problem comes when I render the same component in two options but with different props, because in that case, the component is not rerendered with the new props.
Let me clarify the problem: I have a picklist, I select option "A", then a text component is rendered below the picklist, I type "error" in that text field, then select option "B" in the picklist, then the other text field component disappear and another text field component is rendered just below the picklist. The last component should have been rendered empty, but the problem is that it contains the word "error".
Here's a minimized version of the code reproducing the error:
import React from "react";
class TextField extends React.Component {
constructor(props) {
super(props);
this.state = { value: props.value };
}
_onChange = (event) => {
this.setState({ value: event.currentTarget.value });
};
_handleBlur = (event) => {
this.setState({ value: event.currentTarget.value.trim() });
};
render() {
const { value } = this.state;
return (
<div>
<label>TextField</label>
<input
type="text"
onChange={this._onChange}
onBlur={this._handleBlur}
value={value}
/>
</div>
);
}
}
class Picklist extends React.Component {
constructor(props) {
super(props);
this.state = { selectedOption: "" };
this.options = ["Blank", "A", "B"];
}
// eslint-disable-next-line consistent-return
_handleSelectorCallback = (newOption) => {
this.setState({ selectedOption: newOption.currentTarget.value });
};
_renderChildren(selectedOption) {
if (!selectedOption) {
return null;
}
if (selectedOption === "A") {
return <TextField value="optionA"/>;
}
if (selectedOption === "B") {
return <TextField value="optionB"/>;
}
}
_renderOptions() {
const { selectedOption } = this.state;
return this.options.map((option) => (
<option value={option} selected={selectedOption === option}>
{option}
</option>
));
}
render() {
const { selectedOption } = this.state;
return (
<React.Fragment>
<div>
<label>Demo of the Error!</label>
<select
onChange={this._handleSelectorCallback}
value={selectedOption}
>
{this._renderOptions()}
</select>
</div>
{this._renderChildren(selectedOption)}
</React.Fragment>
);
}
}
export default Picklist;
Ignore how bad the component is written, is just to reproduce the error I'm having. Any idea why the component is not being rerendered with a new value?
I think what's happening is, when you switch from one <TextField> to the other, React is trying to be efficient by just passing different props to the same instance. You can tell React they are different and should be rerendered by adding a key:
_renderChildren(selectedOption) {
if (!selectedOption) {
return null;
}
if (selectedOption === "A") {
return <TextField key="A" value="optionA" />;
}
if (selectedOption === "B") {
return <TextField key="B" value="optionB" />;
}
}
alternatively, you could make your TextField a controlled component, which means it has no internal state, and the value/onChange fn are passed in as props. I edited your codesandbox to follow this pattern: https://codesandbox.io/s/empty-wind-43jq4?file=/src/App.js
I tried your sandbox. Your TextField component does not get unmounted so it's constructor gets called just once. Every change you make that has to do with that component, goes to it's componentDidUpdate hook.
So this is what you are missing:
componentDidUpdate() {
if (this.state.value !== this.props.value) {
this.setState({ value: this.props.value });
}
}

Reset react-select dropdown when new props passed in

I would like to clear the value in the dropdown on getting new props. Currently, the previous value remains visible even though if I expand the dropdown, I can see new values.
import React from 'react';
import Select from 'react-select';
import axios from 'axios';
class KeydateDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
optionList: []
};
};
componentDidUpdate(prevProps) {
let vesselname = this.props.vesselname;
if (prevProps.vesselname !== vesselname) {
let keydateList = [];
this.setState({
optionList: keydateList
});
axios.get('list-keydates', {
params: {
vesselname: vesselname
}
})
.then((response) => {
let data = response.data['intervention'];
data.forEach((element) => {
keydateList.push({ value: element, label: element });
});
this.setState({ optionList: keydateList });
})
}
}
render() {
return (
<Select
isDisabled={this.props.isDisabled}
onChange={this.props.handleKeydateChange}
options={this.state.optionList}
className={styles.dropdown}
/>
);
}
}
export default KeydateDropdown;
I have confirmed that the first setState() inside componentDidUpdate which clears optionList calls render()
Edit: Here's the codesandbox
You forget to explicitly set a value on the Select component. You have to pass this.state.selectedKeydate to KeydateDropdown in MainInputBar and then pass that value to the Select component:
In MainInputBar.jsx:
keydateDropdown = (
<KeydateDropdown
vesselname={this.state.selectedVessel.value}
selectedKeydate={this.state.selectedKeydate}
handleKeydateChange={this.handleKeydateChange}
value={this.state.selectedKeydate}
/>
);
In KeyStateDropdown.jsx:
<Select
isDisabled={this.props.isDisabled}
onChange={this.props.handleKeydateChange}
options={this.state.optionList}
className={styles.dropdown}
value={this.props.value}
/>
I forked your sandbox and fixed it here: https://codesandbox.io/s/happy-einstein-55klv

React trying to make a list of dynamic inputs

I have built this site
https://supsurvey.herokuapp.com/surveycreate/
now I am trying to move the fronted to React so I can learn React in the process.
with vanila js it was much easier to create elements dynamically.
I just did createElement and after that when I clicked "submit" button
I loop throw all the elements of Options and take each target.value input.
so I loop only 1 time in the end when I click Submit and that's it I have now a list of all the inputs.
in react every change in each input field calls the "OnChange" method and bubbling the e.targe.value to the parent and in the parent I have to copy the current array of the options and rewrite it every change in every field.
is there other way? because it seems crazy to work like that.
Options.jsx
```import React, { Component } from "react";
class Option extends Component {
constructor(props) {
super(props);
this.state = { inputValue: "", index: props.index };
}
myChangeHandler = event => {
this.setState({ inputValue: event.target.value });
this.props.onChange(this.state.index, event.target.value);
};
render() {
return (
<input
className="survey-answer-group"
type="text"
placeholder="Add Option..."
onChange={this.myChangeHandler}
/>
);
}
}
export default Option;
______________________________________________________________________________
Options.jsx````
```import React, { Component } from "react";
import Option from "./option";
class Options extends Component {
render() {
console.log(this.props);
return <div>{this.createOptions()}</div>;
}
createOptions = () => {
let options = [];
for (let index = 0; index < this.props.numOfOptions; index++) {
options.push(
<Option key={index} onChange={this.props.onChange} index={index} />
);
}
return options;
};
}
export default Options;```
______________________________________________________________________________
App.jsx
```import React from "react";
import OptionList from "./components/Options";
import AddButton from "./components/add-button";
import "./App.css";
class App extends React.Component {
state = {
numOfOptions: 2,
options: [{ id: 0, value: "" }, { id: 1, value: "" }]
};
handleChange = (index, value) => {
const options = [...this.state.options];
console.log("from App", value);
options[index].value = value;
this.setState({
options: options
});
console.log(this.state);
};
addOption = () => {
const options = [...this.state.options];
options.push({ id: this.state.numOfOptions + 1, value: "" });
this.setState({
numOfOptions: this.state.numOfOptions + 1,
options: options
});
};
submitButton = () => {};
render() {
return (
<div className="poll-create-grid">
<div id="poll-create-options">
<OptionList
onChange={this.handleChange}
numOfOptions={this.state.numOfOptions}
/>
</div>
<button
className="surveyCreate-main-btn-group"
onClick={this.addOption}
>
Add
</button>
<button
className="surveyCreate-main-btn-group"
onClick={this.submitButton}
>
Submit
</button>
</div>
);
}
}
export default App;
```
So firstly,
The issue is with the way your OptionList component is defined.
Would be nice to pass in the options from the state into the component rather than the number of options
<OptionList
onChange={this.handleChange}
options={this.state.options}
/>
The you basically just render the options in the OptionsList component (I'm assuming it's same as the Options one here
class Options extends Component {
...
render() {
return
(<div>{Array.isArray(this.props.options) &&
this.props.options.map((option) => <Option
key={option.id}
index={option.id}
onChange={this.props.onChange}
value={option.value}
/>)}
</div>);
}
...
}
You would want to use the value in the Option component as well.
this.props.onChange(this.state.index, event.target.value); No need using the state here to be honest
this.props.onChange(this.props.index, event.target.value); is fine

compare the first state to the last state reactjs

I am trying to make an edit page. I am update the state for any changes made. I want to compare the initial state with the last state on the last save. but I can not control the first state.
export default class extends Component {
constructor(props){
super(props);
this.changeDetails = this.changeDetails.bind(this);
this.state = {
driver: this.props.driver
}
}
changeDetails = value => {
this.setState({
driver:value
})
}
onRegister = () => {
//I want to make a comparison here.
}
render() {
const {driver} = this.state
return (
<div>
<EditView driver={driver} changeDetails={this.changeDetails}/>
</div>
);
}
}
EditView.js
export default class extends Component {
render() {
const { driver} = this.props;
const changeDetails = event => {
driver['fname] = event.target.value;
this.props.changeDetails(driver);
};
return (
<div>
<Input
value={driver.fname}
onChange={event => changeDetails(event)}
/>
</div>
);
}
}
Do not mutate driver itself directly. Use something like this:
const changeDetails = event =>
this.props.changeDetails( { ...driver, fname: event.target.value } );

Create a custom radio button using React JS

I'm trying to create a custom radio button. The issue that i'm facing is that i'm unable to uncheck the radio button when another radio button is clicked. Currently it behaves like a checkbox.
import {React, ReactDOM} from '../../shared/lib/react';
export default class RadioButton extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedRadioBtn: false
};
this.toggleRadioBtn = this.toggleRadioBtn.bind(this);
};
toggleRadioBtn(){
this.setState({checkedRadioBtn: !this.state.checkedRadioBtn});
};
render() {
return (
<div className="radio-btn-group">
<div onClick={this.toggleRadioBtn} className={this.state.checkedRadioBtn ? "radiobtn checked" : "radiobtn unchecked"} data-value={this.props.value}></div>
<label>{this.props.text}</label>
</div>
);
}
};
You need to have container for group of radio buttons. That container will maintain the state for all the radio buttons and handle check/uncheck for each option. Here is the sample code for that,
import React from 'react'
import ReactDOM from 'react-dom'
class RadioBtn extends React.Component{
constructor(props) {
super(props);
}
handleClick(){
this.props.handler(this.props.index);
}
render() {
return (
<div className="radio-btn-group" onClick={this.handleClick.bind(this)}>
<div className={this.props.isChecked ? "radiobtn checked" : "radiobtn unchecked"} data-value={this.props.value}></div>
<label>{this.props.text}</label>
</div>
);
}
}
class RadioGrp extends React.Component{
constructor() {
super();
this.state = {
selectedIndex: null,
selectedValue: null,
options: ["option 0","option 1","option 2","option 3"]
};
}
toggleRadioBtn(index){
this.setState({
selectedIndex: index,
selectedValue: this.state.options[index],
options: this.state.options
});
}
render() {
const { options } = this.state;
const allOptions = options.map((option, i) => {
return <RadioBtn key={i} isChecked={(this.state.selectedIndex == i)} text={option} value={option} index={i} handler={this.toggleRadioBtn.bind(this)} />
});
return (
<div>{allOptions}</div>
);
}
}
var app = document.getElementById('app');
ReactDOM.render(<RadioGrp />, app);
Since you're using a div for a custom checkbox that doesn't behave like a normal checkbox you should be checking value against the selected value.
toggleRadioBtn(e){
this.setState({checkedRadioBtn: e.target.value});
};
Another question that I have is that you are assuming a single checkbox here so I have to assume you have a calling component that returns multiple instances. If that is the case then you need to pass your onClick down so you can pass the value back up to the parent. Then pass the selected value back down.
This is an example that I have in my application.
var languges = this.props.languages;
var languageToSelect = this.state.selectedLanguage;
var handleChange = this.handleChange;
var languageRows = Object.keys(languges).map(function(key) {
var language = languges[key];
return <LanguageBlock
key={ key }
language={ language }
languageCode={ key }
checked={ languageToSelect === key }
handleChange={ handleChange }
/>;
});
In my use case I have multiple languages and onChange I pass the selected language back up then on rerender the selected language will be updated so the radio options will reflect the change.
handleChange: function handleChange(event) {
this.setState({ selectedLanguage: event.target.value });
},
The handle change just sets state for the new value. The language block itself is just a simple component so no need to make it a class/component.
const LanguageBlock = ({ checked, language, languageCode, handleChange }) => {
return (
<div className="truncification">
<input type="radio" name="lang" id={ 'lang_' + languageCode }
value={ languageCode } checked={ checked } onChange={ (evt) => { handleChange(evt); } } />
<label htmlFor={ 'lang_' + languageCode }>{ language }</label>
</div>
);
};

Resources