Match background with users current weather conditions - reactjs

I am new to React, trying to learn and I have this unsolvable problem. I have developed a weather app, I'm still working on it, but at this moment I am stuck for 3 days trying to have a background image that changes depending on the users weather conditions. I have tried something using the icon, from openweather API. I used the same method to get the icon (image from my folder) to match users weather conditions.
import React from "react";
export default function Background(props) {
const codeMapping = {
"01d": "clear-sky-day",
"01n": "clear-sky-night",
"02d": "cloudy-day",
"02n": "cloudy-night",
"03d": "cloudy-day",
"03n": "cloudy-night",
"04d": "cloudy-day",
"04n": "cloudy-night",
"09d": "shower-rain-day",
"09n": "shower-rain-night",
"10d": "rain-day",
"10n": "rain-night",
"11d": "thunderstorm-day",
"11n": "thunderstorm-night",
"13d": "snow-day",
"13n": "snow-night",
"50d": "fog-day",
"50n": "fog-night",
};
let name = codeMapping[props.code];
return (
<img
className="background"
src={`background/${name}.jpg`}
alt={props.alt}
size="cover"
/>
);
}
So... in order to get "icon" of the input city by the user I have to call "<Background cod={weatherData.icon} alt={weatherData.description} />" from the function "Search" which is the function handling the submit form and running api call for input city. But the image is not showing(img1), but to have the img as a background I would call <Background> from my App function(img2), but in this case I will not have access to the real icon value from the input city. I should mention I have a folder in "src" called background and the images names match the codes name from the mapping.
Thank you in advance!
current preview of my app
how I see in other documentation I should set a background

You can pass the code from Search.js as the state.
App.js
const codeMapping = {
"01d": "clear-sky-day",
"01n": "clear-sky-night",
};
export const App = () => {
const [code, setCode] = useState(null) // <-- We'll update this from Search.js
const [backgroundImage, setBackgroundImage] = useState("")
useEffect(() => {
// Set background value based on the code
setBackgroundImage(codeMapping[`${code}`])
}, [code]); // <-- useEffect will run everytime the code changes
return (
<div style={{
height: '100px',
width: '100px',
backgroundImage: `${backgroundImage || "defaultBackgroundImage"}`
}}>
<Search setCode={setCode} />
</div>
)
}
Search.js
import { WeatherContext } from './App';
export const Search = ({ setCode }) => {
const handleClick = (apiResponse) => {
// Some API call returning the actual code value here //
setCode(apiResponse)
}
return (
<input
onClick={() => handleClick("01n")}
type="button"
value="Change city"
/>
)
}

Related

Why lottieRef returns only null?

