I'm writing a website. It's a website that uses and fetches data from a stock API to displays stock prices and charts.
I have a search bar which changes state whenever a letter is typed in.. However, it's causing problems because it's instantly updating the state, which then fetches data from the API, and if for example I type in Z - then the API is instalty looking for a stock named "Z"
and the app crashes, goes blank ( because such link doesnt exist ). I literally have to copy the stock name like "AAPL" for example and then paste it - for the app to work properly.
var baseUrl = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${search}&apikey=**********`;
because state of "search" is updated and it's searching for that stock/
Anyways, here's my stock search bar component.
const StockSearchBar = () => {
const { search, setSearch } = useContext(SearchContext); // i'm using context API to store state so I can access it across different components
const handleClick = (e) => {
if (setSearch !== '') { // if there's something in state, then post data to backend
const options =
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({search}),
};
const response = fetch(`http://localhost:3001/api/search`, options);
};
function handleButtonEvents(e) { // i'm using a function to chain handleClick() and setTimeout() to be run when button is clicked
e.preventDefault();
handleClick();
setTimeout(() => window.location.reload(), 3000); // delay the function for 3 seconds to give it time to fetch the data and display it, otherwise it might refresh the page without updating it with new data
}
const handleSearch = e => {
e.preventDefault();
setSearch(e.target.value.toUpperCase()); // i think the problem is here - i need to delay setting state
}
return (
<>
<div className="searchBar">
<label>Look for Stocks or Cryptos: </label>
<input type='text' onChange={handleSearch} onKeyPress={(ev) => {
if (ev.key === "Enter") { handleButtonEvents(); }}}
{/* search if Enter key is pressed */} // i think the problem is with **onChange** or the handleSearch **function** but not sure how to deal with it
placeholder={search} required />
<Button variant="success" type="submit" onClick={handleButtonEvents} >Search</Button>
</div>
<h1 className="src">{search}</h1>
{// display state to see if it's changing ( just testing }
</>
);
};
export default StockSearchBar;
Let me know if you want to see the context API file, or something else. But I think the probem is in the search bar component.
Thank you in advance for help.
you need to use Debounce in order to create a delay e.g. from lodash. so in your case it would be
const handleSearch = e => {
e.preventDefault();
debounce(setSearch(e.target.value.toUpperCase()), 500)// debounce 500 milliseconds
}
Related
I'm using Antd Input library, whenever I type in the start or in the middle of the word my cursor jumps to the end.
const handleOpenAnswer =( key, value )=>{
handleFieldChange({
settings: {
...settings,
[key]: value
}
})
}
return (
<Input
required
size='default'
placeholder='Label for Diference Open Answer Question'
value='value'
onChange={({ target: { value } }) => {
handleOpenAnswer('differenceOpenAnswerLabel', value)
}}
/>
The reason why your cursor always jumps to the end is because your parent component gets a new state and therefore re-renders its child components. So after every change you get a very new Input component. So you could either handle the value change within the component itself and then try to pass the changed value up to the parent component after the change OR (and I would really recommend that) you use something like React Hook Form or Formik to handle your forms. Dealing with forms on your own can be (especially for complex and nested forms) very hard and ends in render issues like you face now.
Example in React-Hook-Form:
import { FormProvider, useFormContext } = 'react-hook-form';
const Form = () => {
const methods = useForm();
const { getValues } = methods;
const onSubmit = async () => {
// whatever happens on submit
console.log(getValues()); // will print your collected values without the pain
}
return (
<FormProvider {...methods}>
<form onSubmit={(e) => handleSubmit(onSubmit)(e)>
{/* any components that you want */}
</form>
</FormProvider>
);
}
const YourChildComponent = () => {
const { register } = useFormContext();
return (
<Input
{...register(`settings[${yourSettingsField}]`)}
size='default'
placeholder='Label for Diference Open Answer Question'
/>
)
}
I have a simple flashcard app that posts information about the flashcard to the backend whenever the user clicks on their answer choice. It posts the question, the answer choices, a unique id for each flashcard, the answer that was selected, and the correct answer. The next thing I want to send to the backend is the user's name that they enter into a form at the top of the page. The problem that I'm running into is that the function that posts the data to the backend is inside my flashcard.js which structures each flash card so I can't put the form on that file because then it is on every flashcard displayed but I just want the form displayed at the top.
Here is what my flashcard.js file looks like, the data is posted in the checkGuess function:
export default function Flashcard({ flashcard }) { // recieving flashcard prop from our
mapping in flashcardlist.js, each w a unique id
const MAX_TRIES = 4
// const [incorrect, setIncorrect] = useState(incorrect)
const [guess, setGuess] = useState(0)
const [flip, setFlip] = useState(false)
const [height, setHeight] = useState('initial') //sets the state for our initial height to be
replaced by the max height
const frontEl = useRef() // lets us have a reference from the front and back through every
rerendering of them
const backEl = useRef()
// const callDouble = () =>{
// checkGuess();
// postData();
// }
async function postData() {
}
const checkGuess = (answer, stateValue) => {
try {
console.log(stateValue)
let result = fetch('http://127.0.0.1:5000/post', {
method: 'POST',
mode: 'no-cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
key: `${Date.now()}`,
question: flashcard.question,
answer: flashcard.answer,
options: flashcard.options,
guess: answer,
user: stateValue
})
});
} catch(e) {
console.log(e)
}
if (answer === flashcard.answer) {
setFlip(true)
return
}
if (guess + 1 === MAX_TRIES) {
setFlip(true)
}
setGuess(guess + 1)
// setIncorrect(true)
}
function setMaxHeight() {
const frontHeight = frontEl.current.getBoundingClientRect().height //gives us dimensions of
the rectangle but we only need the height
const backHeight = backEl.current.getBoundingClientRect().height
setHeight(Math.max(frontHeight, backHeight, 100)) // sets the height (setHeight) to the
maximum height of front or back but the minimum is 100px
}
useEffect(setMaxHeight, [flashcard.question, flashcard.answer, flashcard.options]) //anytime any
of these change then the setMaxHeight will change
useEffect(() => {
window.addEventListener('resize', setMaxHeight) //everytime we resize our browser, it sets
the max height again
return () => window.removeEventListener('resize', setMaxHeight) //removes the eventlistener
when component destroys itself
}, [])
return (
<div
onClick={() => postData()}
className={`card ${flip ? 'flip' : ''}`} // if flip is true classname will be card and flip,
if flip isnt true it will just be card
style={{ height: height }} //setting height to the variable height
// onClick={() => setFlip(!flip)} // click changes it from flip to non flip
>
<div className="front" ref={frontEl}>
{flashcard.question}
<div className='flashcard-options'>
{flashcard.options.map(option => {
return <div key={option} onClick={() => checkGuess(option)} className='flashcard-
option'>{option}</div>
})}
</div>
</div>
<div onClick={() => setFlip(!flip)} className='back' ref={backEl}>
{flashcard.answer}
</div>
</div>
)
}
// setting the front to show the question and the answers by looping through the options to make
them each an option with a class name to style
// back shows the answer
so i've decided to render my form inside my flashcardList.js file because that is where all the cards from flashcard.js get sent and then displayed on the page in a grid like format. the form which is inside NameForm.js is rendered before the flashcards so that it appears above the flashcards. here is the flashcardlist.js file, it's very simple:
import React from 'react'
import Flashcard from './Flashcard'
export default function FLashcardList({ flashcards }) {
// taking in the flashcards as destructured props so we dont have to make a props. variable
return (
// card-grid is a container so we can put all the cards in a grid to ensure they change in size
proportionally to the size of the window //
<div className='card-grid'>
{flashcards.map(flashcard => { // loops through the flashcards api and maps each one to
flashcard
return <Flashcard flashcard={flashcard} key={flashcard.id} /> // each flashcard is then
passed down to the "Flashcard.js" component we created returned w a unique id
})}
</div>
)
}
and then here is my nameform.js file which creates the form, provides an alert to the browser displaying the name that was entered and setting the state:
import React from "react"
export default class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = (event) => {
this.setState({value: event.target.value});
}
handleSubmit = (event) => {
alert('A name was submitted: ' + this.state.value);
this.props.checkGuess(this.props.answer, this.state.value);
event.preventDefault();
}
checkGuess() {
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
}
The form works and the alert works displaying the correct name but when i try to console log stateValue inside the checkGuess function inside flashcard.js, it returns 'undefined' in the console so I'm clearly not lifting the state correctly because the alert to the browser displays the correct name that was inserted but the console log inside the flashcard.js function is undefined. does anybody know how to solve this? id really appreciate any help. let me know if you need any other information, my backend is just one simple python file that receives the data from the post request and sends it to the database. that works fine with the old data i just need to add the user id to this data.
I am trying to get only females from an array using a filter, but on the first attempt react query returns the whole array, after that it is working fine. Any idea what property I have to add or remove, so this side effect disappears.
Here is my code:
import React, { useState } from "react";
import { useQuery } from "react-query";
import getPersonsInfo from "../api/personCalls";
export default function Persons() {
const [persons, setPersons] = useState([]);
const { data: personData, status } = useQuery("personsData", getPersonsInfo, {
onSuccess: (data) => {
setPersons(data.data);
},
onError: (error) => {
console.log(error);
}
});
const getFemaleOnlyHandler = () => {
const result = personData.data.filter(
(person) => person.gender === "female"
);
setPersons(result);
};
return (
<>
<button onClick={getFemaleOnlyHandler}>Female only</button>
{status === "loading" ? (
<div>Loading ... </div>
) : (
<div>
{persons.map((person) => (
<div>
<p>{person.name}</p>
<p>{person.lastName}</p>
<p>{person.address}</p>
<p>{person.gender}</p>
<p>{person.country}</p>
<p>{person.city}</p>
</div>
))}
</div>
)}
</>
);
}
I added the full code in code sandbox: https://codesandbox.io/s/relaxed-drake-4juxg
I think you are making the mistake of copying data from react-query into local state. The idea is that react-query is the state manager, so the data returned by react-query is really all you need.
What you are experiencing in the codesandbox is probably just refetchOnWindowFocus. So you focus the window and click the button, react-query will do a background update and overwrite your local state. This is a direct result of the "copy" I just mentioned.
What you want to do is really just store the user selection, and calculate everything else on the fly, something like this:
const [femalesOnly, setFemalesOnly] = React.useState(false)
const { data: personData, status } = useQuery("personsData", getPersonsInfo, {
onError: (error) => {
console.log(error);
}
});
const getFemaleOnlyHandler = () => {
setFemalesOnly(true)
};
const persons = femalesOnly ? personData.data.filter(person => person.gender === "female") : personData.data
you can then display whatever you have in persons, which will always be up-to-date, even if a background update yields more persons. If the computation (the filtering) is expensive, you can also use useMemo to memoize it (compute it only when personData or femalesOnly changes - but this is likely a premature optimization.
I'm not totally familiar with react-query however the problem is likely that it is re-fetching (async!) everytime the component updates. Since setPersons() triggers an update (ie. sets state) it'll update the new persons state to be the filtered female list and then trigger a fetch of all persons again which comes back and sets the persons state back to the full list (ie. see what happens when you click the female filter button and then just leave it).
There is a more idiomatic way to achieve this in React which is to keep a "single source of truth" (ie. all the persons) and dynamically filter that based on some local ui state.
For example see below where data becomes the source of truth, and persons is a computed value out of that source of truth. This has the benefit that if your original data changes you don't have to manually (read: imperatively) update it to also be females only. This is the "unidirectional data flow" and "reactivity" people always talk about and, honestly, it's what makes React, React.
const { data = { data: [] }, status } = useQuery(
"personsData",
getPersonsInfo,
{
onSuccess: (data) => {},
onError: (error) => {
console.log(error);
}
}
);
const [doFilterFemale, setFilterFemale] = useState(false);
const persons = doFilterFemale
? data.data.filter((person) => person.gender === "female")
: data.data;
https://codesandbox.io/s/vigorous-nobel-9n117?file=/src/Persons/persons.jsx
This is ofc assuming you are always just loading from a json file. In a real application setting, given a backend you control, I would always recommend implementing filtering, sorting and pagination on the server side otherwise you are forced to over-fetch on the client.
So I have a form that contains the data about two tables on the backend:
table zone, and
table area
Normally, the update method in react-admin is just straight forward, but in this case not really.So I have taken the data from the zone and area tables and put it in one form.
I also altered the saveButton to tweak the form values before submitting the form according to this react-admin documentation so in this way values are in correct form before submitting.
The challenge is that...
When I click the button somehow in onClick, I would need to execute the update on 2 endpoints with 2 update calls to dataProvider. I was thinking to use redux-saga for this case, but didn't have luck yet.
Any ideas, hints, or code examples are welcome.
Two alternatives are possible here:
Call the two dataProvider.updates within a single click. Alternatively, you can fetch() directly for greater flexibility. (You might want to make use of React.useCallback and async for better performance)
export const SaveButton = ({
basePath = '',
label = 'Save',
record,
...rest
}) => {
const handleClick = () => {
if (record !== undefined) {
// first call -> Zone
async () => await dataProvider.update(...);
// Second call -> Area
async() => await dataProvider.update(...);
}
};
return <Button label={label} onClick={handleClick} {...rest} />;
};
Declare two functions to call each call dataProvider.update separately.This would give you flexibility if you intend to do the same calls once any other clickEvent is triggered within that same component
export const SaveButton = ({
basePath = '',
label = 'Save',
record,
...rest
}) => {
const handleClick = () => {
if (record !== undefined) {
// first call -> Zone
handleZoneCall();
// second call -> Area
handleAreaCall();
}
};
const handleZoneCall = async () => await dataProvider.update(...);
const handleAreaCall = async() => await dataProvider.update(...);
return <Button label={label} onClick={handleClick} {...rest} />;
};
One of the above should get you going...
I have a search screen, contain Input And TopTabs "Songs, Artists",
When I get data from API after a search I make two things
1- I setState to appear the TopTab Component "true/false"
2- dispatch an action to save Songs & Artists Data in redux store.
that works fine.
But in topTab component, as I say before I have tow tabs "songs, artists"
For example, In the Songs component, I want to manipulate the data to achieve my case so in componentDidMount I Map the songs array from redux and push the new data into the component state.
But it's not working fine!
At the first time, I got songs from redux as empty [] although it's saved successfully in redux store when I get data from API
So how can I handle this case to not mutate the data?
Search.js "Main screen"
onSearch = async () => {
const {searchText} = this.state;
if (searchText.length > 0) {
this.setState({onBoarding: false}); // to appear the TopTab Component
try {
let response = await API.post('/search', {
name: searchText,
});
let {
data: {data},
} = response;
let artists = data.artists.data;
let songs = data.traks.data;
this.props.getResult(songs, artists);
}
catch (err) {
console.log(err);
}
}
render(){
<View style={styles.searchHeader}>
<Input
onChangeText={text => this.search(text)}
value={this.state.searchText}
onSubmitEditing={this.onSearch}
returnKeyType="search"
/>
</View>
{this.state.onBoarding ? (
<SearchBoard />
) : (
<SearchTabNavigator /> // TopTabs component
)}
}
SongsTab
...
componentDidMount() {
console.log('props.songs', this.props.songs); // Empty []
let All_tunes = [];
if (this.props.songs?.length > 0) {
console.log('mapping...');
this.props.songs.map(track =>
All_tunes.push({
id: track.id,
name: track.name,
url: URL + track.sounds,
img: URL + track.avatar,
}),
);
this.setState({All_tunes});
}
}
...
const mapStateToProps = state => {
return {
songs: state.searchResult.songs,
};
};
Edit
I fix the issue by using componentDidUpdate() life cycle
If you have any other ways tell me, please!
SongsTab
manipulateSongs = arr => {
let All_tunes = [];
arr.map(track =>
All_tunes.push({
id: track.id,
name: track.name,
url: URL + track.sounds,
img: URL + track.avatar,
}),
);
this.setState({All_tunes});
};
componentDidMount() {
if (this.props.songs?.length > 0) {
this.manipulateSongs(this.props.songs);
console.log('mapping...');
}
}
componentDidUpdate(prevProps) {
if (prevProps.songs !== this.props.songs) {
this.manipulateSongs(this.props.songs);
}
}
The problem you're referring to has to do with the way asynchronous code is handled in JavaScript (and in turn react-redux). When your component initially mounts, your redux store passes its initial state to your SongsTab.js component. That seems to be an empty array.
Any API call is an asynchronous action, and won't update the redux store until the promise has resolved/rejected and data has been successfully fetched. Any HTTP request takes much longer to complete than painting elements to the DOM. So your component loads with default data before being updated with the response from your API call a number of milliseconds later.
The way you've handled it with class-based components is fine. There are probably some optimizations you could add, but it should work as expected. You might even choose to render a Spinner component while you're fetching data from the API as well.
If you want a different approach using more modern React patterns, you can try and use the equivalent version with React hooks.
const Songs = ({ fetchSongs, songs, ...props }) => {
React.useEffect(() => {
// dispatch any redux actions upon mounting
// handle any component did update logic here as well
}, [songs])
// ...the rest of your component
}
Here are the docs for the useEffect hook.