React: component only rendering new values on second onClick of button - javascript

I making a react app and I have a parent component Search with child components Input and Result. Input has a drop down menu which passes a value, genreValue, to Search, through a callback function when a button is clicked. Search then makes an api call, which works fine.
My problem is it takes two clicks of the button for the new API data to render. Looking at other SO questions I suspect I need to pass genreValue as an argument to the cb function, or my onClick is only initialising, rather than invoking it on the first click.
It's a pretty simple app so I wouldn't think Flux etc would be needed. My console logs seem to show the value being changed in the Search and Input components.
So what am I doing wrong?
Search.js
let Search = React.createClass ({
getInitialState(){
return {
movies: ['Men In Black'],
genreValue: '12'
};
},
componentDidMount(){
this.getMovies()
},
getMovies(){
let genre = this.state.genreValue;
let url = `http://api.themoviedb.org/3/discover/movie?${key}&with_genres=${genre}`;
Request.get(url).then((response) => {
console.log('response.body.results', response.body.results)
this.setState({
movies: response.body.results.map(function(movie){
return movie.title
})
});
});
},
handleGenre(newGenre) {
this.setState({ genreValue: newGenre })
return this.getMovies();
},
render(){
console.log(this.state.movies)
console.log('genreValue state', this.state.genreValue)
return (
<div>
<Input genre={this.state.genreValue} onGenreChanged={this.handleGenre}/>
<ul>
{this.state.movies.map( function(movie){
return <Results key={movie.id} data={movie}/>;
})}
</ul>
</div>
);
}
});
export default Search;
Input.js
let Input = React.createClass ({
selectHandler(){
return this.props.onGenreChanged(this.refs.genre.value);
},
render() {
console.log('genreValue prop', this.props.genre);
console.log('refs', this.refs.genre)
return <div>
<select ref="genre">
<option value="28">Action</option>
<option value="12">Adventure</option>
<option value="16">Animation</option>
<option value="35">Comedy</option>
<option value="80">Crime</option>
<option value="99">Documentary</option>
<option value="18">Drama</option>
<option value="10751">Family</option>
<option value="14">Fantasy</option>
<option value="10769">Non-english</option>
<option value="36">History</option>
</select>
<button onClick={this.selectHandler} value="Go">Go</button>
</div>
}
});
export default Input;

In the handleGenre function, state may not have updated when this.getMovies is called. You could change it to the following:
handleGenre(newGenre) {
this.setState({ genreValue: newGenre }, function() {
return this.getMovies();
});
},
Or, probably better practice would be to call this.getMovies in a componentDidUpdate lifecycle function if genreValue has changed:
componentDidUpdate: function(prevProps, prevState) {
if (prevState.genreValue !== this.state.genreValue) {
this.getMovies();
}
}

Related

Dropdown state in React-redux

Iam trying to create a dropdown component and would like to use that selected option through out my app.
The thought is when a user select a Dropdown value then, its state got save in Redux reducer then to use that value for other action. But being a beginner Iam stuck on implementation part.
Note: The dropdown wouldnt have a submit button, just the action of selecting the drop down option.
My code until this stage looks like this:
RoleDropdown.js
class RoleDropdown extends Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
...
...
render() {
return (
<div>
<select
onChange={() => this.props.selectedRoleAction()}
name="roles" className="form-control">
<option>Select a Role</option>
<option value="ABC" >ABC</option>
<option value="DEF" >DEF</option>
<option value="GHI" >GHI</option>
</select>
<p>role is: {this.props.activeRole.value}</p> //No value output here
</div>
)
}
SelectedRoleAction.js
const selectedRoleAction = (role) => {
const [value, setValue] = useState("")
setValue({ value: role.target.value })
console.log("event from drop down is " + role) //I cant see any logged value as well
return {
type: "ROLE_SELECTED",
payload: role,
}
};
Where Am I doing wrong? Does the "setValue" can be used in action reducers?
An action creator does not hold any local state. It is just a function that maps from some arguments to a Redux action which is an object with properties type and payload.
This is an action creator function:
const selectedRoleAction = (role) => {
return {
type: "ROLE_SELECTED",
payload: role,
}
};
In which case your component would call:
onChange={(e) => this.props.selectedRoleAction(e.target.value)}
You are trying to map from the event to the value in the action creator rather than in the component. This is unusual and I would recommend the above approach, but it's doable.
const selectedRoleEventHandler = (event) => {
return {
type: "ROLE_SELECTED",
payload: event.target.value,
}
};
In which case your component would call:
onChange={(e) => this.props.selectedRoleEventHandler(e)}
or just:
onChange={this.props.selectedRoleEventHandler}
Right now you are calling the function with no arguments this.props.selectedRoleAction() which will not work.
That just creates the action. I'm assuming that you are using the connect higher-order component so that calling this.props.selectedRoleAction will dispatch it to Redux.
The actual state is set through your Redux reducer function.
If the value is stored and updated through Redux then it should not also be in the component state.
You are dealing with a controlled component so you'll want to set the value property on your select.
I am disabling "Select a Role" and also giving it a value of the empty string "". I am using that value as a fallback if this.props.activeRole is not set.
<select
name="roles"
className="form-control"
value={this.props.selectedRole || ""}
onChange={(e) => this.props.selectedRoleAction(e.target.value)}
>
<option value="" disabled>Select a Role</option>
<option value="ABC">ABC</option>
<option value="DEF">DEF</option>
<option value="GHI">GHI</option>
</select>

