What I want to do
When state changes, I would like to fetch data from API and set it to state.
Problem
When I load a page, componentDidUpdate is automatically emitted although anything doesn't change and this is initial render.
I am beginner to React.
I would like to get data from API when I input some data and state changes.
However, when I load a page I got an error that cannot property map of undefined.
Apparently, componentDidUpdate is called for some reasons when the page is first rendered.
In fact, I compared prevState.line to this.state.line, and then I found prevState.line undefined.
I mean, componentDidUpdate is emitted before "" that is an initial value of this.state.line is set to this.state.line.
I just would like to prevent componentDidUpdate from being initially called.
If you know a way to do that, I would like you to tell me that and why it is happening.
Thank you very much.
=========== ============ ============
My code is like this.
class User_Add_PickUp_Form extends Component {
constructor(props) {
super(props);
this.state = {
owner: this.props.loginUser,
lines: '',
stations: '',
textInput: '',
allLines: '',
allStations: '',
};
this.handleChange = this.handleChange.bind(this);
}
spreadAndSetState = (key, value) => {
this.setState({ ...this.state, [key]:value });
};
componentDidMount() {
console.log('Original is ' + this.state.lines)
axios
.get('http://express.heartrails.com/api/json?method=getLines&prefecture=TOKYO')
.then((res) => {
console.log(res.data)
this.setState({ allLines: res.data.response.line })
})
.catch((err) => console.log(err));
}
componentDidUpdate(prevState){
console.log(prevState)
console.log(this.state)
if(prevState.lines != this.state.lines){
axios.get('http://express.heartrails.com/api/json?method=getStations&line=' + this.state.lines)
.then((res)=> {
//
// res.data.response.stations is going to be an array
//
res.data.response.station.map((station) => {
this.spreadAndSetState(this.state.allStations, station.name)
})
})
}
}
handleChange = (e) => {
const name = e.target.name;
const value = e.target.value;
this.spreadAndSetState(name,value)
};
render() {
const { owner, lines, stations, textInput, allLines, allStations } = this.state;
if (allLines === '') {
return <CircularProgress />;
} else {
return (
<div>
<h2>Add Pickup Places</h2>
<select name="lines" onChange={this.handleChange}>
<option value="">Choose a line</option>
{allLines.map((line) => {
return <option value={line}>{line}</option>;
})}
</select>
<select name="lines" onChange={this.handleChange}>
<option value="">Choose a station</option>
{allLines.map((line) => {
return <option value={line}>{line}</option>;
})}
</select>
</div>
);
}
}
}
Related
I have the following code https://codesandbox.io/s/async-http-u9zsg
What I am trying to achieve is to reset the value of the second select box every time I switch movies.
I tried a bunch of different things without any luck. I'm not asking to write me the code, I just want a push in the right direction am I missing something, is my approach completely wrong?
thanks in advance
The code :
import React, { useState, useEffect } from "react";
import fetch from "node-fetch";
const query = `{
allFilms{
films{
title
characterConnection{
characters{
name
species{
name
}
homeworld{
name
}
}
}
}
}
}`;
function App() {
const [state, setState] = useState({
loading: true,
appliedFilters: {},
species: []
});
useEffect(() => {
const fetchMovieData = () => {
const options = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query })
};
return fetch(
"https://swapi-graphql.netlify.com/.netlify/functions/index",
options
)
.then(res => res.json())
.then(res =>
setState({ ...state, data: res.data.allFilms.films, loading: false })
);
};
fetchMovieData();
}, []);
if (state.loading) {
return <div>Loading...</div>;
}
/*work from here */
const getSpeciesOptions = () => {
if (state.appliedFilters.movie === undefined) {
return <option>Select a movie first</option>;
}
const currentMovie = state.data.filter(
movie => movie.title === state.appliedFilters.movie
);
const characters = currentMovie[0].characterConnection.characters;
const speciesList = characters.map(char =>
char.species === null ? "unknown" : char.species.name
);
return [...new Set(speciesList)].map(specie => <option>{specie}</option>);
};
const handleFilterChange = e => {
setState({
...state,
appliedFilters: {
...state.appliedFilters,
[e.target.name]: e.target.value
}
});
};
console.log(state);
const removeFilters = () => setState({ ...state, appliedFilters: {} });
const movieOptions = () =>
state.data.map(movie => <option>{movie.title}</option>);
return (
<div className="App">
<form>
<select
name="movie"
onChange={e =>
e.target.value !== "default"
? handleFilterChange(e)
: removeFilters()
}
>
<option value="default">Please select movie</option>
{movieOptions()};
</select>
<select name="species">
<option value="default">Please select a species</option>
{getSpeciesOptions()}
</select>
</form>
</div>
);
}
export default App;
A better approach is to fully control the value of the second select as well. So we add the value of it to the state as well:
const [state, setState] = useState({
loading: true,
appliedFilters: {},
species: [],
selected: "default"
});
Update the second control to read the value from state and also manipulate the state on change:
<select
value={state.selected}
name="species"
onChange={e => {
setState({ ...state, selected: e.target.value });
}}
>
<option value="default">Please select a species</option>
{getSpeciesOptions()}
</select>
And finally also manipulate the callback of the first to also reset the value:
const handleFilterChange = e => {
setState({
...state,
appliedFilters: {
...state.appliedFilters,
[e.target.name]: e.target.value,
},
selected: "default"
});
};
https://codesandbox.io/s/hardcore-nash-vtyx7 working example
All you need to do is to create a ref for the second select control.
let ref = React.createRef();
...
<select ref={ref}name="species">
<option value="default">Please select a species</option>
{getSpeciesOptions()}
</select>
</form>
Then in the callback of the first select do this:
const handleFilterChange = e => {
setState(...);
ref.current.value = 'default';
};
With the ref to the elememt you can now manipulate the value.
Actually, you know ReactJS exactly, but your approach causes you are complicated now. For such cases just keep the state of second select and after changing the first select reset the second one.
<select name="species" onChange={handleSpeciesChanges}>
I mean to write a state handler for the second select and then in the state handler of first one just reset the second.
In a React component for a menu, I need to set the selected attribute on the option that reflects the state.
i have an array, which has key and value,
whenever i select and change value from public to private than values are hit but nothing to change ..
here is my code..
constructor(props) {
super(props);
this.state = {
privacy: [
{
ratingVisiblity:'1',
CompanyVisiblity:'1'
}
]
};
this.onSelectHand = this.onSelectHand.bind(this);
}
onSelectHand = e => {
this.setState({
...this.state.privacy,
[e.target.name]: [e.target.value]
});
};
render() {
return (
<>
<div>
{this.state.privacy.map((value, ind) =>
Object.keys(value).map((key, indx) => (
<>
<span>{key}</span>
<select
name={key}
value={this.state[key] || this.state[value[key]]}
onChange={this.onSelectHand}
>
<option value="1"> public</option>
<option value="0">private</option>
<option value="2">Network</option>
</select>
</>
))
)}
</div>
</>
);
}
}
1.onSelectHand function may lost "this" point , try to bind this to your function :
constructor(props){
super(props);
this.state={
privacy:[{
ratingVisiblity:'1',
CompanyVisiblity:'1'
}]
}
this.onSelectHand = this.onSelectHand.bind(this);
}
please return value from iterator object
onSelectHand = (e, key) => {
console.log([e.target.name] + ":" + e.target.value);
console.info({ key });
let result = this.state.privacy;
result[0][key] = e.target.value;
console.info({ result });
this.setState(result);
};
please refer : https://codesandbox.io/s/jolly-mclaren-0x6ph?fontsize=14&hidenavigation=1&theme=dark
I have to create a dropdown with 3-4 options and based on the value selected by the user, I have to then call an API using the selected Value as the search string. The JSON returned from this API should then render as a DataGrid in the mainContent section of the page.
This is how the dropdown looks like, pretty basic:
handleDropdownChange(e) {
this.setState({selectedValue: e.target.value});
}
render() {
<div>
<select id="dropdown" onChange={this.handleDropdownChange} className={classes.mainContent}>
<option value="">select</option>
<option value="option1">1</option>
<option value="Option2">2</option>
<option value="Option3">3</option>
</select>
</div>
And I can then do:
<div>Selected value is : {this.state.selectedValue}</div>
This works!
But instead of the above, I want to use a function something-like:
grabData = () => {
fetch(API + {this.state.selectedValue})
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('something is wrong');
}
})
.then (data => this.setState({ myhits: data.hits, hitsIndex: 0, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
}
so, inside the render() I want to basically do- (see that I want to use the selectedValue in the function grabData above):
<div>
Selected value is : {this.grabData}
</div>
I'm open to suggestions on how best to do this. This is what I could think of, but it doesn't work.
Or, even better if someone can help me render that JSON in a datagrid.
I hope I haven't confused everyone :)
If you wish to run grabData, when the user changed the value, then call this function on change.
change this function:
handleDropdownChange(e) {
this.setState({selectedValue: e.target.value}, () => {
// Once the state is updated - call grabData...
this.grabData()
});
}
I marked Gugu's answer as accepted as it did work eventually :-) Thanks bud.
So, I got this to work. This is how:
This is how my dropdown looks like. See I added the grabData() inside my handleDropdownChange():
handleDropdownChange(e) {
this.setState({selectedValue: e.target.value});
this.grabData()
}
grabData = (selectedValue) => {
fetch(API + {selectedValue})
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('something is wrong');
}
})
.then (data => this.setState({ selectedData: data.hits, selectedIndex: 0, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
And then in the render() I added a map for the data:
render() {
<div>
<select id="dropdown" onChange={this.handleDropdownChange} className={classes.mainContent}>
<option value="">select</option>
<option value="option1">1</option>
<option value="Option2">2</option>
<option value="Option3">3</option>
</select>
</div>
<div>
<ThemeProvider theme={LIGHT_THEME}>
{
selectedData.map(hit =>
<ThemeProvider theme={DARK_THEME}>
<div key={hit.objectID}>
<button> {hit.created_at} {hit.url}</button>
</div>
</ThemeProvider>
)}
</ThemeProvider>
</div>
I just need to figure out how to render on each click. Right now, it renders on the first selection and doesn't render fresh results if I change the selection.
I am trying to set the state to the current select item in the dropdown. I thought there would be an easier way of doing this than going through the whole, action/reducer chain. But in this case setState will always be empty and componentWillReceiveProps will never be called.
Is there a way to accomplish this?
componentWillReceiveProps(nextProps) {
console.log("collection next props", nextProps);
this.setState({
currentList: nextProps.currentList,
});
}
handleChange (e) {
console.log('handle change called', e.target.value); //Value is received
//this.setState({value: e.target.value});
this.setState({currentList: e.target.value}); //This is never getting the value
this.props.fetchlistOfAssets(e.target.value); //This action executes without problem
};
render() {
/* Read from market and last price */
const postItems = this.props.indexes.map(post => (
<option key={post} value={post}>{post}</option>
));
return (
<div>
<h1>Select index</h1>
<select name="index" onChange={this.handleChange}>{postItems}</select>
</div>
);
}
}
Edit adding connect and mapState
const mapStateToProps = state => ({
indexes: state.posts.indexdays,
currentList: state.posts.currentList
});
export default connect(
mapStateToProps,
{ fetchIndexDays, fetchlistOfAssets }
)(CollectionDropDown);
I'm mapping over an array of vitamins from json, and I want to return the name of each Vitamin in the array in a dropdown menu when clicked on.
I thought I could declare a const variable in the fetch function, and use that in my JSX.
componentDidMount() {
fetch('/users')
.then(res => res.json())
.then(micros => micros.vitamins.map((micro) => {
const microVit = micro;
}))
}
render() {
return (
<form>
<label className="nutrient-label">
Vitamins
</label>
<select value={this.props.value} onChange={this.handleChange}>
<option value="" selected>--Vitamins--</option>
{microVit.map((vitamin, index) =>
<option value={vitamin.value} key={index}>{vitamin.name}</option>
)}
</select>
</form>
)
}
When I console.log(microVit) in the fetch function, I do get the array of Vitamins. It just doesn't carry over to the map function I'm trying to work in the return statement of my render function.
You need to store it in the state and update it via setState to re-render the component with the new data:
class ... {
state = { microVit: [] }
componentDidMount() {
...
.then(({ vitamins }) => this.setState({ microVit: vitamins })
}
render() {
...
{this.state.microVit.map(...
Your const microVit is encapsulated.
Move the decleration to outside your component did mount method ideally to internal state or a redux store.