Using the library "# lottiefiles/react-lottie-player"
You need to get lottieRef to interact with animation, but I get null.
Code reference: https://codesandbox.io/s/great-rgb-7dp4j0?file=/src/HorizontalPicker/HorizontalPicker.jsx
import React, {useEffect, useRef} from "react";
import {Player} from "#lottiefiles/react-lottie-player";
export default function App() {
const player = useRef(null)
const lottie = useRef(null)
useEffect(() => {
if(lottie && lottie.current){
console.log(lottie.current) //return null
}
}, [])
return (
<div className="App">
<Player
lottieRef={data => lottie.current = data}
ref={player}
onEvent={event =>{
if(event === "load"){
lottie.current.play() //nothing
}
}}
keepLastFrame={true}
autoplay={false}
loop={true}
src={"https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json"}
style={{width: "100%", height: "2.5em", padding: "0", margin: "0"}}/>
</div>
);
}
There is also an interesting point.
If you output lottie, we get an object with null (while there is something inside it)
And if you output lottie.current, we get null.
Reference to an example of this thing: https://ibb.co/RQWxLkJ
Can you pass the lottie ref into your ref like the following (see ref on the div):
export default function App() {
const player = useRef(null)
const lottie = useRef(null)
useEffect(() => {
if(lottie && lottie.current){
console.log(lottie.current) //return null
}
}, [])
return (
<div className="App">
<Player
lottieRef={data => lottie.current = data}
ref={player}
onEvent={event =>{
if(event === "load"){
lottie.current && lottie.current.play() //nothing
}
}}
keepLastFrame={true}
autoplay={false}
loop={true}
src={"https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json"}
style={{width: "100%", height: "2.5em", padding: "0", margin: "0"}}/>
</div>
);
}
This way I am able to get the animation object when I console lottie.current
I'd prefer to use useState to save the animationData rather than a ref. But there was also an issue on the player where the 'load' event was firing but the player hadn't finish setting its internal instance of the animation therefor calling play() wouldn't work. This was happening only on functional components in React that's why it went undiscovered.
I've made a few changes to your code and to the player, using v1.5.2 you should be able to accomplish what you're looking for:
import css from "./HorizontalPicker.module.css";
import React, { useRef, useState, useEffect } from "react";
import { Player } from "#lottiefiles/react-lottie-player";
const HorizontalPicker = () => {
const player = useRef(null);
// const lottie = useRef(null);
const [lottie, setLottie] = useState(null);
useEffect(() => {
if (lottie) {
console.log(" Lottie animation data : ");
console.log(lottie);
// You can also play by calling the underlying lottie method
// lottie.play();
}
}, [lottie]);
return (
<>
<Player
onEvent={(event) => {
// console.log(event);
if (event === "instanceSaved" && player && player.current) {
console.log("Playing animation..");
player.current.play();
}
}}
lottieRef={(data) => {
setLottie(data);
}}
ref={player}
keepLastFrame={true}
autoplay={false}
loop={true}
src={
"https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json"
}
/>
</>
);
};
export default HorizontalPicker;
Sandbox:
https://codesandbox.io/s/lottie-react-functional-component-2bs8fs?file=/src/HorizontalPicker/HorizontalPicker.jsx
Cheers!
Well, according to official documentation https://github.com/LottieFiles/lottie-react, lottieRef represents a callback function which is fired by Player component (and this function returns AnimationFrame object)
I'm not familiar with this library, and whatever I'll describe next are just my assumptions :) Seems that whenever player "plays", it displays frames one-by-one (from json file in "src" attribute"). And whenever player displays frames from .json file - Player fires "lottieRef" event which you utilize to set lottie.current. And player starts displaying frames only when it loads .json data using "src" parameter in Player definition (see network tab in your browser to ensure that additional http request presents)
And in this case everything seems pretty logical: you try to access "lottie" variable in useEffect of your component but it's empty as far as Player did not manage to display any frame yet because the callback (lottieRef) did not fire yet as far as .json file is not loaded yet. No matter if .json file is large or small, Player requests it via additional http call, which requires some (even minital) amount time. And useEffect fires before .json is loaded immediately after rendering DOM (that's how ReactJS works)
On the other hand, if you delay a bit request to "lottie" ref - you'll see that it is populated:
Code example:
import React, { useEffect, useRef } from 'react';
import { Player } from '#lottiefiles/react-lottie-player';
export default function App() {
const player = useRef(null);
const lottie = useRef(null);
useEffect(() => {
setTimeout(() => console.log(lottie), 50);
}, []);
return (
<div className="App">
<Player
lottieRef={(data) => (lottie.current = data)}
ref={player}
keepLastFrame={true}
autoplay={false}
loop={true}
src={
'https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json'
}
/>
</div>
);
}
So if you delay onEvent callback event a bit, "lottie" ref would be initialized by that moment and .play() would work:
onEvent={(event) => {
event === 'load' &&
setTimeout(
() => lottie.current && lottie.current.play()
);
}
}
BUT, if the only purpose you have is to execute Player whenever it's ready - why not to use "autoplay" built-in property ? (autoplay={true})
import React, { useRef } from 'react';
import { Player } from '#lottiefiles/react-lottie-player';
export default function App() {
const player = useRef(null);
const lottie = useRef(null);
return (
<div className="App">
<Player
lottieRef={(data) => (lottie.current = data)}
ref={player}
keepLastFrame={true}
autoplay={true}
loop={true}
src={
'https://lottie.host/2c01fd6c-437d-494e-af27-2a37e322bc60/prXv4Ic6px.json'
}
/>
</div>
);
}
Hope it'll help

Like Button with Local Storage in ReactJS

I developed a Simple React Application that read an external API and now I'm trying to develop a Like Button from each item. I read a lot about localStorage and persistence, but I don't know where I'm doing wrong. Could someone help me?
1-First, the component where I put item as props. This item bring me the name of each character
<LikeButtonTest items={item.name} />
2-Then, inside component:
import React, { useState, useEffect } from 'react';
import './style.css';
const LikeButtonTest = ({items}) => {
const [isLike, setIsLike] = useState(
JSON.parse(localStorage.getItem('data', items))
);
useEffect(() => {
localStorage.setItem('data', JSON.stringify(items));
}, [isLike]);
const toggleLike = () => {
setIsLike(!isLike);
}
return(
<div>
<button
onClick={toggleLike}
className={"bt-like like-button " + (isLike ? "liked" : "")
}>
</button>
</div>
);
};
export default LikeButtonTest;
My thoughts are:
First, I receive 'items' as props
Then, I create a localStorage called 'data' and set in a variable 'isLike'
So, I make a button where I add a class that checks if is liked or not and I created a toggle that changes the state
The problem is: I need to store the names in an array after click. For now, my app is generating this:
App item view
localStorage with name of character
You're approach is almost there. The ideal case here is to define your like function in the parent component of the like button and pass the function to the button. See the example below.
const ITEMS = ['item1', 'item2']
const WrapperComponent = () => {
const likes = JSON.parse(localStorage.getItem('likes'))
const handleLike = item => {
// you have the item name here, do whatever you want with it.
const existingLikes = likes
localStorage.setItem('likes', JSON.stringify(existingLikes.push(item)))
}
return (<>
{ITEMS.map(item => <ItemComponent item={item} onLike={handleLike} liked={likes.includes(item)} />)}
</>)
}
const ItemComponent = ({ item, onLike, liked }) => {
return (
<button
onClick={() => onLike(item)}
className={liked ? 'liked' : 'not-liked'}
}>
{item}
</button>
)
}
Hope that helps!
note: not tested, but pretty standard stuff

