Summing information from API/divs - reactjs

I got access to the API and I stored them using useState. I have a question - how can I easily sum values of two divs together in React? I don't want to use jQuery, I want it to be nice and clean. Any ideas? Here you can see a part of the relevant code. Maybe useRef?
const [details, setDetails] = useState({})
const search = evt => {
if (evt.key === "Enter") {
fetch(`${api.base}forecast?q=${query}&units=metric&APPID=${api.key}`)
.then(res => res.json())
.then(result => {
setDetails(result);
setQuery('');
console.log(result);
return result;
})
----
<div className="detail--icon">Morning: {Math.round(details.daily[1].temp.morn)}°c</div>
<div className="detail--icon">Day: {Math.round(details.daily[1].temp.day)}°c</div>
<div className="detail--icon">Night: {Math.round(details.daily[1].temp.night)}°c</div>
<div className="detail--icon">Humidity: {details.daily[1].humidity}%</div>

Not sure what do you mean by sum values of divs, but given that the content you're showing in the repeating divs differs quite a bit (Math.round is not used everywhere, suffixes are different, data is not part of a single object, e.g. first three are part of details.daily[1].temp, but last one is part of details.daily[1]), I'd suggest two options:
A helper function which accepts the content between the tags (content param will be JSX):
const getDetail = (content) => {
return (
<div className="detail--icon">{content}</div>
)
}
And use it like:
{getDetail(<>Morning: {Math.round(details.daily[1].temp.morn)}°c</>)}
Note that you need to put <> and </> in-between to make it a valid JSX (this is shorthand for React.Fragment).
Or a small component (probably slightly less amount of code):
const Detail = (props) => (
<div className="detail--icon">{props.children}</div>
)
...
<Detail>Morning: {Math.round(details.daily[1].temp.morn)}°c</Detail>
You can go further by storing all of the content of the rows in an array of JSX elements and do a Array.map on it, but I'm not sure how readable that would be.
Another thing apart from the markup refactoring I'd suggest is to store details.daily[1] or details.daily[1].temp in a variable before rendering and just write daily or daily.temp where necessary.

Related

Handling form with single state in React for web app

I have an idea to create simple web app for my own needs. I would like user to be able to use picklist to define number of form fields needed (ex. 1 - 10). Picklist would create chosen number of components with text inputs. User can type string into text input and all data would be stored as single state. I could do it just simply saying string1, string2 and having 10 states but I wonder if this would be doable as single state because I would like all text inputs to create single string in form of token.
Example:
User choose 3 text inputs:
hi
hello
goodbye
This would form token of {hi/hello/goodbye}.
Please let me know how to handle that with React.
Thanks.
Probably you can solve this using an array of objects inside your state and latter assessing to the values. I'm not sure what do you want to do, but if you want to create an app that generates dynamically inputs and after set the values in the corresponding state, you can do something like this.
export default function DinamicLinks() {
const [quantity, setQuantity] = useState(0)
const [messages, setMessages] = useState([])
const createInputs = (quantity) => {
const params = []
for (let i = 1; i <= quantity; i++){
let param = {name:i};
params.push(param)
}
return params
}
const handleMessages = (passedMessage) => {
const filteredMessages = messages.filter((message) => message.name !== passedMessage.name)
setMessages([...filteredMessages, passedMessage])
}
return (
<div className="App">
<button onClick={() => setQuantity(quantity + 1)}>More inputs</button>
<button onClick={() => setQuantity(quantity - 1)}>Less inputs</button>
{createInputs(quantity).map(({name}) => <input key={name} onChange={(e) => handleMessages({name, message:e.currentTarget.value})}></input>)}
<button onClick={() => console.log(messages)}>See messages</button>
</div>
);
}
This code will go to generate inputs dynamically and will go to store his values inside an state, that store objects inside an array, later you can implement the logic to handle the text in the way you whatever you want. By the way, this code isn't the best in performance, but I want to give you and easy solution quickly. Undoubtedly there are better ways to do this, and also is valid to mention, don't abuse the arrow functions like I did in this example, are an easy ways to have trouble of performance in React.

React - toggle css class but do not rerender everything

I have a Gatsby website and I want to use it to show flashcards. I have found multiple existing solutions online, but I would like to try it a different way. However, I am completely stuck.
Desired result:
Show one random word at a time. When the users clicks on a button 'show', the meaning of the word should be shown.
Issue:
I can generate a random combination of the words. But when the user clicks on 'show', a new combination is loaded instead of the answer. I understand why this happens. However, I do not understand how to fix this.
I have tried this (stripped to the bare essentials):
export const Flashcard = () => {
const [flip, setFlip] = useState(false)
const onButtonClick = () => {
setFlip(!flip)
}
let word = words[GenerateRandomNumber(0, words.length - 1)] // words is an array containing all the possible combinations
return (
<div className={`card ${flip ? "flip" : ""}`}>
<div className="front">
{word["language1"]}
</div>
<div className="back">
{word["language2"]}
</div>
<button onClick={onButtonClick}>Show answer</button>
</div>
)
}
export default Flashcard
I feel like I am on a totally wrong path, but don't see how to fix it. Should I structure my logic different? Should I use some other hooks? Or anything else?
In addition, I would also like the user having the possibility to say he knew the meaning (clicking on a button 'correct') or not (button 'wrong'). If correct, a new word is shown, if wrong, the word is stored to be shown later again. How should I trigger this logic?
I think you should add a state to store the picked word
const [word, setWord] = useState("");
and When you do a click you need to get a random word and store in word
useEffect(() => {
const randomNumber = GenerateRandomNumber(0, words.length - 1);
const newWord = words[randomNumber];
setWord(newWord);
}, [flip]); //This useEffect will call everytime when flip change.
in return you can refer the word
{word["language1"]}
here is a demo

is filter best way to remove items in array? (react.js)

I'm learning react and making todo app and etc.
but all the tutorial only shows using filter to remove items in array.
I'll show you simple code.
const ProductItem = () => {
const [users, setUser] = useState([
{ id: 1, name: "jenny" },
{ id: 2, name: "tom" },
{ id: 3, name: "juan" },
]);
const removeUser = (user) => {
//this is filter way
// let newList = users.filter((x) => x.id !== user.id);
// setUser([...newList]);
//this is delete item by index way
users.splice(users.indexOf(user), 1);
setUser([...users]);
};
return (
<div>
{users &&
users.map((x) => (
<div key={x.id}>
<li>{x.name}</li>
<button onClick={() => removeUser(x)}>remove</button>
</div>
))}
</div>
);
};
export default ProductItem;
and both way is working correctly , but I just wonder if there's any reason that peoples using filter way?
There is no hard and fast rule to use .filter all the time.
but all the tutorial only shows using filter to remove items in array.
Below are the main reasons:
.filter always returns a new array with the results and keep the original array intact.
React won't perform re-rendering if the reference has not changed or if it's mutated.
In your case, you can write setUser in one line a below if you use .filter
setUser(users.filter((x) => x.id !== user.id));
Where as .splice mutates the original users array and you should use spread operator while setting data with setUser else React won't perform re-rendering of component.
Remember one thumb rule less lines of code ~ less bugs.
In Javascript, an array's length can be changed after it is initialized.
(I.E., a Javascript Array can be updated like a Linked List of C language)
splice() function can perform add and remove operations on an array.
splice() can be used when the requirement is to update the original data.
More information on splice():
https://www.w3schools.com/jsref/jsref_splice.asp
filter() function only selects a subset of elements that match a criteria, but does not delete anything from the original array.
filter() function need to be used when the original data must be kept intact.
More information on filter():
https://www.w3schools.com/jsref/jsref_filter.asp

React: check if elements in different components overlap

I am looking at many answers of checking if elements overlap, but they are not applicable.
I have a wrapper component that has a header with position fixed and children
const Wrapper = ({ children }) => {
return (
<>
<Header/>
{children}
</>
)
};
export default Wrapper
I need to know when the header is overlapping certain parts in several different pages (children) so as to change the header color
I am trying to detect in the wrapper component, but the elements do not exist or are not accesible to the container
Do I need to pass refs all the way from the wrapper to all the children? Is there an easier way to do this?
Thanks
there are a few approaches i can think of, one being the one you mentioned and passing refs around and doing a bunch of calculations and a few others below.
Hardcoded heights of the pages
This would work basically by having a large switch case in you header file and check the offset scroll position of your scroll target.
getBackgroundColor = (scrollPosition) => {
switch (true) {
case scrollPosition <= $('#page1').height:
return 'red'
case scrollPosition <= $('#page1').height + $('#page2').height:
return 'blue'
case scrollPosition <= $('#page1').height + $('#page2').height + $('page3').height
return 'green'
default:
return 'yellow'
}
}
This has obvious flaws, one being, if the page content is dynamic or changes frequently it may not work, checking height every re-render may cause reflow issues, and this requires knowledge of page IDs on the page. (note: this snippet is just to prove concept, it will need tweeks).
Intersection Observer (Recommended way)
Intersection observer is an awesome API that allows you to observe elements in a performant way and reduces layout thrashing from the alternative ways with constant measurements
heres an example I made with react and intersection observer, https://codesandbox.io/s/relaxed-spence-yrozp. This may not be the best if you have hundreds of targets, but I have not ran any benchmarks on that so not certain. Also it is considered "Experimental" but has good browser support (not IE).
here is the bulk of the codesandbox below just to get an idea.
React.useEffect(() => {
let headerRect = document.getElementById("header").getBoundingClientRect();
let el = document.getElementById("wrapper");
let targets = [...document.getElementsByClassName("block")];
let callback = (entries, observer) => {
entries.forEach(entry => {
let doesOverlap = entry.boundingClientRect.y <= headerRect.bottom;
if (doesOverlap) {
let background = entry.target.style.background;
setColor(background);
}
});
};
let io = new IntersectionObserver(callback, {
root: el,
threshold: [0, 0.1, 0.95, 1]
});
targets.forEach(target => io.observe(target));
return () => {
targets.forEach(target => io.unobserve(target));
};
}, []);
ALso notice this is not the most "React" way to do things since it relys a lot on ids, but you can get around that by passing refs everywhere i have used dom selections, but that may become unwieldy.

Setting span labels with a loop

I pulled data with request and now I'm trying to present each result on my site. I get an array with genres each time, so I need some ideas on how I could use loops in JSX in order to put those spans in result div that already has images, heading and stuff.
setGenres = () => {
let data = this.props.data.genres;
let labelsText = '';
for (let i = 0; i < data.length; i++) {
labelsText += <span className='genre'>data[i]</span>
}
return (labelsText);
}
<div className='result-info'>
<h4>{this.props.data.name}</h4>
{this.setGenres()}
</div>
Back when I was using vanilla JS, I could use string and put it via innerHTML, but now I have no idea what to do. It just returns [Object object] on my site.
You can use map to iterate over the array and return another result. So it's possible to return a portion of JSX which will be rendered inside your div:
<div className='result-info'>
<h4>{this.props.data.name}</h4>
{this.props.data.genres.map((genre,idx)=>(
<span key={idx} className='genre'>{genre}</span>
)}
</div>
JSX generates element classes for React to use. React allows rendering of various things, including arrays of React classes. In your scenario setGenres needs to change to the following.
setGenres = () => {
const data = this.props.data.genres;
const spans = data.map(genre => (<span key={genre} className='genre'>{genre}</span>));
return spans;
}
As noted by another user, I added the key prop as react will complain otherwise. It's best to set the key to something guaranteed to be unique, rather than just an index.
You can read more about array mapping here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
You can read more about lists in react here - https://reactjs.org/docs/lists-and-keys.html

Resources