I am trying to have a search bar which updates the set of json entries depending upon query. Here's the code that displays the video list (<Videos videos={this.state.data}/>). When the page loads, I want to call <Videos videos={this.state.data}/>, but after this query from search bar should update this list. My search functionality is not working for some reason.
class App extends Component {
state = {
query: "",
data: [],
filteredData: []
};
handleInputChange = event => {
const query = event.target.value;
this.setState(prevState => {
const filteredData = prevState.data.filter(element => {
return element.title.toLowerCase().includes(query.toLowerCase());
});
return {
query,
filteredData
};
});
};
getData = () => {
fetch('http://localhost:3000/api/videos')
.then(response => response.json())
.then(data => {
const { query } = this.state;
const filteredData = data.filter(element => {
return element.title.toLowerCase().includes(query.toLowerCase());
});
this.setState({
data,
filteredData
});
});
};
componentWillMount() {
this.getData();
}
render() {
return (
<div className="searchForm">
<form>
<input
placeholder="Search for..."
value={this.state.query}
onChange={this.handleInputChange}
/>
</form>
<Videos videos={this.state.data}/>
<div>{this.state.filteredData.map(i => <p>{i.name}</p>)}</div>
</div>
);
}
}
I am new to react, any pointers will be appreciated.
You are creating a new object without the data property and setting that object as the state whenever you are calling this.setState. So the data property is getting deleted.
On the handleInputChange method do this:
return {
...this.state,
query,
filteredData
};
And on the getData method do this:
this.setState({
...this.state,
data,
filteredData
});
You are using this.state.data in the Videos component which you got from the server. You should use this.state.filteredData to show entries depend on query:
<Videos videos={this.state.filteredData}/>
You need to bind the handleInputChange to this.
Look at this for more on this.
Related
Learning react
Trying to loop through an object from an API call that returns a json object and display it but struggling to implement it
This is the component that should render it
export default class ProfilePage extends Component {
constructor() {
super();
this.state = { data: '' };
}
mapObject(object, callback) {
return Object.keys(object).map(function (key) {
return callback(key, object[key]);
})
}
async componentDidMount() {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const json = await response.json();
// console.log(json)
this.setState({ data: json });
}
render() {
const data = this.state.data
console.log(data)
return (
<div className="row">
{Object.values(data).map(data => {
<div key={key}>
{data[key]}
</div>
})
}
Woerkkk please
</div>
);
}
}
All I'm getting is a blank screen.
in the console i get the error 'key' is not defined no-undef
You are missing a return statement in your map for your render method.
Edit: Key is not returned from Object.values
Either reconfigure with a return statement like so:
{Object.keys(data).map(key => {
return (<div key={key}>
{data[key]}
</div>);
})
Or alternatively you can implicitly return from arrow function using brackets
{Object.keys(data).map(key => (
<div key={key}>
{data[key]}
</div>)
))
Using Object.values(myObj) you can get all object values as a array. So, with this array, you can iterate over the array and show your items, like this:
{Object.values(myObj).map(value => <p>{value}</p>)}
Don't forget use key prop when iterating.
You can use useState and useEffect to fetch the object data
const App = () => {
const [objData, setObjData] = useState({});
const [objItems, setObjItems] = useState([]);
const fetchObj = async () => {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const data = await response.json();
setObjData(data);
setObjItems(data.items);
}
useEffect(() => {
fetchObj()
},[]);
return(
<div>
<h1> Order Id :{objData.order_id}</h1>
// or any other objData keys
<h1>Items : </h1>
<ul>
{
objItems.map((i, idx) => {
return(
<li key={idx}>Name : {i.name} , Category: {i.category}, Price: {i.price}, Currency: {i.currency}</li>
)
})
}
</ul>
</div>
)
}
export default App;
I am using the react autosuggest library to build auto-suggestion
import Autosuggest from "react-autosuggest";
import React, { Component } from "react";
import QueryString from "query-string";
class AutoSuggestSearch extends Component {
constructor() {
super();
this.state = {
value: "",
suggestions: []
};
this.getSuggestionValue = this.getSuggestionValue.bind(this);
this.renderSuggestion = this.renderSuggestion.bind(this);
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
getSuggestionValue = suggestion => suggestion.fullNameSuggestion;
renderSuggestion = suggestion => <div>{suggestion.name}</div>;
onSuggestionSelected = (event, { suggestion}) => {
console.log(suggestion);
this.setState({
suggestions: [],
value: suggestion.name
});
};
onSuggestionsFetchRequested = ({ value }) => {
const params = {
stationPrefixName: value
};
const queryParams = QueryString.stringify(params);
fetch(`http://localhost:8000/api/suggest?${queryParams}`)
.then(res => res.json())
.then(data => {
console.log(data);
this.setState({
suggestions: data
});
})
.catch(console.log);
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
value: ""
});
};
render() {
const { value, suggestions } = this.state;
const inputProps = {
placeholder: "Search",
value,
onChange: this.onChange
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
inputProps={inputProps}
/>
);
}
}
export default AutoSuggestSearch;
The suggestion gets rendered on typing on search box as well as the logging inside onSuggestionSelected gets logged correctly but the input search box does not update correctly.
On debugging further I found that onSuggestionsClearRequested also gets invoked after onSuggestionSelected which is causing the search input box to be empty.
I validated this by adding const string inside onSuggestionsClearRequested
onSuggestionsClearRequested = () => {
alert("clear request");
this.setState({
suggestions: [],
value: "mysearch"
});
};
Is there anyway to prevent onSuggestionsClearRequested invokation on suggestion selection?
Or updating the search query value inside onSuggestionsClearRequested is the correct way?
You can use componentDidUpdate or UseEffect if you are using it in functional component.
I have used react-autosuggest in functional component and clear suggestion works only if value doesn't matches with the suggestions:
const [clear, setClear] = useState(false);
const handleOnChange = newValue => {
setValue(newValue);
setClear(false);
};
useEffect(() => {
if (!suggestions.some(option => option === value) && clear) {
setValue('');
}
}, [clear]);
const onSuggestionsClearRequested = () => {
setClear(true);
setSuggestions([]);
};
The onSuggestionsClearRequested function gets called everytime you click outside the search input, which is the default implementation of the libary being used,
What we implement in onSuggestionsClearRequested is upto us.
you can change the implementation as follows :
Approach keep keyword inside input if available options are not selected
onSuggestionsClearRequested = () => {};
this should provide the desired implementation behaviour.
Hi you may approach with hooks. It looks good and less coding.
You may find below
https://github.com/rajmaravanthe/react-auto-suggestion
TLDR:How would i better map an array within a initalState prop
The following code im trying to attempt is to retrieve all postIds from the like array, and compare post.id to like.postId
However
like.postId is undefined.
post.id is available on the console log, the difference between posts, and likes is that posts are mapped on the client side, and likes are not. So im trying to do the mapping in the reducer because i would need to get amount of like counts stored for each post, and map it to its respected post.id.
The reason why i would need to set the values within the likes initialState is because i would need to use this following action to upvote posts
case ADD_LIKE:
// console.log(action.id) // renders post id which is 2
// console.log(state.posts) // logs posts array
// console.log(state.posts)
return {
...state,
likes: state.likes + 1
};
if i do something like this like[0].postId it will only get the values for that like.id only. I need to be able to get all of likes from all posts, and compare like.postId to post.id and then set the value.
And retrieve the count like
{this.props.likes}
just to get an idea what the array looks like.
This is example posts array, and within the Posts array, you have the likes array.
Here is how im calling posts
export const GetPosts = () => {
return (dispatch, getState) => {
return Axios.get('/api/posts/myPosts')
.then( (res) => {
const data = res.data
const likes = res.data // gets the first item within array, and shows likes.
const myLikes = likes.map( (post) => {
return post.Likes
})
console.log(myLikes)
dispatch({type: GET_POSTS, data, myLikes})
})
}
}
reducer
export default (state = initialState, action) => {
switch (action.type) {
case GET_POSTS:
console.log(action.data[0].Likes.length)
return {
...state,
posts: action.data, // maps posts fine
// set likes to but it only gets the first post, when it should get all posts
likes: action.data.map( (post) => {
action.myLikes.map( (like) => {
// if(post.id === like.postId){
console.log(like) // renders an array of likes for all posts
console.log(like.postId) // renders undefined,
// }
})
})
}
}
this is how its being mapped
PostList.js
render(){
const {posts} = this.props;
return (
<div>
{posts.map((post, i) => (
<Paper key={post.id} style={Styles.myPaper}>
{/* {...post} prevents us from writing all of the properties out */}
<PostItem
myTitle={this.state.title}
editChange={this.onChange}
editForm={this.formEditing}
isEditing={this.props.isEditingId === post.id}
removePost={this.removePost}
{...post}
/>
</Paper>
))}
</div>
)
}
GetPosts action is called within this component
class Posts extends Component {
state = {
posts: [],
loading: true,
isEditing: false,
}
async componentWillMount(){
await this.props.GetPosts();
this.setState({ loading: false })
const reduxPosts = this.props.myPosts;
const ourPosts = reduxPosts
console.log(reduxPosts); // shows posts line 35
}
render() {
const {loading} = this.state;
const { myPosts} = this.props
if (!this.props.isAuthenticated) {
return (<Redirect to='/signIn' />);
}
if(loading){
return "loading..."
}
return (
<div className="App" style={Styles.wrapper}>
<h1> Posts </h1>
<PostList posts={myPosts}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
isAuthenticated: state.user.isAuthenticated,
myPosts: state.post.posts
})
const mapDispatchToProps = (dispatch, state) => ({
GetPosts: () => dispatch( GetPosts())
});
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Posts));
When you use Array.map() it will create a new array with the results of calling a provided function on every element in the calling array, so you will get an array of arrays (array of each post's likes array) in order to solve your issue and get an array of likes you need to use the reducer function as follows:
export const GetPosts = () => {
return (dispatch, getState) => {
return Axios.get('/api/posts/myPosts')
.then( (res) => {
const data = res.data
const likes = res.data // gets the first item within array, and shows likes.
const myLikes = likes.reduce( (acc,post) => {
return acc.concat(post.Likes)
},[])
console.log(myLikes)
dispatch({type: GET_POSTS, data, myLikes})
})
}}
I'm learning React Framework. When i try to fetch from my api using setState .map function dont work. It render "data.map is not a function"
This is the only Method i know:
constructor(props) {
super(props);
this.state = {
name: "secret",
email: "secret",
datas: []
};
}
componentDidMount() {
const { name, password} = this.state;
fetch(
`http://localhost:3000/api/user/account?username=${username}&email=${email}`
)
.then(res => res.json())
.then(getdata => {
this.setState({ datas: getdata });
})
.catch(err => console.log(err));
}
render() {
const { datas } = this.state;
const repoItems = datas.map(data => (
<div key={data.id}>
<p>{data.name}</p>
<p>{data.email}</p>
</div>
));
return (
<div>
{datas}
</div>
);
}
Did I wrote my code wrong or is there any other method beside from this
From the top view everything seems fine with your code. map will only run on arrays and I can see that you are setting default state to array as well. However the problem may lies when you attempt to set the state after getting response from fetch call
fetch(
`http://localhost:3000/api/user/account?username=${username}&email=${email}`
)
.then(res => res.json())
.then(getdata => {
this.setState({ datas: getdata });
})
.catch(err => console.log(err));
Here you need to make sure that whatever you are setting data, it should be an array. So in case you are not getting the array in the response set the value which is actually an array and in case the response is empty set an empty array in state instead, something like below
// may be array is a property of response
this.setState({ datas: getdata.array });
// may be getdata is empty
this.setState({ datas: getdata || [] });
I'm making an application where I have to grab certain data from the Github API. I need to grab the name, url, language and latest tag. Because the latest tag is in a separate url, I need to make another fetch call there to grab that data.
I'm running into a certain amount of errors.
1st being the typeError cannot read property 'name' of undefined. I'm sure this is from the fetch call to the tag url where there isn't any data. I'm not really sure how to check if it's undefined. I've tried calling checking to see if the typeof data is undefined and so on but still get the error.
2nd problem being my tag url data doesn't show up with the other data. I'm sure I'm chaining the data wrong because when I click the add button it shows up.
Here is my code:
import React, { Component } from 'react'
import './App.css'
class App extends Component {
state = {
searchTerm: '',
repos: [],
favourites: []
}
handleChange = e => {
const { searchTerm } = this.state
this.setState({ searchTerm: e.target.value })
if (searchTerm.split('').length - 1 === 0) {
this.setState({ repos: [] })
}
}
findRepos = () => {
const { searchTerm } = this.state
// First api call here
fetch(`https://api.github.com/search/repositories?q=${searchTerm}&per_page=10&access_token=${process.env.REACT_APP_TOKEN}
`)
.then(res => res.json())
.then(data => {
const repos = data.items.map(item => {
const { id, full_name, html_url, language } = item
const obj = {
id,
full_name,
html_url,
language,
isFavourite: false
}
// Second api call here. I need the data from map to get the tags for the correct repo
fetch(`https://api.github.com/repos/${full_name}/tags`)
.then(res => res.json())
.then(data => {
obj.latest_tag = data[0].name
})
.catch(err => console.log(err))
return obj
})
this.setState({ repos })
})
.catch(err => console.log(err))
}
render() {
const { searchTerm, repos, favourites } = this.state
return (
<div className="App">
<h1>My Github Favorites</h1>
<input
type="text"
placeholder="search for a repo..."
value={searchTerm}
onChange={e => this.handleChange(e)}
onKeyPress={e => e.key === 'Enter' && this.findRepos()}
/>
<button
type="submit"
onClick={this.findRepos}>
Search
</button>
<div className="category-container">
<div className="labels">
<h5>Name</h5>
<h5>Language</h5>
<h5>Latest Tag</h5>
</div>
// Here I list the data
{repos.map(repo => (
<div key={repo.id}>
<a href={repo.html_url}>{repo.full_name}</a>
<p>{repo.language}</p>
{repo.latest_tag ? <p>{repo.latest_tag}</p> : <p>-</p>}
<button onClick={() => this.addToFavs(repo)}>Add</button>
</div>
))}
<h1>Favourites</h1>
{favourites.map(repo => (
<div key={repo.id}>
<a href={repo.html_url}>{repo.full_name}</a>
<p>{repo.language}</p>
<p>{repo.latest_tag}</p>
<button>Remove</button>
</div>
))}
</div>
</div>
)
}
}
export default App
If you use Promise.all(), you could rewrite your code like the following.
findRepos = () => {
const { searchTerm } = this.state;
// First api call here
const first = fetch(
`https://api.github.com/search/repositories?q=${searchTerm}&per_page=10&access_token=${
process.env.REACT_APP_TOKEN
}`
);
// Second api call here. I need the data from map to get the tags for the correct repo
const second = fetch(`https://api.github.com/repos/${full_name}/tags`);
Promise.all([first, second])
.then((res) => Promise.all(res.map(r => r.json())))
.then([data1, data2] => {
data1.then((firstData) => {
/*Do something you want for first.*/
});
data2.then((secondData) => {
/*Do something you want for second.*/
});
})
.catch((err) => console.log(err));
};
Hope this works for you.