How to get 5 day weather forecast using React Hooks and OpenWeatherMap API

So I want to be able to show the 5 day weather forecast for a chosen city, using the OpenWeatherMap API and React.
I've seen a few tutorials online but they all use Class components, I want to use mine using a functional Component and the UseState hook.
I have this working code which allows me to get the CURRENT weather,location name and displays a little weather icon.
I want to be able to get the info for 5 days, and put it into a list. Specificially I want the high, low, main, description and an icon, for each day.
I'm really inexperienced at making API calls so I'm struggling to figure it out. I have my API key, and I think my API call should look something like this
https://api.openweathermap.org/data/2.5/weather?q=${placename},IE&appid=${apiKey}&units=metric
where placename is a prop I pass to it, and IE is my country code.
I was looking at this tutorial which does what I want, but it uses class-based components instead. I can't figure out how to do it without using classes.
https://medium.com/#leizl.samano/how-to-make-a-weather-app-using-react-403c88252deb
If someone could show me how to do this, that would be much appreciated. Here is my current code that gets just the current temperature.
export default function Weather (props) {
// State
const [apiData, setApiData] = useState({});
const [state, setState] = useState('Belfast');
var placename = props.placeprop
// API KEY AND URL
const apiKey = process.env.REACT_APP_API_KEY;
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${placename},IE&appid=${apiKey}&units=metric`;
useEffect(() => {
fetch(apiUrl)
.then((res) => res.json())
.then((data) =>
setApiData(data),);
}, [apiUrl]);
return (
<div className="weather">
<div>
{apiData.main ? (
<div>
<img
src={`http://openweathermap.org/img/w/${apiData.weather[0].icon}.png`}
alt="weather status icon"
/>
<br/>
{apiData.name}
<br/>
{apiData.main.temp}° C
</div>
)
: (
<h1>Loading</h1>
)}
</div>
</div>
)
} ```
This is not a complete answer but I came across this question so I'm sharing what I have.
import React, {useEffect, useState} from 'react';
import css from './Weather.module.css';
function useOpenWeather ({apiKey, lat, lon, units = 'metric'}) {
const [apiData, setApiData] = useState(null);
const apiUrl = `https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&appid=${apiKey}&units=${units}`;
useEffect(() => {
fetch(apiUrl)
.then((res) => res.json())
.then((data) => {
setApiData(data);
});
}, [apiUrl]);
return apiData;
}
function Weather ({lat, lon}) {
const weather = useOpenWeather({
apiKey: API_KEY
lat,
lon,
units: 'imperial'
});
return (
<div className={css.weather}>
{weather && weather.daily.slice(0, 5).map(d => (
<div>
<img
src={`http://openweathermap.org/img/w/${d.weather[0].icon}.png`}
alt={d.weather[0].main}
/>
<div>{d.temp.max} / {d.temp.min}</div>
</div>
))}
</div>
);
}
export default Weather;
.weather {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-gap: 16px;
margin: 16px;
}

useEffect cleanup when triggered by state