filter an array based on selected dropdown item -redux-react

I have a dropdownlist. Based on selected dropdown item i have to display currency. Here is the data structure : [{mruCode: "1700", country: "US", countryText: "United States", division: "WORLDWIDE_INDUSTRIAL",currency:"USD"}....]. I mapped this data to my select item. Now based on selected item (ex: division: "WorldWIDE_Industrial") i have to show currency(ex. "USD") in a label. If dropdown value change then onChange event will fire and display the corresponding currency.
I have created the action and reducer for this and filter the list based onChange action fire. I am not able to understand how to handle the change event. Please help on this.
component code:
class LocationList extends React.Component{
constructor(props){
super(props);
this.state={
isLoading:false,
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.props.loadData();
}
handleChange(mruCode){
this.props.currencyLists(mruCode);
}
render(){
const{location}=this.props;
console.log(this.props.currList);
const _labels = store.getLabels();
return(<div>
<span className="btnElement_spacing">You are viewing pricing for </span>
//here is the problem start i assume
<select id="toggleLocationName">
{location.map((item,index) =>
<option key={index} onChange={()=>this.handleChange(item.mruCode)}> value={index}>{_labels[item.division]}</option>
)}
</select>
<span className="btnElement_spacing"> in </span>
{this.props.currList.map((item,index)=><label id="toggle-currency" key ={index}>{item.currency}</label>)}
</div>
);
}
}
const mapStateToProps = state => {
return {
location: state.locationRed.location,
currList: state.locationRed.currList
}
}
const mapDispatchToProps = dispatch => {
return {
loadData:()=>{dispatch(loadData())},
currencyLists:(mruCode)=>{dispatch(currencyLists(mruCode))}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(LocationList);
action code:
export const currencyLists = mruCode =>({
type: CURRENCY_LIST,
payload: mruCode
});
reducer code:
case 'CURRENCY_LIST':
let newState = Object.assign({}, state)
let newCurrList = newState.location.filter((el) => el.mruCode === action.mruCode)
return Object.assign({}, newState, {
currList: newCurrList
});
i am trying to filter out the main list based on mruCode with action id for onChange and saved the result into a currList. mapped to display the currency. But i am failed. currList initially showing empty. onChange not triggered. How to make action fire to show the currency
Onchange should be called on select tag(not on option tag). Below code should work.
<select id="toggleLocationName" onChange={this.handleChange}>
{location.map((item, index) =>
<option key={index} value={item.mruCode}>{_labels[item.division]}</option>
)}
</select>
handleChange(e){
this.props.currencyLists(e.target.value);
}

React Component - reload data within multiple times

Imagine a simple React component with <select> element that allows to choose a city based on country. For example
<MyCitySelectComponent
country={ 'France' }
city={ 'Paris' }
onChange={ someFunction }
/>
When mounted, it should load list of available Cities (based on Country) and render <select>.
When city property is changed - it should modify <select> input value and trigger onChange event.
When country property is changed (from parent component) - it should reload list of available cities from a remote server and trigger the same onChange event.
I managed to implement first two, here is simplified code:
class MyCitySelectComponent extends Component {
constructor(props) {
super(...props);
this.state = {
cities: null,
city: props.city,
country: props.country
};
}
onCityChange( e ) {
this.setState({
city: e.target.value
});
this.props.onChange( e.target.value );
}
loadCities() {
fetch({
path: '/get/cities?country=' + this.state.country,
}).then( cities => {
this.setState({
cities: cities
});
});
}
componentDidMount() {
this.loadCities();
}
render() {
if ( !this.state.cities ) {
// not loaded yet
return null;
}
return (
<select>
{ this.state.cities.map( ( name, index ) =>
<option
value={ name }
onChange={ this.onCityChange }
selected={ name === this.state.city }
/>
) }
</select>
)
}
}
But I'm having trouble reloading cities when country is changed dynamically from parent component. I tried using shouldComponentUpdate, but all I get is infinite loops.
Is there any pattern for such type of component?
Thank you.
Fetching new data based on prop changes should be handled in componentDidUpdate or getDerivedStateFromProps. Have a look at the docs for an example: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data-when-props-change
Note that componentWillReceiveProps is deprecated!

showing select on check reactjs

I am trying to get a select to show/hide on check but the select just renders and does not disappear nor reappear. I am fairly new to react, so I am sure I am doing something wrong.
export default class TreeTest extends Component {
constructor() {
super();
this.state = {
checked: [
'/grantSettingsPermissions/Admin',
'/grantSettingsPermissions/ContentGroups/AddLocations',
],
expanded: [
'/grantSettingsPermissions',
'/grantSettingsPermissions/ContentGroups',
],
};
this.onCheck = this.onCheck.bind(this);
this.onExpand = this.onExpand.bind(this);
this.handleChange = this.handleChange.bind(this);
}
onCheck(checked) {
console.log(checked);
this.setState({
checked,
});
}
onExpand(expanded) {
this.setState({
expanded,
});
}
handleChange() {
this.setState({
checked: !this.state.checked,
});
}
render() {
const { checked, expanded } = this.state;
const content = this.state.checked
? <select>
<option value="test1">test1</option>
<option value="test2">test2</option>
</select>
: null;
return (
<div>
{ content }
<CheckboxTree
checked={checked}
expanded={expanded}
nodes={nodes}
onCheck={this.onCheck}
onExpand={this.onExpand}
expandDisabled={true}
onChange={ this.handleChange }
/>
</div>
);
}
}
I have a feeling I just need to add stuff to the onCheck function, but I am not entirely sure. Any help would be awesome!
Your condition should be:
const content = this.state.checked.length === 0
? <select>
<option value="test1">test1</option>
<option value="test2">test2</option>
</select>
: null;
I'm not sure what your component CheckboxTree does, but here is some info that applies to regular input controls:
Your event handler onChecked is expecting checked to be the value of your checkbox, but in fact it will be an event object. So you need to get the value from the event object and set the state with that:
onCheck(e) {
console.log(e);
let checked = {checked: e.target.value}
this.setState({
checked,
});
}
UPDATE
I see from the documentation that they are doing it the same way, so it should work, because your code is equivalent to this:
onCheck={checked => this.setState({ checked })}
onExpand={expanded => this.setState({ expanded })}

How to two-way bind select element with prop in React

Whats the approved way to create select element in react, which is two way bound with the prop of selection containing component? The default selection should be the present attribute of the prop (may be generated, because the value is arbitrary, and on selection the prop attribute should reflect the selection. Also, it should be possible to write the value directly to the selection field.
There isn't an "approved" way as such, but you should note a couple of things:
The change event is triggered on the element, not the element.
Controlled and uncontrolled components defaultValue are set differently.
This is a generic example of a controlled dropdown menu
var MyDropdown = React.createClass({
getInitialState: function() {
return {
value: 'select'
}
},
change: function(event){
this.setState({value: event.target.value});
},
render: function(){
return(
<div>
<select id="fruit" onChange={this.change} value={this.state.value}>
<option value="select">Select</option>
<option value="Apples">Apples</option>
<option value="Mangoes">Mangoes</option>
</select>
<p></p>
<p>{this.state.value}</p>
</div>
);
}
});
React.render(<MyDropdown />, document.body);
and here's a working demo.
I add the options to an array on state and then map overthem,
try this code
import React, { Component } from 'react'
class SelectExample extends Component {
constructor() {
super()
this.state = {
options: ['One', 'Tow', 'Three'],
selectedOption: 'One',
}
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value,
})
}
render() {
return (
<select name='selectedOption' onChange={this.handleChange}>
{this.state.options.map(i => i == this.state.selectedOption ? (
<option value={i} selected>
{i}
</option>
) : (<option value={i}>{i}</option>) )}
</select>
)
}
}

Categories