-
+ const cities = useSelector((state) => state.cities.cities);
+ const dispatch = useDispatch();
+ const [newCity, setNewCity] = useState("");
-
- Get started by editing
- app/page.js
-
-
- By{' '}
-
-
-
-
- {
+ if (!cities.some((city) => city.name.toLowerCase() === newCity.toLowerCase())) {
+ dispatch(addCity(newCity));
+ }
+ setNewCity("");
+};
+
+ return (
+
+
Weather Forecast
+
+ setNewCity(e.target.value)}
+ placeholder="Add a city"
+ className="form-control"
/>
+
-
-
-
-
+ );
}
diff --git a/app/store/configureStore.js b/app/store/configureStore.js
new file mode 100644
index 0000000..9afb8ce
--- /dev/null
+++ b/app/store/configureStore.js
@@ -0,0 +1,8 @@
+import { configureStore } from "@reduxjs/toolkit";
+import rootReducer from "./rootReducer";
+
+const store = configureStore({
+ reducer: rootReducer,
+});
+
+export default store;
diff --git a/app/store/rootReducer.js b/app/store/rootReducer.js
new file mode 100644
index 0000000..7eeccf1
--- /dev/null
+++ b/app/store/rootReducer.js
@@ -0,0 +1,8 @@
+import { combineReducers } from "redux";
+import citiesReducer from "./slices/cities";
+
+const rootReducer = combineReducers({
+ cities: citiesReducer,
+});
+
+export default rootReducer;
diff --git a/app/store/slices/cities.js b/app/store/slices/cities.js
new file mode 100644
index 0000000..11596a1
--- /dev/null
+++ b/app/store/slices/cities.js
@@ -0,0 +1,67 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+
+const apiKey = "";
+
+export const addCity = createAsyncThunk(
+ "city/fetchCityWeather",
+ async (cityName) => {
+ const response = await fetch(
+ `https://api.openweathermap.org/data/2.5/forecast?q=${cityName}&appid=${apiKey}&units=imperial`
+ );
+ const data = await response.json();
+
+ const avgTemp =
+ Math.round(data.list.reduce(
+ (acc, threeHour) => acc + threeHour.main.temp,
+ 0
+ ) / data.list.length);
+
+ const avgHumidity =
+ Math.round(data.list.reduce(
+ (acc, threeHour) => acc + threeHour.main.humidity,
+ 0
+ ) / data.list.length);
+
+ const avgPressure =
+ Math.round(data.list.reduce(
+ (acc, threeHour) => acc + threeHour.main.pressure,
+ 0
+ ) / data.list.length);
+
+ return {
+ name: data.city.name,
+ temp: data.list.map((threeHour) => threeHour.main.temp),
+ pressure: data.list.map((threeHour) => threeHour.main.pressure),
+ humidity: data.list.map((threeHour) => threeHour.main.humidity),
+ avgTemp: avgTemp,
+ avgHumidity: avgHumidity,
+ avgPressure: avgPressure,
+ };
+ }
+);
+
+const citySlice = createSlice({
+ name: "city",
+ initialState: {
+ cities: [],
+ status: "idle",
+ error: null,
+ },
+ reducers: {},
+ extraReducers: (builder) => {
+ builder
+ .addCase(addCity.pending, (state) => {
+ state.status = "loading";
+ })
+ .addCase(addCity.fulfilled, (state, action) => {
+ state.status = "fulfilled";
+ state.cities.push(action.payload);
+ })
+ .addCase(addCity.rejected, (state, action) => {
+ state.status = "rejected";
+ state.error = action.error.message;
+ });
+ },
+});
+
+export default citySlice.reducer;
diff --git a/package-lock.json b/package-lock.json
index 90f6bb1..0d3d11e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,11 +8,14 @@
"name": "parsity_rtk_weather",
"version": "0.1.0",
"dependencies": {
+ "@reduxjs/toolkit": "^2.6.1",
"eslint": "8.50.0",
"eslint-config-next": "13.5.3",
"next": "13.5.3",
"react": "18.2.0",
- "react-dom": "18.2.0"
+ "react-dom": "18.2.0",
+ "react-redux": "^9.2.0",
+ "react-sparklines": "^1.7.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -296,6 +299,30 @@
"node": ">= 8"
}
},
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.1.tgz",
+ "integrity": "sha512-SSlIqZNYhqm/oMkXbtofwZSt9lrncblzo6YcZ9zoX+zLngRBrCOjK4lNLdkNucJF58RHOWrD9txT3bT3piH7Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rushstack/eslint-patch": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.0.tgz",
@@ -314,6 +341,12 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
"node_modules/@typescript-eslint/parser": {
"version": "6.7.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.3.tgz",
@@ -1759,6 +1792,16 @@
"node": ">= 4"
}
},
+ "node_modules/immer": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
+ "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -2683,6 +2726,57 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-redux": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+ "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-sparklines": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/react-sparklines/-/react-sparklines-1.7.0.tgz",
+ "integrity": "sha512-bJFt9K4c5Z0k44G8KtxIhbG+iyxrKjBZhdW6afP+R7EnIq+iKjbWbEFISrf3WKNFsda+C46XAfnX0StS5fbDcg==",
+ "license": "MIT",
+ "dependencies": {
+ "prop-types": "^15.5.10"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz",
@@ -2723,6 +2817,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
"node_modules/resolve": {
"version": "1.22.6",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
@@ -3225,6 +3325,15 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
+ "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
diff --git a/package.json b/package.json
index 6ca0f8f..6eb0e21 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,13 @@
"lint": "next lint"
},
"dependencies": {
+ "@reduxjs/toolkit": "^2.6.1",
"eslint": "8.50.0",
"eslint-config-next": "13.5.3",
"next": "13.5.3",
"react": "18.2.0",
- "react-dom": "18.2.0"
+ "react-dom": "18.2.0",
+ "react-redux": "^9.2.0",
+ "react-sparklines": "^1.7.0"
}
}
- Docs -> -
-Find in-depth information about Next.js features and API.
- - - -- Learn -> -
-Learn about Next.js in an interactive course with quizzes!
- - - -- Templates -> -
-Explore the Next.js 13 playground.
- - - -- Deploy -> -
-- Instantly deploy your Next.js site to a shareable URL with Vercel. -
- +
+
+
+
-
- )
+ | + City + | ++ Temp + | ++ Pressure + | ++ Humidity + | +
|---|