in reactJs onChangeHandler method is not working properly - javascript

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)

Related

Component is loading twice [duplicate]

I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through Firestore. Everything seems to be working fine except it renders twice... The first one renders the phone number and zero points. The second time it renders all the data is displayed correctly. Can someone guide me to the solution.
class Update extends Component {
constructor(props) {
super(props);
const { match } = this.props;
this.state = {
phoneNumber: match.params.phoneNumber,
points: 0,
error: ''
}
}
getPoints = () => {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
const docRef = database.collection('users').doc(user.uid).collection('customers').doc(this.state.phoneNumber);
docRef.get().then((doc) => {
if (doc.exists) {
const points = doc.data().points;
this.setState(() => ({ points }));
console.log(points);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
const error = 'This phone number is not registered yet...'
this.setState(() => ({ error }));
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
} else {
history.push('/')
}
});
}
componentDidMount() {
if(this.state.phoneNumber) {
this.getPoints();
} else {
return null;
}
}
render() {
return (
<div>
<div>
<p>{this.state.phoneNumber} has {this.state.points} points...</p>
<p>Would you like to redeem or add points?</p>
</div>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
);
}
}
export default Update;
You are running your app in strict mode. Go to index.js and comment strict mode tag. You will find a single render.
This happens is an intentional feature of the React.StrictMode. It only happens in development mode and should help to find accidental side effects in the render phase.
From the docs:
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:...
^ In this case the render function.
Official documentation of what might cause re-rendering when using React.StrictMode:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
This is because of React Strict Mode code.
Remove -> React.StrictMode, from ReactDOM.render code.
Will render 2 times on every re-render:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Will render 1 time:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);
React is rendering the component before getPoints finishing the asynchronous operation.
So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.
If you want, you can show a loader or an indicator that the data is being fetched and is not ready yet to display with conditional rendering.
Just add another Boolean key like isFetching, set it to true when you call the server and set it to false when the data is received.
Your render can look something like this:
render() {
const { isFetching } = this.state;
return (
<div>
{isFetching ? (
<div>Loading...</div>
) : (
<div>
<p>
{this.state.phoneNumber} has {this.state.points} points...
</p>
<p>Would you like to redeem or add points?</p>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
)}
</div>
);
}
React.StrictMode, makes it render twice, so that we do not put side effects in following locations
constructor
componentWillMount (or UNSAFE_componentWillMount)
componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
componentWillUpdate (or UNSAFE_componentWillUpdate)
getDerivedStateFromProps
shouldComponentUpdate
render
setState updater functions (the first argument)
All these methods are called more than once, so it is important to avoid having side-effects in them. If we ignore this principle it is likely to end up with inconsistent state issues and memory leaks.
React.StrictMode cannot spot side-effects at once, but it can help us find them by intentionally invoking twice some key functions.
These functions are:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
This behaviour definitely has some performance impact, but we should not worry since it takes place only in development and not in production.
credit: https://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/
it is done intentionally by react to avoid this
remove
<React.StrictMode> </React.StrictMode>
from index.js
I worked around this by providing a custom hook. Put the hook below into your code, then:
// instead of this:
useEffect( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
}, []);
// do this:
useEffectOnce( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
});
Here is the code for the hook:
export const useEffectOnce = ( effect => {
const destroyFunc = useRef();
const calledOnce = useRef(false);
const renderAfterCalled = useRef(false);
if (calledOnce.current) {
renderAfterCalled.current = true;
}
useEffect( () => {
if (calledOnce.current) {
return;
}
calledOnce.current = true;
destroyFunc.current = effect();
return ()=> {
if (!renderAfterCalled.current) {
return;
}
if (destroyFunc.current) {
destroyFunc.current();
}
};
}, []);
};
See this blog for the explanation.
Well, I have created a workaround hook for this. Check this, if it helps:
import { useEffect } from "react";
const useDevEffect = (cb, deps) => {
let ran = false;
useEffect(() => {
if (ran) return;
cb();
return () => (ran = true);
}, deps);
};
const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === "development";
export const useOnceEffect = isDev ? useDevEffect : useEffect;
CodeSandbox Demo: https://github.com/akulsr0/react-18-useeffect-twice-fix
React internally monitors & manages its render cycles using its virtual dom and its diffing algorithms, so you need not worry about the number of re-renders. Let the re-renders to be managed by react. Even though the render function is getting invoked, there are sub components which doesn't gets refreshed on ui, if there is no props or state change inside it. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.
So in your case, since you have a setstate defined inside the getPoints function, it tells react to rerun the diffing process through the render function.

Testing in React/Redux - how to guarantee that state has been updated?

I'm writing tests for my new React app, part of my intention with this project is to fully understand this testing thing - it's been on my radar for a while but I haven't put it into production before.
Written a fair number of test so far that are using snapshots and other static & synchronous approaches. This seems to work fine until now where I'm dealing with a setState -> expect(postFunctionState).toEqual(desiredState) situation and while I console.log my way through the flow and can see that setState() is being called and I can see the results in the browser, I can't seem to write a test that replicates the behaviour.
Here's the relevant code:
//Component (extracted):
export class CorsetCreator extends React.Component {
constructor(props) {
super(props);
this.state = {
productName: '',
productType: 'Overbust',
enabled: false,
created: false,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleNameChange = this.handleNameChange.bind(this);
this.handleProductChange = this.handleProductChange.bind(this);
}
handleNameChange(e) {
this.setState({ productName: e.target.value });
this.handleChange.bind(this)(e);
}
handleProductChange(e) {
this.setState({ productType: e.target.value });
this.handleChange.bind(this)(e);
}
handleChange(e) {
e.preventDefault();
if (e.target.value === '') {
this.setState({ enabled: false }); //User should not be able to submit an empty product name
return;
}
const { corsets } = this.props.corsetGallery;
if (!corsets) {
this.forceUpdate();
this.setState({ enabled: true }); //Point of this exercise is to guarantee unique combos of name&type. If there are no pre-existing corsets then no need to test for uniqueness
return;
}
const productType =
e.target.value === 'Underbust' || e.target.value === 'Overbust'
? e.target.value
: this.state.productType;
const productName =
e.target.value === 'Underbust' || e.target.value === 'Overbust'
? this.state
: e.target.value;
const filteredCorsets = corsets.filter(
corset => corset.type === productType && corset.name === productName,
);
this.setState({
enabled: !(filteredCorsets && filteredCorsets.length > 0),
});
}
//Test (extracted)
it('handles statechanges correctly with a valid new corset', () => {
const store = configureStore({}, browserHistory);
const creator = mount(
<Provider store={store}>
<CorsetCreator />
</Provider>,
);
const namebox = creator.find('NameBox').at(0);
const nameBoxField = namebox.find('input').at(0);
const submitbutton = creator.find('SubmitButton').at(0);
creator.setState({ enabled: false });
expect(submitbutton.props().enabled).toEqual(false);
nameBoxField.simulate('change', { target: { value: 'Test' } });
creator.update();
expect(creator.state().enabled).toEqual(true);
});
Because setState is asynchronous I feel like some sort of callback or promise may be the solution here but I've tried both and can't seem to sort through the best way. What is the best way to think about this type of scenario?
TL;DR: Remember React components are functions. In all their glory, they take in props and you receive an output of the render() function. Test the output.
If you have a variable in the state, chances are you're passing it to a child component or manipulating the visual output of the current component. Or said item in state would be useless :)
Testing the state is redundant, as it's like testing React itself.
A concern one normally raises is "But I'm showing/hiding that element by using setState(...)", or "I'm passing the state down into one of the children as a prop".
When writing tests, render the component. Simulate an action and check if the output of the render function has changed.
Take this component for example:
class TextWithClick extends React.Component {
state={ count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1})
}
render() {
return (
<input
value={this.state.count} {/* state passed down as props */}
onClick={this.handleClick}
/>
)
}
}
ReactDOM.render(<TextWithClick/>
, document.getElementById('root'))
It's simple. Clicking on the input field, increases the text it shows, which is a prop. Here are some test assertions:
// using enzyme
it("should increase the count", () => {
const wrapper = shallow(<TextWithClick />);
const input = wrapper.find("input").at(0);
// test props
input.simulate("click");
expect(input.props().value).toEqual(1);
input.simulate("click");
expect(input.props().value).toEqual(2);
input.simulate("click");
expect(input.state().count).toEqual(3); // this tests React more than your component logic :)
});
Remember React components are functions. In all their glory, they take in props and you receive an output of the render() function. Test the output.
In the case of Redux, same thing. Testing state change is like testing Redux's connect() functionality. Mozilla uses a real redux store to test their app. I.e. test the final output.
I quote from the above link regarding testing a React/Redux app (can't seem to to multi-line blockquotes in SO:
"We dispatch real Redux actions to test application state changes. We test each component only once using shallow rendering.
"We resist full DOM rendering (with mount()) as much as possible.
"We test component integration by checking properties.
"Static typing helps validate our component properties.
"We simulate user events and make assertions about what action was dispatched.
A good article: Testing React Component’s State by Anthony Ng.

React with Redux: error on propType property

I'm new in the React world. I got a course to training React and Redux.
Like yesterday I got an error while I'm attending an online training
Even though, I walk through the author course and copy the code from the screen I get an error:
Warning: Failed propType: Required prop courses was not specified in CoursesPage. Check the render method of Connect(CoursesPage).
I have uploaded my code to github: https://github.com/tarcisiocorte/reactredux/blob/master/src/components/course/CoursesPage.js
again....I will appreciate some help.
import React, {PropTypes} from "react";
import {connect} from 'react-redux';
import * as courseActions from '../../actions/courseActions';
class CoursesPage extends React.Component {
constructor(props, context){
super(props, context);
this.state = {
course:{title: ""}
};
this.onTitleChange = this.onTitleChange.bind(this);
this.onClickSave = this.onClickSave.bind(this);
}
onTitleChange(event){
const course = this.state.course;
course.title = event.target.value;
this.setState({course: course});
}
courseRow(course, index){
return <div key={index}>{course.title}</div>;
}
onClickSave() {
this.props.dispatch(courseActions.createCourse(this.state.course));
}
render() {
return (
<div>
<h1>Courses</h1>
{this.props.courses.map(this.courseRow)}
<h1>Add Courses</h1>
<input
type="text"
onChange={this.onTitleChange}
value={this.state.course.title} />
<input
type="submit"
value="Save"
onClick={this.onClickSave} />
</div>
);
}
}
CoursesPage.propTypes = {
dispatch: PropTypes.func.isRequired,
courses: PropTypes.array.isRequired
};
function mapStateToProps(state, ownProps) {
return{
courses: state.courses
};
}
export default connect(mapStateToProps)(CoursesPage);
In https://github.com/tarcisiocorte/reactredux/blob/master/src/index.js#L11
You need to specify a default for courses.
You have specified that your courses prop is required:
courses: PropTypes.array.isRequired
so you need to pass in something from the redux store and by the looks of it the courses property in your redux store is undefined. (Put a breakpoint here to check that is actually the case)
You can either make sure your redux store always returns something for your courses or your can remove the isRequired constrain:
CoursesPage.propTypes = {
dispatch: PropTypes.func.isRequired,
courses: PropTypes.array
};
In your 'Routes' component, you'll want to change
<Route path="courses" component={CoursesPage} />
to
<Route path='courses' render={(stuff) => (
<CoursePage courses={stuff} />
)}/>
When you use component, you can't add your required props, so render would be a good alternative. This also means you'll have to add redux connections to your routes.js since you need to get that information from somewhere.
Another, more simpler, solution would be just to eliminate courses as a prop and get that information directly from redux when CoursePage loads up. You've already done half the battle with your mapStateToProps, therefore you dont need to have it with the "isRequired" in your propTypes. This is basically when Klugjo said, so if you decide to take this approach, give him credit.
I'd also hazard a guess that if 'courses' in your store doesn't exist, your isRequired is being triggered as well. So you might be able to keep isRequired as long as you have your data for that prop in the store.
For anyone coming across a similar failed prop type error, such as below, or if the other answers did not resolve your issue, the following might be an alternate fix for you. In the context of user428745's post above, someProjects and ResponsiblePage in the error below would correspond to the courses prop (some array of values) and the CoursesPage component, respectively.
Given user428745's setup below
CoursesPage.propTypes = {
dispatch: PropTypes.func.isRequired,
courses: PropTypes.array.isRequired
};
function mapStateToProps(state, ownProps) {
return {
courses: state.courses
};
}
The issue might be related to how the redux state gets the state.courses value in the first place. In my case, the prop (ie. courses as in state.courses) in mapStateToProps was being set before the data was available from the redux store. This happened due to an API data call that had not yet finished. My fix was:
function mapStateToProps(state, ownProps) {
return {
courses: state.courses || [] // Equivalent to statement below
//courses: state.courses == null ? [] : state.courses
};
}
If state.courses is null (due to API data not loaded yet) we return [] to satisfy the array requirement on our prop. If it is valid, which means the data was available and was put inside of state.courses, then we simply return state.courses similar to before.
Note also that there might be different required configuration setups (to make redux work properly), ie. depending on how you link your reducer(s) to your root reducer (which would be the content in index.js inside of reducers folder). If the error is still not fixed with these changes, try another approach with the root reducer, such as:
// From this (see `user428745`'s source files, where 'courseReducer' was imported as 'courses')
export default combineReducers({
courseReducer
});
// To this
export default combineReducers({
rootReducer: courseReducer
});
// Where 'mapStateToProps' would also have to change
function mapStateToProps(state, ownProps) {
return {
courses: state.rootReducer.courses || []
};
}
And where you intend to use this value, ie. with this.props.courses or props.courses in your CoursesPage setup, you could console log the values (or whatever you wanted to do) only when the array is not empty:
if (props.courses.length > 0) {
console.log(props.courses);
}
Or maybe listen to props.courses changes so that you perform something only "in the moment" after it changes (whereas the if statement above would be valid at all times, from when the prop was filled with values):
useEffect(() => {
if (props.courses.length > 0) {
console.log(props.courses);
}
}, [props.courses]);
Note that if you use useEffect, make sure it is within your CoursesPage component, and not in the "root" of the file where you would ie. write export default CoursesPage.

Why is my React component is rendering twice?

I don't know why my React component is rendering twice. So I am pulling a phone number from params and saving it to state so I can search through Firestore. Everything seems to be working fine except it renders twice... The first one renders the phone number and zero points. The second time it renders all the data is displayed correctly. Can someone guide me to the solution.
class Update extends Component {
constructor(props) {
super(props);
const { match } = this.props;
this.state = {
phoneNumber: match.params.phoneNumber,
points: 0,
error: ''
}
}
getPoints = () => {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
const docRef = database.collection('users').doc(user.uid).collection('customers').doc(this.state.phoneNumber);
docRef.get().then((doc) => {
if (doc.exists) {
const points = doc.data().points;
this.setState(() => ({ points }));
console.log(points);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
const error = 'This phone number is not registered yet...'
this.setState(() => ({ error }));
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
} else {
history.push('/')
}
});
}
componentDidMount() {
if(this.state.phoneNumber) {
this.getPoints();
} else {
return null;
}
}
render() {
return (
<div>
<div>
<p>{this.state.phoneNumber} has {this.state.points} points...</p>
<p>Would you like to redeem or add points?</p>
</div>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
);
}
}
export default Update;
You are running your app in strict mode. Go to index.js and comment strict mode tag. You will find a single render.
This happens is an intentional feature of the React.StrictMode. It only happens in development mode and should help to find accidental side effects in the render phase.
From the docs:
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:...
^ In this case the render function.
Official documentation of what might cause re-rendering when using React.StrictMode:
https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
This is because of React Strict Mode code.
Remove -> React.StrictMode, from ReactDOM.render code.
Will render 2 times on every re-render:
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Will render 1 time:
ReactDOM.render(
<>
<App />
</>,
document.getElementById('root')
);
React is rendering the component before getPoints finishing the asynchronous operation.
So the first render shows the initial state for points which is 0, then componentDidMount is called and triggers the async operation.
When the async operation is done and the state been updated, another render is triggered with the new data.
If you want, you can show a loader or an indicator that the data is being fetched and is not ready yet to display with conditional rendering.
Just add another Boolean key like isFetching, set it to true when you call the server and set it to false when the data is received.
Your render can look something like this:
render() {
const { isFetching } = this.state;
return (
<div>
{isFetching ? (
<div>Loading...</div>
) : (
<div>
<p>
{this.state.phoneNumber} has {this.state.points} points...
</p>
<p>Would you like to redeem or add points?</p>
<div>
<button>Redeem Points</button>
<button>Add Points</button>
</div>
</div>
)}
</div>
);
}
React.StrictMode, makes it render twice, so that we do not put side effects in following locations
constructor
componentWillMount (or UNSAFE_componentWillMount)
componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
componentWillUpdate (or UNSAFE_componentWillUpdate)
getDerivedStateFromProps
shouldComponentUpdate
render
setState updater functions (the first argument)
All these methods are called more than once, so it is important to avoid having side-effects in them. If we ignore this principle it is likely to end up with inconsistent state issues and memory leaks.
React.StrictMode cannot spot side-effects at once, but it can help us find them by intentionally invoking twice some key functions.
These functions are:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
This behaviour definitely has some performance impact, but we should not worry since it takes place only in development and not in production.
credit: https://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/
it is done intentionally by react to avoid this
remove
<React.StrictMode> </React.StrictMode>
from index.js
I worked around this by providing a custom hook. Put the hook below into your code, then:
// instead of this:
useEffect( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
}, []);
// do this:
useEffectOnce( ()=> {
console.log('my effect is running');
return () => console.log('my effect is destroying');
});
Here is the code for the hook:
export const useEffectOnce = ( effect => {
const destroyFunc = useRef();
const calledOnce = useRef(false);
const renderAfterCalled = useRef(false);
if (calledOnce.current) {
renderAfterCalled.current = true;
}
useEffect( () => {
if (calledOnce.current) {
return;
}
calledOnce.current = true;
destroyFunc.current = effect();
return ()=> {
if (!renderAfterCalled.current) {
return;
}
if (destroyFunc.current) {
destroyFunc.current();
}
};
}, []);
};
See this blog for the explanation.
Well, I have created a workaround hook for this. Check this, if it helps:
import { useEffect } from "react";
const useDevEffect = (cb, deps) => {
let ran = false;
useEffect(() => {
if (ran) return;
cb();
return () => (ran = true);
}, deps);
};
const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === "development";
export const useOnceEffect = isDev ? useDevEffect : useEffect;
CodeSandbox Demo: https://github.com/akulsr0/react-18-useeffect-twice-fix
React internally monitors & manages its render cycles using its virtual dom and its diffing algorithms, so you need not worry about the number of re-renders. Let the re-renders to be managed by react. Even though the render function is getting invoked, there are sub components which doesn't gets refreshed on ui, if there is no props or state change inside it. Every setstate function call will inform react to check the diffing algorithm, and invoke the render function.
So in your case, since you have a setstate defined inside the getPoints function, it tells react to rerun the diffing process through the render function.

React.js - onChange undefined

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.

Categories