I am a beginner in ReactJS and I am getting an error that I don't understand. This is my first written with ReactJS app. Here is my code.
Error
react map is not a function error in my app
SearchBar Component
import RecipeList from './recipes_list';
class SearchBar extends Component {
state = {
term : []
}
onInputChange(term){
this.setState({term});
}
onSubmit = async (term) => {
const recName= this.state.term;
term.preventDefault();
const api_key = 'a21e46c6ab81bccebfdfa66f0c4bf5e9';
const api_call = await Axios
.get(`https://www.food2fork.com/api/search?key=${api_key}&q=${recName}&`)
.then(res=> {this.setState({term : res.data.recipes})})
}
render() {
return (
<div>
<form onSubmit={this.onSubmit} >
<div className="search-bar">
<input
type="text"
value={this.state.term}
onChange={event => this.onInputChange(event.target.value)}
/>
<button type="submit">Search</button>
</div>
</form>
<RecipeList List ={this.state.term}/>
</div>
)
}
}
RecipeList Component
const RecipeList = props => (
<div>
{
props.List.map((recipe) => {
return (
<div>{recipe.title}</div>
)
})
}
</div>
)
export default RecipeList;
Thank you guys for your help
Your problem is in this snippet:
onInputChange(term){
this.setState({term});
}
This will set your state variable to a String. For example, if I type in Hello!, your state object will be { term: 'Hello!' }. You're now trying to .map() over a String, String.map is not a function.
The .map function is only available on array.
It looks like data isn't in the format you are expecting it to be (it is {} but you are expecting []).
this.setState({data: data});
should be
this.setState({data: data.conversations});
Check what type "data" is being set to, and make sure that it is an array.
More generally, you can also convert the new data into an array and use something like concat:
var newData = this.state.data.concat([data]);
this.setState({data: newData})
This pattern is actually used in Facebook's ToDo demo app (see the section "An Application") at https://facebook.github.io/react/.
Related
I'm very new to React and Javascript. I am creating a simple search feature using React and Nodejs where you are able to search for tutors. I am trying to print the output of the search using react. My express server sends a response in the form of a string. It looks like the following:
'[{"tutorID":1,"email":"johndoe#sfsu.edu","firstName":"John","lastName":"Doe","courseTeaching":"csc510","imageReference":" http://localhost:3001/john.png "}]'
I want to be able to display every key and its value in the form of a table. Can someone please help me achieve this?
The code for my search in react is given below:
import React, {useState} from 'react';
import "./SearchForm.css";
import SearchIcon from '#mui/icons-material/Search';
import DisplayResults from './DisplayResults.js';
class SearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedCategory: '',
textSearch: '',
searchResponse: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState( {
...this.state,
[target.name]: value
});
}
handleSubmit(event) {
event.preventDefault();
let cat = this.state.selectedCategory;
let searchquery = this.state.textSearch;
fetch(`http://localhost:3000/onSubmit?param1=${cat}¶m2=${searchquery}`, {
method: "GET",
headers: {
'Content-type': 'application/json'
}
})
.then((result, err) => result.json())
.then(contents => {
this.setState({ searchResponse: contents}, function() {
console.log(this.state.searchResponse);
})
});
}
render() {
return (
<>
<p className="greeting">Hi, what would you like to search?</p>
<form onSubmit={this.handleSubmit}>
<div className="wrapper">
<select class="theme"
name="selectedCategory"
type="category"
value={this.state.selectedCategory}
onChange={this.handleInputChange}>
<option value="all">Search All</option>
<option value="tutors">Tutors</option>
<option value="Courses">Courses</option>
</select>
<input className="searchBar"
name="textSearch"
type="text"
placeholder="search"
value={this.state.textSearch}
onChange={this.handleInputChange}>
</input>
<div className="searchIcon">
<SearchIcon onClick={this.handleSubmit}/>
</div>
</div>
</form>
<DisplayResults searchResults={this.state.searchResponse}/>
</>
)
}
}
export default SearchForm;
The code for the DisplayResults is below:
import React from 'react';
class DisplayResults extends React.Component {
render() {
return (
<div>{this.props.searchResults}</div>
);
}
}
export default DisplayResults;
Any help would be much appreciated, thank you.
What you could do if the response was JSON is to use the .map property.
In this example I omitted the ' ' (at the start and end) from your set of data, to make it valid json.
let object = [{
"tutorID": 1,
"email": "johndoe#sfsu.edu",
"firstName": "John",
"lastName": "Doe",
"courseTeaching": "csc510",
"imageReference": " http://localhost:3001/john.png "
}]
So you could probably do something like this to access the values.
const listItems = object.map((object) => console.log(object.tutorID))
So with .map you can return a component with the data, you just mapped over.
So after that it's up to you what you want to do with the data. So you can create a table, or use one from Bootstrap or something similar and just map the values out.
<Table>
<p>{object.tutorID}</p>
<p>{object.email}</p>
<p>{object.firstName}</p>
<p>{object.lastName}</p>
...
</Table>
If I said anything wrong, or if I didn't quite give you the answer you wanted then let me know.
[Inside SearchForm] After receiving the response, let's turn your string data into something usable. Parse your string to an array of objects using
JSON.parse(YOUR_DATA). You can then set your searchResponse state to this array.
[Inside DisplayResults] You have an array of objects, so iterate over them using YOUR_DATA.map(). Your map function should return some JSX. In this case you are making a table.
This is a function which creates the table.
We use Object.values(YOUR_OBJECT) to get all the values (e.g. John, Doe ...) and Object.keys(YOUR_OBJECT) to get the keys (e.g. firstName, lastName).
You can use something like this in your render() function to create the table.
const createTable = () => {
const tableHeaders = ( // Get the table headers from the first person, OR enter them manually.
<tr>
{Object.keys(this.props.searchResults[0]).map(headerTitle => <th>{headerTitle}</th>)}
</tr>
);
// All the content rows of the table.
const tableRows = this.props.searchResults.map((tutor) => (
<tr> {/* this is one row of the table. It is filled with td's which each have a piece of information about the tutor. */}
{Object.values(tutor).map(value => ( // for each of the properties of the tutor, we create a table cell.
<td>{value}</td>
))}
</tr>
));
// The actual table, with table headers and table rows.
return (
<table>
{tableHeaders}
{tableRows}
</table>
);
}
const listItems = object.map((object) => console.log(object.tutorID))
top of the line work for me.
I am trying to create a todo list using React but i cant seem to understand why I am getting the error: "Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state."
Here's the code:
import React from 'react'
import ReactDOM from 'react-dom'
class Todo extends React.Component{
constructor(props){
super(props)
this.state = {
input: '',
list: []
}
this.handleChange = this.handleChange.bind(this)
this.reset = this.reset.bind(this)
this.removeItem = this.removeItem.bind(this)
this.add = this.add.bind(this)
}
add(){ //Adds a new task
const newItem = {
value: this.state.input,
id: Math.random + Math.random
};
const listed = [...this.state.list]
listed.push(newItem)
this.setState({
input: '',
list: listed
})
}
removeItem(id){ //deletes a task
const list = [...this.state.list]
const updatedList = list.filter(obj => {
return obj.id !== id
})
this.setState({
list: updatedList
})
}
handleChange(e){
this.setState({input: e.target.value})
}
reset(e){
e.preventDefault()
}
render(){
return (
<div>
<form action="" onSubmit={this.reset}>
<input type="text" value={this.state.input} placeholder='Enter a task..' onChange={this.handleChange} />
<button onClick={this.add}>Add Task</button>
{this.state.list.map(item => { //updates when a task is added or removed
return (
<div key={item.id}>
<h1>{item.value}</h1>
<button onClick={this.removeItem(item.id)}>X</button>
</div>
)
})}
</form>
</div>
)
}
}
ReactDOM.render(<Todo />,document.getElementById('root'))
Because you are calling removeItem on render. It needs to be wrapped in a separate function:
<button onClick={() => this.removeItem(item.id)}>X</button>
So that you only call it onClick and not on render.
<button onClick={this.removeItem(item.id)}>X</button>
In this button the event handler you have provided runs immediately due to the presents of the () at the end. To prevent this and still provide your argument item.id you can enclose the handler this.removeItem(item.id) with in another function.
I like the arrow function for this so mine looks like this
<button onClick={ ()=>this.removeItem(item.id) }>X</button>.
Math.random + Math.random is not returning a number like you would want for the element key. This is because your have neglected to include () at telling JS to run the function and return an int.
After making these changes, I ran it in codepen.io and it seemed to work fine.
I have two api requests that return JSON objects. They return an array of objects.
One API request that I make is fine and allows me to update the state with the response, but the other one (below) doesn't and I don't understand why.
API request to fetch genres list:
async getGenreList() {
const genresResults = await getGenres();
return genresResults;
}
The request:
export const getGenres = async () => {
try {
const response = await axios.get(
"https://api.themoviedb.org/3/genre/movie/list?api_key=<APIKEY>&language=en-US"
);
const { genres } = response.data;
return genres;
} catch (error) {
console.error(error);
}
};
The response is an array of 19 genre objects but this is just an example:
[
{id: 28, name: "Action"},
{id: 12, name: "Adventure"}
]
I then want to update the state like this and pass the response to genreOptions. But it tells me Error: Objects are not valid as a React child (found: object with keys {id, name}). If you meant to render a collection of children, use an array instead.
componentDidMount() {
this.getGenreList().then((response) => {
console.log(response)
this.setState({ genreOptions: response});
});
}
The below works when i update the state and map over it but I don't want to do that, i want to pass the whole response down so i can map over the data in my component as I need it there to do some data matching.
this.setState({ genreOptions: response.map((genreOption) => {
return genreOption.name
})});
This is the state:
this.state = {
results: [],
movieDetails: null,
genreOptions: [],
};
I want to pass the genreOptions here to genres then map over it in the MovieResults component.
<MovieResults>
{totalCount > 0 && <TotalCounter>{totalCount} results</TotalCounter>}
<MovieList movies={results || []} genres={genreOptions || []} />
</MovieResults>
Why can't I? Any ideas? I have done it for another similar request :S
UPDATE TO SHOW MOVIELIST COMPONENT
export default class MovieList extends React.Component {
render() {
const { movies, genres } = this.props;
const testFunction = (movieGenreIds) => {
const matchMovieGenresAndGenreIds = genres.map((genreId) => {
const matchedGenres = movieGenreIds.find((movieGenre) => {
return movieGenre.id === genreId
})
return matchedGenres // this returns the matching objects
})
const result = matchMovieGenresAndGenreIds.filter(Boolean).map((el)=> {
return el.name
})
return result
}
return (
<MoviesWrapper>
{movies.map((movie) => {
const {
title,
vote_average,
overview,
release_date,
poster_path,
genre_ids
} = movie;
return (
<MovieItem
title={title}
rating={vote_average}
overview={overview}
release={release_date}
poster={poster_path}
movieGenres={testFunction(genre_ids)}
/>
);
})}
</MoviesWrapper>
);
}
}
**** MOVIE ITEM COMPONENT***
export default class MovieItem extends React.Component {
render() {
const { title, overview, rating, release, poster, movieGenres } = this.props;
return (
// The MovieItemWrapper must be linked to the movie details popup
<MovieItemWrapper>
<LeftCont>
<img
className="movie-img"
src={`https://image.tmdb.org/t/p/w500${poster}`}
/>
</LeftCont>
<RightCont>
<div className="movie-title-container">
<h2 className="movie-title">{title}</h2>
<Rating>{rating}</Rating>
</div>
<div>{movieGenres}</div>
<p>{overview}</p>
<p>{release}</p>
</RightCont>
</MovieItemWrapper>
);
}
}
Please follow this steps to fix your code. I'll try yo explain what's happening along the way:
In your main component. Set the state to the value that you really want to pass to your child component. Remember that response will be an array of objects.
componentDidMount() {
this.getGenreList().then((response) => {
this.setState({genreOptions: response});
});
}
In your MovieList component. Please check your testFunction to respect data types. The following code will return you an array of strings containing the names of the genres that are included in the movies genres array.
const testFunction = (movieGenreIds) => {
return genres
.filter((genre) => {
return movieGenreIds.includes(genre.id);
})
.map((genre) => genre.name);
};
In your MovieItem component. (This is were the real problem was)
Instead of:
<div>{movieGenres}</div>
You may want to do something like this:
<div>{movieGenres.join(' ')}</div>
This converts your array into a string that can be rendered. Your error was due to the fact that you were passing there an array of objects that React couldn't render.
If you have any doubt, please let me know.
NOTE: I suggest you to use a type checker to avoid this kind of problems. And to be consistent with your variables naming conventions.
Update based on new information from chat:
In your ExpandableFilters component, you must fix the following piece of code to get the genre name (string). As explained in chat, you can't have objects as a result for a JSX expression ({}), but only primitives that can be coerced to strings, JSX elements or an array of JSX elements.
<GenreFilterCont marginTop>
{filtersShown && (
<ExpandableFiltersUl>
{this.props.movieGenres.map((genre, index) => {
return (
<ExpandableFiltersLi key={index}>
<Checkbox />
{genre.name}
</ExpandableFiltersLi>
);
})}
</ExpandableFiltersUl>
)}
</GenreFilterCont>
Please also note that I've added a key property. You should do it whenever you have a list of elements to render. For more about this I will refer you to the React Docs.
I added the Nutrition file
const Nutrition = () => {
return(
<div>
<p>Label</p>
<p>Quantity</p>
<p>Unit</p>
</div>
)
}
export default Nutrition
I'm trying to map something in React but I'm getting this error map is not function. I'm trying to fetch an Api and now I'm trying to map another component to it, but the error is still there. Could someone help me or give me a hint
const ApiNutrition = () => {
const [nutritions, setNutritions] = useState([])
useEffect( () => {
getNutritions();
}, [])
const getNutritions = async () => {
const response = await fetch(`https://api.edamam.com/api/nutrition-data?app_id=${API_ID}&app_key=${API_KEY}&ingr=1%20large%20apple`)
const data = await response.json();
setNutritions(data.totalNutrientsKCal)
console.log(data.totalNutrientsKCal);
}
return(
<div>
<form className="container text-center">
<input classname="form-control" type="text" placeholder="CALORIES"/>
<button classname="form-control" type="submit">Submit</button>
</form>
{nutritions.map(nutrition => (
<Nutrition />
))}
</div>
)
}
export default ApiNutrition
From your code, I can see you have two places where you are setting the value of nutritions. One is while defining with useState(), and the other is after API call with setNutritions.
The error you are getting is map is not a function, it means somehow type of nutritions is not an array.
while defining with useState you are providing [] as default value so it means the error is with the API, the response you are getting from API which you are passing to setNutritions is not an array.
You can debug the API response type by typeof data.totalNutrientsKCal inside console.log
I have a Dashboard component that renders an array of cards with data fetched from a backend server. Users can create additional cards by submitting a form, which then redirects them back to the dashboard page.
My issue is that when the form is submitted, a javascript error 'cannot read property "includes" of undefined' is thrown and the dashboard does not render. If I manually refresh the page, the list renders as expected with the new card. I use Array.includes method to filter the cards based on the filterText state value. Does this error happen because the data has not been fetched when render is called? If so, how can I force the component to wait until there is data before rendering? Please see the components and redux action below.
const CardList = (props) => {
const cards = props.cards.map(({ _id, title}) => {
return (
<Card key={_id} title={title} />
)
});
return (
<div className="container">
<input onChange={ (e) => props.handleChange(e.target.value) } />
<div className="row">
{cards}
</div>
</div>
);
}
export default CardList;
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
filterText: ''
}
}
componentDidMount() {
this.props.fetchCards();
}
handleChange = (filterText) => {
this.setState({filterText});
}
render() {
const cardList = this.props.cards.filter(card =>
card.title.includes(this.state.filterText.trim().toLowerCase())
);
return (
<div>
<CardList cards={cardList}
handleChange={filterText => this.handleChange(filterText)} />
</div>
);
}
};
function mapStateToProps({ cards: { cards }}) {
return {
cards,
}
}
export default connect(mapStateToProps, {fetchCards})(Dashboard);
export class SurveyForm extends Component {
render() {
return (
<div>
<form>
<Field component={CardField} type="text"
label={'title'} name={'title'} key={'title'} />
<Button type="submit" onClick={() => submitCard(formValues, history)}>Next</Button>
</form>
</div>
);
}
}
REDUX ACTION DISPATCHER:
export const submitCard = (values, history) => async dispatch => {
const res = await axios.post('/api/cards', values);
try {
dispatch({ type: SUBMIT_CARD_SUCCESS, payload: res.data });
dispatch({ type: FETCH_USER, payload: res.data })
}
catch(err) {
dispatch({ type: SUBMIT_CARD_ERROR, error: err });
}
history.push('/cards');
}
Similar to what #JasonWarta mentioned, it's worth noting that React does not render anything when false, null, or undefined is returned, so you can usually use && to be more succinct than using the conditional ("ternary") operator:
render() {
return this.props.cards && (
<div>
<CardList
cards={this.props.cards.filter(card => card.title.includes(this.state.filterText.trim().toLowerCase())}
handleChange={filterText => this.handleChange(filterText)}
/>
</div>
);
}
Because && short-circuits, the latter part won't be evaluated so you can avoid TypeErrors, and the component will also render no content (same as when you return null).
I've used ternary operators in this kind of situation. You may need to adjust the check portion of the pattern, depending on what your redux pattern is returning. null value is returned if this.props.cards is falsey.
render() {
return (
{this.props.cards
?
<div>
<CardList
cards={this.props.cards.filter(card => card.title.includes(this.state.filterText.trim().toLowerCase())}
handleChange={filterText => this.handleChange(filterText)}
>
</CardList>
</div>
:
null
}
);
}
As an alternative to other answers you can return something else suitable if there is no data in your render function with an if statement. I prefer moving functions like your filter one outside of render. Maybe one other (better?) approach is doing that filter in your mapStateToProps function.
Also, if I'm not wrong you don't need to pass anything to your handleChange function. Because you are getting filterText back from CardList component then setting your state.
cardList = () => this.props.cards.filter(card =>
card.title.includes(this.state.filterText.trim().toLowerCase()));
render() {
if ( !this.props.cards.length ) {
return <p>No cards</p>
// or return <SpinnerComponent />
}
return (
<div>
<CardList cards={this.cardList()}
handleChange={this.handleChange} />
</div>
);
}