Aprende a crear un proyecto de Nextjs junto con Redux Tookit, unido ademas a Typescript.
Creacion del Proyecto
Primero crearemos un proyecto de Nextjs
npx creact-next-app@latest
npm install @reduxjs/toolkit
npm install react-redux
src/redux/store.ts
import { configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {},
devTools: process.env.NODE_ENV !== "production",
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
src/redux/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
src/redux/provider.tsx
"use client";
import { store } from "./store";
import { Provider } from "react-redux";
export function Providers({ children }: { children: React.ReactNode }) {
return <Provider store={store}>{children}</Provider>;
}
src/app/layout.tsx
// import "./globals.css";
import { Providers } from "@/redux/provider";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
src/redux/features/counterSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
type CounterState = {
value: number;
};
const initialState = {
value: 0,
} as CounterState;
export const counter = createSlice({
name: "counter",
initialState,
reducers: {
reset: () => initialState,
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
decrementByAmount: (state, action: PayloadAction<number>) => {
state.value -= action.payload;
},
},
});
export const {
increment,
incrementByAmount,
decrement,
decrementByAmount,
reset,
} = counter.actions;
export default counter.reducer;
src/redux/store.ts
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./features/counterSlice";
export const store = configureStore({
reducer: {
counterReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
src/app/page.tsx
"use client";
import { decrement, increment, reset } from "@/redux/features/counterSlice";
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
export default function Home() {
const count = useAppSelector((state) => state.counterReducer.value);
const dispatch = useAppDispatch();
return (
<main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
<div style={{ marginBottom: "4rem", textAlign: "center" }}>
<h4 style={{ marginBottom: 16 }}>{count}</h4>
<button onClick={() => dispatch(increment())}>increment</button>
<button
onClick={() => dispatch(decrement())}
style={{ marginInline: 16 }}
>
decrement
</button>
<button onClick={() => dispatch(reset())}>reset</button>
</div>
</main>
);
}
src/redux/services/userApi.ts
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
type User = {
id: number;
name: string;
email: number;
};
export const userApi = createApi({
reducerPath: "userApi",
refetchOnFocus: true,
baseQuery: fetchBaseQuery({
baseUrl: "https://jsonplaceholder.typicode.com/",
}),
endpoints: (builder) => ({
getUsers: builder.query<User[], null>({
query: () => "users",
}),
getUserById: builder.query<User, { id: string }>({
query: ({ id }) => `users/${id}`,
}),
}),
});
export const { useGetUsersQuery, useGetUserByIdQuery } = userApi;
src/redux/store.ts
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./features/counterSlice";
import { userApi } from "./services/userApi";
import { setupListeners } from "@reduxjs/toolkit/dist/query";
export const store = configureStore({
reducer: {
counterReducer,
[userApi.reducerPath]: userApi.reducer,
},
devTools: process.env.NODE_ENV !== "production",
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({}).concat([userApi.middleware]),
});
setupListeners(store.dispatch);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
page.jsx
"use client";
import { useGetUsersQuery } from "@/redux/services/userApi";
import { decrement, increment, reset } from "@/redux/features/counterSlice";
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
export default function Home() {
const count = useAppSelector((state) => state.counterReducer.value);
const dispatch = useAppDispatch();
const { isLoading, isFetching, data, error } = useGetUsersQuery(null);
return (
<main style={{ maxWidth: 1200, marginInline: "auto", padding: 20 }}>
<div style={{ marginBottom: "4rem", textAlign: "center" }}>
<h4 style={{ marginBottom: 16 }}>{count}</h4>
<button onClick={() => dispatch(increment())}>increment</button>
<button
onClick={() => dispatch(decrement())}
style={{ marginInline: 16 }}
>
decrement
</button>
<button onClick={() => dispatch(reset())}>reset</button>
</div>
{error ? (
<p>Oh no, there was an error</p>
) : isLoading || isFetching ? (
<p>Loading...</p>
) : data ? (
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr 1fr",
gap: 20,
}}
>
{data.map((user) => (
<div
key={user.id}
style={{ border: "1px solid #ccc", textAlign: "center" }}
>
<img
src={`https://robohash.org/${user.id}?set=set2&size=180x180`}
alt={user.name}
style={{ height: 180, width: 180 }}
/>
<h3>{user.name}</h3>
</div>
))}
</div>
) : null}
</main>
);
}