How to convert string to JSON object and display in react component? - javascript

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}&param2=${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.

Related

why can i not update my state with my api response? data is an array of objects

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.

How can I create a List of items from the API data I get?

I am trying to create a list of subcategories that I'm getting from the API and display them in the App. The problem is that I don't know how to transform the items from the Array (API) into List items.
componentDidMount(){
axios.get('/categories/' + this.props.match.params.id)
.then(response => {
console.log(response.data.children) //Array of strings
})
}
render(){
return(
<div className={classes.Showcategory}>
<h1>{this.props.match.params.id}</h1>
<li>Here I need for each string of the array a list item<li/>
</div>
);
}
You can define a state variable for the component. When you are making a request, update the state. When state gets update your component will be re rendered, with the data you want.
Try this:
constructor(props) {
super(props);
this.state = {
categories: []
};
}
componentDidMount() {
axios.get("/categories/" + this.props.match.params.id).then(response => {
console.log(response.data.children); //Array of strings
this.setState({ categories: response.data.children });
});
}
render() {
return (
<div className={classes.Showcategory}>
<h1>{this.props.match.params.id}</h1>
{this.state.categories.map((category, index) => (
<li key={index}>{category}</li>
))}
</div>
);
}
Note that React recommends that you don't use index as the key. For your case, if category strings are unique, use those instead.

Need to display the array list on frontend

I trying to display the list using map function javascript but I am getting error saying "TypeError: Cannot read property 'map' of undefined".
import React, { Component } from 'react'
import constants from './Constants'
import axios from 'axios'
class Home extends React.Component {
constructor(props) {
super(props)
this.state = {
value: 0,
results: {},
}
this.handleClick = this.handleClick.bind(this)
this.input = React.createRef()
}
handleClick = event => {
this.setState({ value: this.input.current.value })
event.preventDefault()
}
componentDidMount() {
console.log('componnet did mount')
const that = this
axios.get('https://reqres.in/api/users').then(function(response) {
that.setResults(response.data)
})
}
setResults(data) {
this.setState({ results: data })
}
render() {
let newvalue = this.state.value
let obj = this.state.results
console.log(obj)
let latestvalue =
constants.MONTHS[newvalue] == null
? 'invalid month'
: constants.MONTHS[newvalue]
return (
<div className="home">
<h1>Welcome to my portfolio website</h1>
{obj.data.map(emp => (
<tr>
<td> </td>
</tr>
))}
Enter Month number <input type="text" ref={this.input} />
<button type="button" onClick={this.handleClick}>
{' '}
submit{' '}
</button>
<p> Feel free to browse around and learn more about me.</p>
Month is {latestvalue}
</div>
)
}
}
export default Home
Need to display all the first names on DOM.
I just need to display the first names in that array of object also recommend me which Javascript function best to use display data.
Try to update following block of lines may help you:
{obj && obj.data && obj.data.map(emp =>
<tr>
<td> {emp.first_name}</td>
</tr>
)}
When initializing the state, you need to describe the full shape of the object for TypeScript to understand it.
results : {
obj: {
data: null
}
}

react map is not a function error in my app

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/.

Array being populated with JSX Elements not rendering/updating

This may be a quick fix but I have been racking my brain for the past little while, and could really use another set of eyes to take a look.
Basically I am trying to render an array full of generated JSX elements. I fell like I have done this a million times, but it does not seem to work here.
Heres the code:
import React, { Fragment } from 'react'
import css from './Search.scss';
import Header from '../SectionHeader/Header';
import SearchItem from '../SearchItem/SearchItem';
const Search = (props) => {
const { coinObject, coinKeys } = props;
let searchResults = []; // Array in question
const findResults = (searchText) => {
searchResults = []; // Reset the array to blank for each new character typed in input
for(let i = 0; i < coinKeys.length; i++) {
const { FullName } = coinObject[coinKeys[i]]; // App specific logic, not important, or the problem here
if(FullName.toLowerCase().includes(searchText) && (searchResults.length < 5)) {
console.log(FullName, searchText); // Prints the correct Full name based off of the searched text
searchResults.push(<SearchItem key={i} searchText={FullName} />);
}
}
console.log(searchResults); // Prints the updated array with all react elements
}
return (
<Fragment>
<Header title='Find Coins!' />
<div className={css.searchContainer}>
<div className={css.inputContainer}>
<input onChange={input => findResults(input.target.value)} className={css.searchInput} type='text' placeholder='Start Typing a Coin'/>
</div>
{ searchResults }
</div>
</Fragment>
);
}
export default Search;
And the SearchItem Component, which is super simple:
import React from 'react'
import css from './SearchItem.scss';
const SearchItem = (props) => {
return (
<div className={css.searchItem}>
{props.searchText}
</div>
)
}
export default SearchItem;
For a little bit of context, this component just gets a giant object of data, and will display the first 5 instances of what matches the input text. I am trying to make one of those search filter things, where as you type it suggests things that match from the data.
The array gets updated, and I can see the JSX objects in the array, they just do not render. I have a feeling it is due to the array not re-rendering?
Any help is much appreciated. Thanks!
You could make the Search component into a stateful component and store the searchResults in your state instead, so that when it is updated your component will be re-rendered.
Example
class Search extends React.Component {
state = { searchResults: [] };
findResults = searchText => {
const { coinObject, coinKeys } = this.props;
const searchResults = [];
for (let i = 0; i < coinKeys.length; i++) {
const { FullName } = coinObject[coinKeys[i]];
if (
FullName.toLowerCase().includes(searchText) &&
searchResults.length < 5
) {
searchResults.push(FullName);
}
}
this.setState({ searchResults });
};
render() {
return (
<Fragment>
<Header title="Find Coins!" />
<div className={css.searchContainer}>
<div className={css.inputContainer}>
<input
onChange={event => findResults(event.target.value)}
className={css.searchInput}
type="text"
placeholder="Start Typing a Coin"
/>
</div>
{this.state.searchResults.map((fullName, i) => (
<SearchItem key={i} searchText={fullName} />
))}
</div>
</Fragment>
);
}
}

Categories