I've got the last.fm API working in my app, I'm grabbing related artists. So the idea is you search for an artist and it returns a list of related artists.
If I use 'onClick' it works perfect because it grabs the input value, but I want to use 'onChange' and it seems to returning the wrong results. I think it's because it's undefined at some stages or something!
Any ideas how to fix this?
// AJAX
import axios from "axios";
module.exports = {
fetchArtistCategory: (artist) => {
let key = "12345";
let encodedURI = window.encodeURI('http://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist=' + artist + '&api_key=' + key + '&format=json');
return axios.get(encodedURI)
.then((res) => {
return res
});
}
}
// App
import React from "react";
import {render} from "react-dom";
import Artists from "./components/Artists.js";
import Api from "./components/Api.js";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
artist: [],
}
this.searchArtist = this.searchArtist.bind(this);
}
componentDidMount() {
}
searchArtist() {
Api.fetchArtistCategory(this.searchArtists.value)
.then((res) => {
this.setState({
artist: res.data.similarartists.artist
});
})
}
render() {
return (
<div>
<Artists artistValue={this.state.artist} />
<input type="text" placeholder="Search artist" ref={(ref) => this.searchArtists = ref} onChange={this.searchArtist} />
<input type="submit"/>
</div>
)
}
}
render(<App />, document.getElementById("main"));
It's hard to diagnose your problem without knowing what you mean by "returning the wrong results". My initial guess is that you're thinking about onChange incorrectly.
onchange fires for every single text change. If I typed in "Bob Dylan" into your text field, onChange would fire once for each change as I type (eg. "B", "Bo", "Bob", "Bobe", "Bob"(delete e), "Bob "...). Thus you'll be firing a lot of API requests. This is fine for autocomplete, but requires rate-limiting, smart response ordering, etc., and is a different use case than yours.
It sounds like you only want to fire one API request once the user has completed their thought. To do this, you should try the onfocusout or onblur attributes -- as their names imply, they will fire when the user has completed their input and left the text field.
shouldn't you be passing an event through with the onChange handler?
searchArtist(event) {
Api.fetchArtistCategory(event.target.value)
.then((res) => {
this.setState({
artist: res.data.similarartists.artist
});
})
}
^ The only piece of code you should need to change if everything else if correct
And you need the ref unless I'm misunderstanding. You get the value of the input field as event.target.value inside of the event handler.
Related
I am trying to make a TO DO list app using Reactjs. onChangeTitleHandler is not setting the state of title when I type the title and the same happen with the onChangeTaskHandler. the new state is not setting up.
This is the Cockpit component.
const cockpit = (props) => {
return (
<div>
<form onSubmit={props.submitted}>
<input type="text" placeholder="Title" onChange={props.ChangeTitle} />
<textarea rows={2} onChange={props.changeTask} />
<input type='submit' value='ADD TASK'/>
</form>
</div>
);
}
This is the code that I've tried.
this is my App.js file
import "./App.css";
import { React, Component } from "react";
import Cockpit from "./Components/Cockpit";
class App extends Component {
state = {
title:'',
task: '',
allTask : []
};
onChangeTitleHandler = (event)=>{
this.setState={
title: event.target.value,
}
console.log(this.state.title);
}
onChangeTaskHandler =(event)=>{
this.setState={
task: event.target.value,
}
console.log(this.state.title);
}
onSubmitHandler =(event) => {
const tasks = this.state.allTask;
tasks.push({
title:this.state.title,
task:this.state.task
})
this.setState={
allTask:tasks
}
console.log(tasks);
event.preventDefault();
};
render() {
return (
<div className="App">
<h1 className="heading">Prioritise Your Tasks</h1>
<Cockpit
ChangeTitle = {this.onChangeTitleHandler}
changeTask={this.onChangeTaskHandler}
submitted = {this.onSubmitHandler}
/>
</div>
);
}
}
export default App;
I want that onSubmit the new state of title and task added in to the allTask array.
There is a lot going on confusingly or wrongly in your example.
The main thing is that pointed out by Nils: that you are assigning a value to the setState function rather than using it as intended (calling it).
So instead of
onChangeTaskHandler =(event)=>{
this.setState={
task: event.target.value,
}
console.log(this.state.title);
}
you need to do
onChangeTaskHandler = event => {
this.setState({ task: event.target.value })
}
Which leads me to one other thing that may be confusing your "output", which is that your are logging this.state.title immediately after calling the this.setState function. This won't have your expected outcome as the setState function is asynchronous, and therefore may not be as fast as you need it to for it to show the updated state in the console.log call.
As a latter point, from my personal point of view, your code style is somewhat confusing in the way you are naming things. Calling "submitted" to the onSubmit function handler sounds more of a boolean than of a function. Having properties starting with uppercase letters like ChangeTitle may be commonly interpreted as that being a react node or a class of some sort.
Also, for your example, a good idea would be to split the state of the task container, from that of the task itself. Having the container state being just the array, and each task item handling it's own state (title and description). This would give I think greater clarity to what you're trying to achieve.
But, conventions and clarity aside, I'd recommend that you follow the React hands-on tutorial to go through a whole bunch of concepts and good practices that may be of help and will save you a lot of time of try and error (at least this works for me)
I have been learning js and then React.js over the last few weeks, following tutorials on Codecademy and then Educative.io (to learn with the new hooks, rather than the class-based approach). In an attempt to apply what I have learned I have been messing around creating a number of common website features as React components on a hello-world project.
Most recently I have been trying to make a search component, which uses the Spotify API to search for a track, but have been running into synchronisation issues which I can't quite figure out how to solve using the js synchronisation tools that I know of. I come from a Java background so am more familiar with mutexes/semaphores/reader-writer locks/monitors so it may be that I am missing something obvious. I have been basing the code on this blog post.
In my implementation, I currently have a SongSearch component, which is passed its initial search text as a property, as well as a callback function which is called when the input value is changed. It also contains searchText as state, which is used to change the value of the input.
import * as React from 'react';
interface Props {
initialSearchText: string,
onSearchTextUpdated: (newSearchText: string) => void;
}
export const SongSearch = (props: Props) => {
const [searchText, setSearchText] = React.useState(props.initialSearchText);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newSearchText = e.target.value;
setSearchText(newSearchText);
props.onSearchTextUpdated(newSearchText);
}
return <input value={searchText} onChange={onChange}/>;
};
The results are currently just displayed a list in the SearchResults component, the values of which are passed as an array of songs.
import * as React from 'react';
import { SongInfo } from './index';
interface Props {
songs: SongInfo[]
}
export const SearchResults = (props: Props) => {
return (
<ul>
{props.songs.map((song) => {
return <li key={song.uri}>{song.name}</li>
})}
</ul>
);
}
In the App component, I pass a callback function which sets the state attribute searchText to the new value. This then triggers the effect which calls updateSongs(). If we have an auth token, and the search text isn't empty we return the results of the API call, otherwise we return an empty list of songs. The result is used to update the tracks attribute of the state using setTracks().
I have cutdown the code in App.tsx to only the relevant parts:
import SpotifyWebApi from 'spotify-web-api-js';
import React from "react";
// ... (removed irrelevant code)
async function updateSongs(searchText: string): Promise<SongInfo[]>{
if (spotify.getAccessToken()) {
if (searchText === '') {
console.log('Empty search text.');
return [];
} else {
// if access token has been set
const res = await spotify.searchTracks(searchText, {limit: 10});
const tracks = res.tracks.items.map((trackInfo) => {
return {name: trackInfo.name, uri: trackInfo.uri};
});
console.log(tracks);
return tracks;
}
} else {
console.log('Not sending as access token has not yet');
return [];
}
}
function App() {
// ... (removed irrelevant code)
const initialSearchText = 'Search...';
const [tracks, setTracks] = React.useState([] as SongInfo[]);
const [searchText, setSearchText] = React.useState(initialSearchText);
React.useEffect(() => {
updateSongs(searchText)
.then((newSongs) => setTracks(newSongs))
}, [searchText]);
const content = <SearchResults songs={tracks}/>;
return (
<ThemeProvider theme={theme}>
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
<Root config={mui_config}>
<Header
renderMenuIcon={(open: boolean) => (open ? <ChevronLeft /> : <MenuRounded />)}
>
<SongSearch initialSearchText={initialSearchText} onSearchTextUpdated={(newSearchText) => {
console.log(`New Search Text: ${newSearchText}`)
setSearchText(newSearchText);
}}/>
</Header>
<Nav
renderIcon={(collapsed: boolean)=>
collapsed ? <ChevronRight /> : <ChevronLeft />
}
classes={drawerStyles}
>
Nav
</Nav>
<StickyFooter contentBody={content} footerHeight={100} footer={footerContent}/>
</Root>
</div>
</ThemeProvider>
);
}
export default App;
The issue that I am having is that when I type in the name of a long song and then hold down backspace sometimes songs remain displayed in the list even when the search text is empty. From inspection of the console logs in the code I can see that the issue arises because the setTracks() is sometimes called out of order, in particular when deleting 'abcdef' quickly setTracks() the result of updateTracks('a') will be called after the result of updateTracks(''). This makes sense as '' does not require any network traffic, but I have spent hours trying to work out how I can synchronise this in javascript with no avail.
Any help on the matter would be greatly appreciated!
In your case the results are coming back differently because you send multiple events, and the ones that come first - fire a response and then you display it.
My solution would be to use a debounce function on the onChange event of the input field. So that the user will first finish typing and then it should start the search. Although there still might be some problems, if one search has started and the user started typing something else then the first one has finished and the second one has started and finished. In this you might find that cancelling a request helpful. Unfortunately you can't cancel a Promise, so you would have to read about RxJS.
Here's a working example using debounce
P.S.
You might find this conference talk helpful to understand how the event loop is working in JS.
I am new to React and trying to modify this application: https://github.com/nice-table/bitmex-scaled-orders
My goal is, say the prop "instrumentData" found in "src/modules/orders/OrderForm.js" has "instrumentData.lastprice" value changing to a specific value in real-time in the backend. I want to submit the form on that page if the value reaches a specific value. In other words, I want to keep monitoring that prop untill it hits a number and it will submit the form upon that. Is that doable through states? I tried to research it but given I am new to React I am a bit lost as to what code to use and where exactly to add it.
Thanks.
Autosubmitting is simple
It's simple to run some action on data change. React components are data driven - autoupdating. You can just insert a function 'into data flow'.
Your data source is in DataContext then you should use <DataContext.Consumer /> to get data 'stream' - stream because it's frequently updated using socket connection.
<DataContext.Consumer>
{ (data, submitForm, isSubmitting) => {
console.log("context data", data );
// extract data from `data` object
// const someData = data.someProperty;
// if( someData > 12345 ) {
// if( !isSubmitting ) {
// submitForm()
// }
// return "Limit reached"
// }
// return null
}}
</ DataContext.Consumer>
This snippet can be placed almost anywhere after this code:
render={({
values,
errors,
touched,
setFieldValue,
submitForm,
isSubmitting,
isValid,
validateForm
}) => (
<React.Fragment>
// place it here
... and of course before end of this fragment (</ React.Fragment>).
You can pass and use almost all functions defined in this component (file), f.e. setFieldValue("priceUpper", to update form value before submitting.
Autosubmitting is NOT so simple
Problem is not trivial. You should create a component with internal logic to:
set limit (render input, onChange handler, useState) instead of hardcoded value
block(or not? checkbox?) autosubmitting in a loop (formik will submit but later it will clear isSubmitting flag - our component will autosubmit again)
render context consumer inside - optimize rerenderings
etc.
Good luck ;)
React has a really good write up on forms and handling onChange() which is an event that can fire when a field is changed https://reactjs.org/docs/forms.html.
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
submit = () => {
// submit form
// eg axios.post(URL, {
// value: this.state.value
// })
}
handleChange(event) {
this.setState({value: event.target.value});
if (this.state.value == 10) {
this.submit()
}
}
return (
<form>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
</form>
);
}
}
I see the github repo is using hooks in lieu of component classes.
You can read about hooks here https://reactjs.org/docs/hooks-intro.html.
onChange can still call handle change and instead of this.state you may be using useState
I have a question regarding a React filter search input.
I have two Components the the moment.
FirstPageComponent.js
import React from "react"
import AutoComplete from "./AutoComplete"
export class FirstPageComponent extends React.Component {
constructor() {
super()
this.state = {
rows: [],
loading: true,
}
}
async componentDidMount(searchTerm = "marvel") {
await fetch(
"https://api.themoviedb.org/3/search/movie?api_key=&query=" +
searchTerm
)
.then(response => response.json())
.then(responseData => {
this.setState({
rows: responseData.results,
loading: false,
})
})
.catch(error => {
console.log("Error fetching and parsing data", error)
})
}
render() {
console.log(this.state.rows)
return <AutoComplete movies={["hej"]} />
}
}
and
AutoComplete.js
import React from "react"
export default class AutoComplete extends React.Component {
constructor(props) {
super(props)
this.state = {
rows: [],
loading: false,
userInput: "",
}
}
onChange = e => {
const { movies } = this.props
const userInput = e.currentTarget.value
const rows = movies.filter(
suggestion =>
suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
)
this.setState({
rows,
loading: true,
userInput: e.currentTarget.value,
})
}
onClick = e => {
this.setState({
rows: [],
loading: false,
userInput: e.currentTarget.innerText,
})
}
render() {
const {
onChange,
onClick,
state: { rows, loading, userInput },
} = this
let moviesListComponent
if (loading && userInput) {
if (rows.length) {
moviesListComponent = (
<ul>
{rows.map((movie, index) => {
return (
<li key={index} onClick={onClick}>
{movie}
</li>
)
})}
</ul>
)
} else {
moviesListComponent = (
<>
<p>Couldn't find any movies. Try searching for another movie.</p>
</>
)
}
}
return (
<React.Fragment>
<input type="search" onChange={onChange} value={userInput} />
{moviesListComponent}
</React.Fragment>
)
}
}
I basically want to know if i’m approaching this the right way. I want to make a dynamic request for movie titles from fetch(”https://themovie.db”) API.
Send down the movie titles as props and then filter the values in autocomplete component if the user input is similar to the movie title props.
Am i thinking about this the right way? I've tried to have the fetch call in the same component as AutoComplete but haven't gotten it to work as i want it to. How would you setup the component structure if you'd be solving this issue for example?
Feel free to ask if there’re any confusions.
Thanks
These are my assumptions about the above code, let me know if any are incorrect.
You're doing an initial API request for a list of movies by search term. That search term is given outside of the code above and doesn't change by any mechanism above.
You're further filtering the data returned by the API request dynamically via the AutoComplete component.
Most of your React code here looks pretty good, but I've got some recommendations:
In general, I find it's best to keep the list of things completely separate, so I would create a separate MovieList function component that simply takes an array of movie objects, and a titleFilter string and renders them.
componentDidMount doesn't take arguments. searchTerm should be a prop of FirstPageComponent (presumably set by the parent component by some kind of selection or user input)
I'd rename AutoComplete to TitleFilter or the like. It's not really auto-completing, it's more of a filter field.
This line: if (loading && userInput) { will result in displaying no movies when the user hasn't yet filled in text into the filter field. I'm not sure that is what you would want.
This line is a bit misleading: <p>Couldn't find any movies. Try searching for another movie.</p> because you could have just filtered out all the results returned. In this case, it wasn't that you couldn't find movies, it's that your filter is too restrictive.
For the onClick handler, I'm not sure such a general approach is best. It could create a strange interaction if you were to somehow click on the <ul>, for example. To improve this, I'd recommend each movie row component implement its own onClick and call a function to set the filter.
You're going to have to deal with pagination on this, I'd bet. Check your API and see what it does for that and make sure you're accounting for it.
Small nits:
userInput: e.currentTarget.value, could be shortened to userInput,, which is shorthand for userInput: userInput, (you already have a variable named userInput.)
For key props, an actual unique id would be better, in the case you change your filtering and index numbers don't line up with the unique rows you're rendering anymore.
I like to go ahead and create a separate component for each row, so I'd make a MovieRow function component which takes a movie object and renders it. It just keeps the code a bit cleaner. (and makes #6 above easier to implement)
I'm creating a hackernews-clone using this API
This is my component structure
-main
|--menubar
|--articles
|--searchbar
Below is the code block which I use to fetch the data from external API.
componentWillReceiveProps({search}){
console.log(search);
}
componentDidMount() {
this.fetchdata('story');
}
fetchdata(type = '', search_tag = ''){
var url = 'https://hn.algolia.com/api/v1/search?tags=';
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.props.getData(data.hits);
});
}
I'm making the API call in componentDidMount() lifecycle method(as it should be) and getting the data correctly on startup.
But here I need to pass a search value through searchbar component to menubar component to do a custom search. As I'm using only react (not using redux atm) I'm passing it as a prop to the menubar component.
As the mentioned codeblock if I search react and passed it through props, it logs react once (as I'm calling it on componentWillReceiveProps()). But if I run fetchData method inside componentWillReceiveProps with search parameter I receive it goes an infinite loop. And it goes an infinite loop even before I pass the search value as a prop.
So here, how can I call fetchdata() method with updating props ?
I've already read this stackoverflow answers but making an API call in componentWillReceiveProps doesn't work.
So where should I call the fetchdata() in my case ? Is this because of asynchronous ?
Update : codepen for the project
You can do it by
componentWillReceiveProps({search}){
if (search !== this.props.search) {
this.fetchdata(search);
}
}
but I think the right way would be to do it in componentDidUpdate as react docs say
This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
componentDidMount() {
this.fetchdata('story');
}
componentDidUpdate(prevProps) {
if (this.props.search !== prevProps.search) {
this.fetchdata(this.props.search);
}
}
Why not just do this by composition and handle the data fetching in the main HoC (higher order component).
For example:
class SearchBar extends React.Component {
handleInput(event) {
const searchValue = event.target.value;
this.props.onChange(searchValue);
}
render() {
return <input type="text" onChange={this.handleInput} />;
}
}
class Main extends React.Component {
constructor() {
this.state = {
hits: []
};
}
componentDidMount() {
this.fetchdata('story');
}
fetchdata(type = '', search_tag = '') {
var url = 'https://hn.algolia.com/api/v1/search?tags=';
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.setState({ hits: data.hits });
});
}
render() {
return (
<div>
<MenuBar />
<SearchBar onChange={this.fetchdata} />
<Articles data={this.state.hits} />
</div>
);
}
}
Have the fetchdata function in the main component and pass it to the SearchBar component as a onChange function which will be called when the search bar input will change (or a search button get pressed).
What do you think?
Could it be that inside this.props.getData() you change a state value, which is ultimately passed on as a prop? This would then cause the componentWillReceiveProps function to be re-called.
You can probably overcome this issue by checking if the search prop has changed in componentWillReceiveProps:
componentWillReceiveProps ({search}) {
if (search !== this.props.search) {
this.fetchdata(search);
}
}