I have a component that I trigger via a state object:
const Alerts = () => {
const alert = useStore((state) => state.alert);
const setAlert = useStore((state) => state.setAlert);
const alertsModalRef = useRef(null);
useEffect(() => {
console.log("TRIGGER");
if (alert) {
alertsModalRef.current?.present();
}
}, [alert]);
...
I trigger it with a button click from another component on different screens. For example:
export function UpdateSomething() {
const setAlert = useStore((state) => state.setAlert);
return (
<View>
<Button
title="Continue"
onPress={() => {
setAlert("something");
}}
/>
</View>
);
}
When I load my app and hit a triggering button, I see "TRIGGER" is printed once to the console. However, when I go to another screen and hit another button or even when I then go back to the first screen and hit the same button again, it then prints "TRIGGER" three times.
I suspect that I need to add some kind of cleanup method to the useEffect - but if so, what is the cleanup required there?
And if it's not that? what is going on?
Sorry, I realize this may have been asked in various forms a million times before. I read the docs and many posts, but still confused.
Update 1
As Drew recommended, I tested and saw the Alerts component is re-mounted whenever I visit a screen in my app, which is using React Navigation.
I also wrap each screen with the following Provider:
Navigator
const WrappedSearchScreen = withModalProvider(SearchScreen);
function SearchNavigator() {
const SearchStack = createStackNavigator();
return (
<SearchStack.Navigator>
<SearchStack.Screen
name="Search"
component={WrappedSearchScreen}
/>
...
</SearchStack.Navigator>
);
}
Provider
...
import { Alerts } from "./Alerts";
import { useStore } from "./store";
export const withModalProvider = (Component: FC) => () => {
const progressBarVisible = useStore((state) => state.progressBarVisible);
return (
<BottomSheetModalProvider>
<Component />
<Alerts />
<ProgressBar
indeterminate
color="green"
visible={progressBarVisible}
style={{ position: "absolute", bottom: 0, left: 0 }}
/>
</BottomSheetModalProvider>
);
};

Show loading state but also show previous results in React Concurrent gives a warning

UPDATE: Ok, it I misunderstood useDeferredValue, I thought it was more like a debounced value but it's not, you can define the timeout to be the time the old results will be shown.
So
const search = useDeferredValue(value, { timeoutMs: 10000 })
Gave me the desired effect, only it still show the warning right know.
Original
I want to have a search with the results below it, the search result should filter immediately based on the input of the text field. Then the query should be done debounced and the old results should show also when it takes less than e.g. 3000 m.s.
I'm working with the new concurrent mode in React and Relay experimental. I used the new useDeferredValue, documented on this page: https://reactjs.org/docs/concurrent-mode-reference.html#usetransition
But I got this warning:
Warning: Asynchronous triggered a user-blocking update that suspended.
The fix is to split the update into multiple parts: a user-blocking update to provide immediate feedback, and another update that triggers the bulk of the changes.
Refer to the documentation for useTransition to learn how to implement this pattern
I don't get this since it works but it still gives me a warning.
My code:
import React, {
Suspense,
useState,
// #ts-ignore - useDeferredValue does not exist yet in types
useDeferredValue,
// #ts-ignore - useDeferredValue does not exist yet in types
// useTransition,
useCallback,
ChangeEvent,
} from 'react'
import TextField from '#material-ui/core/TextField'
import LinearProgress from '#material-ui/core/LinearProgress'
import { graphql } from 'babel-plugin-relay/macro'
import { useLazyLoadQuery } from 'react-relay/hooks'
import {
FlowBlockFinderQuery,
FlowBlockFinderQueryResponse,
} from '../__generated__/FlowBlockFinderQuery.graphql'
import ErrorBoundaryWithRetry from '../helpers/ErrorBoundaryWithRetry'
interface RenderFuncProps {
search: string
filterSearch: string
}
function QueryResults({ search, filterSearch }: RenderFuncProps) {
const { blocks }: FlowBlockFinderQueryResponse = useLazyLoadQuery<
FlowBlockFinderQuery
>(
graphql`
query FlowBlockFinderQuery($search: String) {
blocks(search: $search) {
id
title
description
slug
blockType
}
}
`,
{ search },
{ fetchPolicy: 'store-or-network' }
)
return (
<div>
{blocks
.filter(
block =>
!filterSearch ||
block.title.toLowerCase().includes(filterSearch.toLowerCase())
)
.map(block => (
<div key={block.id} style={{ fontSize: 19 }}>
{block.title}
</div>
))}
</div>
)
}
function Results({ search, filterSearch }: RenderFuncProps) {
return (
<>
Zoekterm: {filterSearch}
<ErrorBoundaryWithRetry
fallback={({ error }) => <div>Er is iets foutgegaan</div>}
>
<Suspense fallback={<LinearProgress />}>
<QueryResults search={search} filterSearch={filterSearch} />
</Suspense>
</ErrorBoundaryWithRetry>
</>
)
}
export default function Asynchronous() {
const [value, setValue] = useState('')
// const [search, setSearch] = useState('')
const search = useDeferredValue(value, { timeoutMs: 3000 })
// const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG)
const onInputChange = useCallback(
(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
// startTransition(() => {
setValue(event.currentTarget.value)
// })
},
[setValue]
)
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<TextField
label="Nieuw of bestaand blok"
fullWidth
variant="outlined"
value={value}
onChange={onInputChange}
/>
<br />
<Results search={search} filterSearch={value} />
</div>
)
}
React docs "if some state update causes a component to suspend, that state update should be wrapped in a transition". You have to make the async request suspense compatible and fetch the query in useTransition.
Here is an example from react docs
function handleChange(e) {
const value = e.target.value;
// Outside the transition (urgent)
setQuery(value);
startTransition(() => {
// Inside the transition (may be delayed)
setResource(fetchTranslation(value));
});
}
And the link to code sandbox

Resources