How to initialize svelte kit application with globally shared data from db - sveltekit

I have e language table in my db where it has all the words/sentences of my website in different language. I don't want to call API for every single word/sentence in the website. What is the best approach in svelte kit to get all the data from that table before my website initializes and then store them in such way that all my pages and component can access them? Also that data need to be updated if the user changes the language of the website.

You could define a server load function at the top level layout and pass on that data to a store which can be provided as a context to every page using the layout.
E.g.
// +layout.server.ts
import type { LayoutServerLoad } from './$types';
const fakeData = {
title: 'SvelteKit',
description: 'A new way to build web applications',
};
export const load: LayoutServerLoad = async ({ fetch }) => {
// Fetch localization data here
return {
localization: fakeData,
};
};
<!-- +layout.svelte -->
<script lang="ts">
import { setContext } from 'svelte';
export let data: { localization: any };
setContext('localization', writable(data.localization));
</script>
Usage in a page:
<script lang="ts">
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
const localization = getContext<Writable<any>>('localization');
</script>
{$localization.title}
The key for the context and getting/setting the context could be extracted to constants/extra functions. If the language has to be updated on the fly, you just have to fetch the new data and set the store content.

Related

Does Vue 3 support something like Angular-Services?

I'm porting a project from AngularJs to Vue3. In Angular we have a service component, which creates und supplies objects to components. The objects are automatically reactive, if they are used in a binding in a component. If component A changes an object, the View of component B shows the change of the state immediately.
Is there a way to accomplish such a service in Vue3? The goal is, to maintain an array of objects, and every change to the array is reflected in the Views of the different components.
Differences: Vue VS AngularJS
Vue is a UI framework that doesn't specifically have such entities as services, it is up to a developer to implement them. Also in contrast to Angular, Vue doesn't provide dependency-injection (DI) features.
Composables as service in Vue
In Vue, reusable component code is supposed to be provided by composables or composable functions, their reactivity is implemented with composition API:
// Usage: service = useLocalService()
const useLocalService = () => {
const someState = reactive({});
const someAction = () => /* change state */;
return { someState, someAction }
};
provide / inject to share services in Vue (compare DI)
For global singleton services that are inherent in Angular, service state is defined in a module and evaluated once, so it's shared across the app:
// Usage: import * as service from '...';
export const someGlobalState = reactive({});
export const someGlobalAction = () => /* change state */;
Whether it's beneficial to wrap it in a composable function for consistency depends on a situation:
// Usage: service = useGlobalService()
const someGlobalState = reactive({});
const someGlobalAction = () => /* change state */;
const useGlobalService = () => {
return { someGlobalState, someGlobalAction }
};
In Angular 2 or later, DI containers allow to provide service instances per component hierarchy. In Vue, this is done through provide/inject:
// Usage in a parent: setupRootOrNestedService()
const setupRootOrNestedService = () => {
const someRootOrNestedState = reactive({});
const someRootOrNestedAction = () => /* change state */;
provide('rootOrNestedService', { someRootOrNestedState, someRootOrNestedState });
};
// Usage in a child: service = useRootOrNestedService()
const useRootOrNestedService = () => {
return inject('rootOrNestedService');
};
This allows to identify services by tokens at any level and benefit from using DI pattern.
Pinia
Pinia (a Vue store) library provides lightweight state management influenced by Flux pattern. It relies on composition API and allows for multiple stores. The result is similar to the above, with the addition of a convention over code, extended Vue devtools, proper TypeScript typing, and a plugin layer:
// Usage as local service: service = useLocalOrGlobalService()
// Usage as global service: export default useLocalOrGlobalService()
// And: import * as service from '...';
const useLocalOrGlobalService = () => {
return defineStore('localOrGlobalService' + random(), {
state: () => ({}),
actions: {
someAction() { /* change state */ }
}
})
};
Pinia doesn't restrict ways in which a store is instantiated and provided to components, so it can be combined with provide/inject if necessary.
Just for the records: This is, what I came up with, as simple sample code. First I wrote a little service:
import { reactive } from "vue";
export const objectService = {
objects: reactive([]), // reactive is important, otherwise it doesn't work
start: function(){
for (var i = 0; i < 10; i++)
((j)=>{
setTimeout(()=> // I use setTimeout as example for an async operation
objectService.objects.push({value: j}), 1000);
})(i);
}
};
The point here is, that the service manipulates the state in an async operation (in real live systems a rest call or access to indexedDb, etc).
Now there are two components running in two separate Vue apps. The reason, why they run in separate apps is out of the scope of the issue here. App1:
<template>
<!--App1.vue-->
<p>Here is App1</p>
<div v-for="obj,ix in objectService.objects" :key="ix">{{obj.value}}
<input v-model="obj.value" />
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { objectService } from '../services/object-service.js';
const App1 = defineComponent({
setup() {
return { objectService };
},
})
export default App1;
</script>
App2:
<template>
<!--App2.vue-->
<p>Here is App2</p>
<div v-for="obj,ix in objectService.objects" :key="ix">{{obj.value}}
<input v-model="obj.value" />
</div>
</template>
<script>
import { defineComponent } from 'vue';
import { objectService } from '../services/object-service.js';
const App2 = defineComponent({
setup() {
// const os = objectService;
objectService.start();
return { objectService };
},
})
export default App2;
</script>
These 2 components are identical with one exception: App2 calls the start method of the service. The result is shown in both components. And each of the components can alter the state, which is immediately reflected in the other component.
The point of the whole solution is the use of reactive({}) in the service. I hope, this is useful for somebody.
Again thanks to Estus Flask & hc_dev to point me into the right direction.

Understanding Context in Svelte (convert from React Context)

I have a react app that uses ContextAPI to manage authentication and I am trying to implement a similar thing in Svelte. [Web Dev Simplified][1]
In Authenticate.js I have this:
import React, { useContext, useState, useEffect } from "react"
import { auth } from "../firebase"
const AuthCt = React.createContext()
export function Auth() {
return useContext(AuthCt)
}
export function AuthComp({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function logout() {
return auth.signOut()
}
useEffect(() => {
const unmount = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unmount
}, [])
const value = {
currentUser,
login,
signup
}
return (
<AuthCt.Provider value={value}>
{!loading && children}
</AuthCt.Provider>
)
}
This context is used in other Login.js component like this:
import { Auth } from "./Authenticate"
const Login = () => {
const { currentUser, login } = Auth()
And in App.js I have:
import { AuthComp } from "./Authenticate";
function App() {
return (
<AuthComp>
<div> All others go here </div>
</AuthComp>
);
}
How do I achieve this in Svelte, particularly the Authenticate context?
I haven't been able to do much in Svelte as I don't know how to proceed from here. So far I have AuthComp.svelte. I don't know if I am doing the right thing.
<script>
import { getContext, setContext } from 'svelte';
import { auth } from '../firebase';
import { writable } from 'svelte/store';
let Auth = getContext('AuthCt')
setContext('Auth', Auth)
let currentUser;
let loading = true;
const unmount = auth.onAuthStateChanged(user => {
currentUser = user;
loading = false
});
function login(email, password) {
return auth.signInWithEmailandPassWord(email,password)
}
function logout() {
return auth.signOut()
}
const value = { currentUser, login, signUp }
</script>
<slot value={value}></slot>
Migrating from React Context to Svelte
Context in Svelte and React may seem similar, but they are actually used differently. Because at the core, Svelte's context is much more limited. But that's ok. In fact, it actually will make your code simpler to write and understand.
In Svelte, you have more tools at your disposal for passing data round your app (and keeping it in sync) than just context. Each one does pretty much one thing (making everything predictable), and they do it well. Of these, you have:
Context
Stores
Props
As someone who's recently switched from React to Svelte, I think I can help explain some of the differences between each of these and help you avoid some of my conceptual mistakes. I'll also go over some differences in life cycle methods, because if you used to use useEffect, you might feel very lost since Svelte doesn't have an equivalent API. Yet combining everything together in Svelte will make everything simple.
Context
Context in Svelte does one thing: pass data from a parent component to any children (not necessarily direct children). Unlike in React, context is not reactive. It is set once when the component mounts, and then will not be updated again. We'll get to "reactive context" in a second.
<!-- parent.svelte -->
<script>
import { setContext } from 'svelte'
setContext('myContext', true)
</script>
<!-- child.svelte -->
<script>
import { getContext } from 'svelte'
const myContext = getContext('myContext')
</script>
Notice that context involves two things, a key and a value. Context is set to a specific key, then the value can be retrieved using that key. Unlike React, you do not need to export functions to retrieve the context. Both the key and value for the context can be anything. If you can save it to a variable, you can set it to context. You can even use an object as a key!
Stores
If you have data that needs to stay in sync in multiple places across your app, stores are the way to go. Stores are reactive, meaning they can be updated after they're created. Unlike context in either React or Svelte, stores don't simply pass data to their children. Any part of your app can create a store, and any part of your app can read the store. You can even create stores outside of Svelte components in separate JavaScript files.
// mystore.ts
import { writable } from 'svelte/store'
// 0 is the initial value
const writableStore = writable(0)
// set the new value to 1
writableStore.set(1)
// use `update` to set a new value based on the previous value
writableStore.update((oldValue) => oldValue + 1)
export { writableStore }
Then inside a component, you can subscribe to the store.
<script>
import { writableStore } from './mystore'
</script>
{$writableStore}
The dollar sign subscribes to the store. Now, whenever the store is updated, the component will rerender automatically.
Using stores with context
Now that we have stores and context, we can create "reactive context"(a term I just made up, but it works). Stores are great because they're reactive, and context is great to pass data down to the children components. But we can actually pass a store down through context. This makes the context reactive and the store scoped.
<!-- parent.svelte -->
<script>
import { setContext } from 'svelte'
import { writable } from 'svelte/store'
const writableStore = writable(0)
setContext('myContext', writableStore)
</script>
<!-- child.svelte -->
<script>
import { getContext } from 'svelte'
const myContext = getContext('myContext')
</script>
{$myContext}
Now, whenever the store updates in the parent, the child will also update. Stores can of course do much more than this, but if you were looking to replicate React context, this is the closest you can get in Svelte. It's also a lot less boilerplate!
Using "reactive context" with "useEffect"
Svelte does not have an equivalent of useEffect. Instead, Svelte has reactive statements. There's a lot on these in the docs/tutorial, so I'll keep this brief.
// doubled will always be twice of single. If single updates, doubled will run again.
$: doubled = single * 2
// equivalent to this
let single = 0
const [doubled, setDoubled] = useState(single * 2)
useEffect(() => {
setDoubled(single * 2)
}, [single])
Svelte is smart enough to figure out the dependencies and only run each reactive statement as needed. And if you create a dependency cycle, the compiler will yell at you.
This means that you can use reactive statements to update stores (and hence update the context). Here, the valueStore will be update on every keystroke to the input. Since this store is passed down through context, any child can then get the current value of the input.
<script>
import { setContext } from 'svelte'
import { writable } from 'svelte/store'
// this value is bound to the input's value. When the user types, this variable will always update
let value
const valueStore = writable(value)
setContext('inputContext', valueStore)
$: valueStore.set(value)
</script>
<input type='text' bind:value />
Props
For the most part, props function exactly the same in React and Svelte. There are a few differences because Svelte props can take advantage of two-way binding (not necessary, but possible). That's really a different conversation though, and the tutorial is really good at teaching two-way binding with props.
Authentication in Svelte
Ok, now after all of that, let's look at how you'd create an authentication wrapper component.
Create an auth store
Pass the auth store down via context
Use Firebase's onAuthStateChanged to listen to changes in auth state
Subscribe to the auth store in the child
Unsubscribe from onAuthStateChanged when the parent is destroyed to prevent memory leaks
<!-- parent.svelte -->
<script>
import { writable } from 'svelte/store'
import { onDestroy, setContext } from 'svelte'
import { auth } from '../firebase'
const userStore = writable(null)
const firebaseUnsubscribe = auth.onAuthStateChanged((user) => {
userStore.set(user)
})
const login = (email, password) => auth.signInWithEmailandPassWord(email,password)
const logout = () => auth.signOut()
setContext('authContext', { user: userStore, login, logout })
onDestroy(() => firebaseUnsubscribe())
</script>
<slot />
<!-- child.svelte -->
<script>
import { getContext } from 'svelte'
const { login, logout, user } = getContext('authContext')
</script>
{$user?.displayName}
In Svelte, context is set with setContext(key, value) in a parent component, and children can access the value object with getContext(key). See the docs for more info.
In your case, the context would be used like this:
<script>
import { getContext, setContext } from 'svelte';
import { auth } from '../firebase';
import { writable } from 'svelte/store';
// you can initialize this to something else if you want
let currentUser = writable(null)
let loading = true
// maybe you're looking for `onMount` or `onDestroy`?
const unmount = auth.onAuthStateChanged(user => {
currentUser.set(user)
loading = false
});
function login(email, password) {
return auth.signInWithEmailandPassWord(email,password)
}
function logout() {
return auth.signOut()
}
const value = { currentUser, login, signUp }
setContext('Auth', value)
</script>
{#if !loading}
<slot></slot>
{/if}
Here, currentUser, login, and signup (not sure where that's coming from?) are set as context with setContext(). To use this context, you would probably have something like this:
<!-- App -->
<AuthComp>
<!-- Some content here -->
<Component />
</AuthComp>
<!-- Component.svelte -->
<script>
import { getContext } from 'svelte'
const { currentUser, login, signup } = getContext('Auth')
// you can subscribe to currentUser with $currentUser
</script>
<div>some content</div>
As written in the docs, context is not reactive, so currentUser is first converted into a store so it can be subscribed to in a child. As for the useEffect, Svelte has lifecycle functions that you can use to run code at different points, such as onMount or onDestroy.
If you're new to Svelte, their tutorial is very comprehensive with plenty of examples that you can refer back to.
Hope this helped!

Fetching Data from API using NextJS and Material UI React

I am trying to create dynamic pages that shows individual book details (i.e. title/author etc) on a separate page based on a query string of the "id" of each book. However, I am having difficulty in understanding how to make a request to a API endpoint using NextJS that will get the book details based on its "id". I would like to use Material UI as a UI Framework.
ISSUE: When I run npm run dev the book page loads but the book's "props" are not being passed along to the BookAttributes component. The console.log(book) I added in the book page is undefined and the console.log(title) in BookAttributes is undefined as well.
I've tested the API endpoint in POSTMAN and it appears to work.
When I refactor the same code using Semantic UI-React instead of Material UI, the book pages load correctly.
I am using the NextJS Material UI starter template from the Material UI website as a baseline.
I am fairly new to NextJS and Material UI so your assistance and guidance would be greatly appreciated. Thank you for your help on this!
Here is the code I have so. I have tried to keep in clean and simple.
BOOK PAGE (within 'pages' directory)
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
function Book({ book }) {
console.log(book)
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
Book.getInitalProps = async ({ query: { _id } }) => {
const url = 'http://localhost:3000/api/book';
const payload = { params: { _id }}
const response = await axios.get(url, payload)
return { book: response.data }
}
export default Book;
BOOK API ENDPOINT (within 'pages/api' directory)
import Book from '../../models/Book';
import connectDb from '../../utils/connectDb';
connectDb()
export default async (req, res) => {
const { _id } = req.query
const book = await Book.findOne({ _id })
res.status(200).json(book);
}
BOOK ATTRIBUTE COMPONENT (within 'components' directory)
import React from 'react';
function BookAttributes({ title }) {
console.log(title)
return (
<>
<h1>{title}</h1>
</>
)
}
export default BookAttributes;
You should be using dynamic routes here if you want to work with data-fetching methods like getStaticProps or getServerSideProps.
You can create a page like pages/book/[id].js. But to generate the page you have to decide what data-fetching method you want to run. If the data for the page doesn't change very often you can choose to use static-site-generation using getStaticProps which will generate the pages at build time. If the data will be changing a lot you can either do server-side-rendering using getServerSideProps or fetch the data client-side.
Here is an example for your use-case that you can use for server-side-rendering using getServerSideProps, keep in mind the API call inside getServerSideProps might fail so you should have appropriate error handling.
In pages/book/[id].js
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
export const getServerSideProps = async (ctx) => {
const bookId = ctx.params?.id
const url = 'http://localhost:3000/api/book';
const response = await axios.get(url, { params: { _id: bookId} })
return {
props: {
book: response.data
}
}
}
function Book({ book }) {
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
export default Book;
Using static-site-generation
Because the page is dynamic you have to provide a list of paths for which nextjs will generate the pages. You can do that by exporting an async function called getStaticPaths.
in pages/book/[id].js
import axios from 'axios';
import BookAttributes from '../components/Book/BookAttributes';
export const getStaticPaths = async () => {
// here you have two options if you know all the ids of the books
// you can fetch that data from the api and use all the ids to generate
// a list of paths or show a fallback version of page if you don't know all
// ids and still want the page to be static
// Pseudo code might look like this
const res = await axios.get('api-endpoint-to-fetch-all-the-books')
const paths = res.data.map(book => ({ params: { id: book.id }}))
return {
paths,
fallback: false
}
}
export const getStaticProps = async (ctx) => {
const bookId = ctx.params?.id
const url = 'http://localhost:3000/api/book';
const response = await axios.get(url, { params: { _id: bookId} })
return {
props: {
book: response.data
}
}
}
function Book({ book }) {
return (
<>
<h1>Book Page</h1>
<BookAttributes {...book} />
</>
)
}
export default Book;
The fallback property in the returned value of getStaticPaths is somewhat important to understand. If you know all the necessary id for the pages you can set the fallback to false. In this case nextjs will simply show a 404 error page for all the paths that were not returned from the function getStaticPaths.
If fallback is set to true nextjs will show a fallback version of page instead of a 404 page for the paths that were not returned from the getStaticPaths function. Now where should you set fallback to true? Let's suppose in your case new books are added to the database frequently, but the data for the books doesn't change very often so you want the pages to be static. In this case, you can set fallback to true and generate a list of paths based on avaliable book ids. For the new books nextjs will first show the fallback version of the page than fetch the data based on the id provided in the request and will send the data as JSON which will be used to render the page in the client.

Is using Redux with Next.js an anti-pattern?

I'm building a Next.js app and it currently is using Redux. As I am building it I am wondering if the use of Redux is really necessary and if its use is actually an anti-pattern. Here is my reasoning:
In order to properly initialize the Redux Store in Next.js, you must create a custom App component with a getInitialProps method. By doing this you are disabling the Automatic Static Optimization that Next.js provides.
By contrast, if I were to include Redux on the client-side, only after the App has mounted, then the Redux store will reset after every server-side navigation. For instance, I have a Next.js app that initializes the Redux store on the client-side, but when routing to a dynamic route such as pages/projects/[id], the page is server-side rendered, and I have to re-fetch any information that was in the store.
My questions are:
What are the benefits of a Redux store in this circumstance?
Should I initialize the store in the root App component and forego the Automatic Static Optimization?
Is there a better way to do to manage state in Next.js 9.3 with getStaticProps and the other data fetching methods
Am I missing something?
If you have a custom App with getInitialProps then the Automatic
Static Optimization that Next.js provides will be disabled for all
pages.
True, if you follow this approach.
Is there a better way ?
Yes, you can create a Redux Provider as a wrapper and wrap the component you need, the redux context will be automatically initialized and provided within that component.
Example:
const IndexPage = () => {
// Implementation
const dispatch = useDispatch()
// ...
// ...
return <Something />;
}
IndexPage.getInitialProps = ({ reduxStore }) => {
// Implementation
const { dispatch } = reduxStore;
// ...
// ...
}
export default withRedux(IndexPage)
You have now the possibility to use Redux only for the pages which need state management without disabling the optimization for the entire App.
Answering you question "Is using Redux with Next.js an anti-pattern?"
No, but it needs to be used properly.
More info on how is done here: https://github.com/vercel/next.js/tree/canary/examples/with-redux
I hope this helps
we use Redux mainly for 2 reasons.
1- pass data between components.
if you do not use redux, then you need to do prop drilling. To decide if user logged in or not, we fetch the data and then store it in redux store and then Header components connects to the store and gets the authentication info. If you are not using redux, then you need to fetch the user in each page and then pass it to the Header component.
Next.js pre-renders every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript. Pre-rendering can result in better performance and SEO. next-redux-wrapper package allows you to use the redux with automatic-static-optimization. If you click on the link, there is a note saying: "Next.js provides generic getInitialProps when using class MyApp extends App which will be picked up by wrapper, so you must not extend App as you'll be opted out of Automatic Static Optimization:". I set up this package for my project and it is easy to setup.
But downside of using redux, it is not caching. You store the data and then you refetch it periodically to make sure it is up to date. and this is an extra expensive work. To achieve caching in redux, we use reselect library. This means extra dependency for your project on top of redux and will make you write more code.
There is a nice package swr which is created by next.js. Stale-While-Revalidate. it first returns the data from cache(stale), then sends the fetch request, and finally comes with the updated data again. I choose the use this in each page.
import useSWR from "swr";
export const useGetUser = () => {
// fetcher can be any asynchronous function which returns the data. useSwr will pass "/api/v1/me" to fetcher
const { data, error, ...rest } = useSWR("/api/v1/me", fetcher);
// !data && !error if both true, loading:true, data=null=>!data=true, error=null => !error=true
return { data, error, loading: !data && !error, ...rest };
};
here is resuable fetcher
export const fetcher = (url: string) =>
fetch(url).then(
async (res: Response): Promise<any> => {
const result = await res.json();
if (res.status !== 200) {
return Promise.reject(result);
} else {
return result;
}
}
);
2- Making api requests.
I set up redux store for my project and it was conflicting with the text-editor that I set up. Redux was somehow blocking the editor and i could not populate the store with the text that i wrote on the editor. So I used reusable hooks for fetching api. it looks intimating in the beginning but if you analyze it, it will make sense.
export function useApiHandler(apiCall) {
// fetching might have one those 3 states. you get error, you fetch the data, and you start with the loading state
const [reqState, setReqState] = useState({
error:null,
data:null,
loading:true, // initially we are loading
});
const handler = async (...data) => {
setReqState({ error: null, data: null, loading: true });
try {
// apiCall is a separate function to fetch the data
const res = await apiCall(...data);
setReqState({ error: null, data: res.data, loading: false });
alert(res.data);// just to check it
return res.data;
} catch (e) {
// short circuting in or. if first expression is true, we dont evaluate the second.
// short circuting in and. if first expression is true, result is the second expression
const message =
(e.response && e.response.data) || "Ooops, something went wrong...";
setReqState({ error: message, data: null, loading: false });
return Promise.reject(message);
}
};
return [handler, { ...reqState }];
}
A simple apiCall function
const createBlog = (data) => axios.post("/api/v1/blogs", data);
and then this is how we use it :
export const useCreateBlog = () => useApiHandler(createBlog);
Setting redux is easy since it is easy people are not worried about the performance of their app, they just set it up. In my opinion, if you have a large app you need to set up redux or if you are familiar with graphql you can use Apollo. Here is a good article to get an idea about using apollo as state management. apollo as state management. I built a large ecommerce website and I used redux, my in my new app, since it is relatively small I do not use next js and make it more complicated.
Redux Toolkit Query
I think redux toolkit query (RTK query) is the biggest improvement in the redux ecosystem. It is actually built on top of redux-toolkit library. redux-toolkit helped us to write our redux code much simpler and update the state easier by using immer.js behind the scene.
With "RTK Query" we can handle data fetching and state management together. All the data fetching is combined under one API and we can cache the data, invalidate the cache or refetch the query. It is actually doing what the combination of swr and context Api is doing. state management with swr and context api
If you are using Redux, you do not need to have getInitialProps on _app.js.
You can use next-redux-wrapper, and just wrap _app.js export with it.
Store example, with next-redux-wrapper and thunk:
import { createStore, applyMiddleware } from 'redux';
import { createWrapper } from 'next-redux-wrapper';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './rootReducer';
const bindMiddleware = middleware => {
return composeWithDevTools(applyMiddleware(...middleware));
};
const initStore = (initialState = {}) => {
return createStore(rootReducer, initialState, bindMiddleware([thunkMiddleware]));
};
export const wrapper = createWrapper(initStore, { debug: true });
Then inside your _app.js, you are exporting it as functional component with
const App = ({ Component, pageProps }) => {
return (
<Component {...pageProps} />
)
}
export default wrapper.withRedux(App);
Works like a charm. Just make sure you are doing hydration ssr -> csr.
Personally I think using the Redux is not a good idea at any case. It would be better to use, for example, useContext, or in case of extreme need for centralized storage look towards mobx. But in fact, there is a simple way to use Redux with SSR without using getInitialProps.
There is an important point here - the solution I gave is applicable only if you DO NOT use the rendering of literally every page on the server - when following the route after the first render, the application renders the next page on its own. In this solution it is assumed that the store will be initialized on the server side once and then the rendering result will be transferred to the client. If you need to render the page on the server absolutely every time you navigate the route and you need to save the state of store, then perhaps you really better still look towards the next-redux-wrapper.
So to initialize store at getServerSideProps first you will need to change your storage initialization file as follows (perhaps you will have other imports):
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
let storeInstance: any;
export const makeStore = (initialState: {}) => {
storeInstance = createStore(
Reducers,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware)) // Optional, but is a handy thing
);
return storeInstance;
};
// initializeStore used for pages that need access to store at getServerSideProps
export const initializeStore = (preloadedState) => {
let reInitiatedStore = storeInstance ?? makeStore(preloadedState)
// After navigating to a page with an initial Redux state, merge that state
// with the current state in the store, and create a new store
if (preloadedState && storeInstance) {
reInitiatedStore = makeStore({ ...storeInstance.getState(), ...preloadedState});
// Reset the current store
storeInstance = undefined;
}
// Keep in mind that in some cases this can cause strange
// and difficult to track errors, so whether or not
// to uncomment next lines depends on the architecture of your application.
// if (typeof(window) === 'undefined') {
// return reInitiatedStore; // For SSG and SSR always create a new store
// }
// Create the store once in the client
if (!storeInstance) {
storeInstance = reInitiatedStore;
}
return reInitiatedStore;
}
After that, in the page, where you need store on server side in the getServerSideProps, you can simple use initializeStore:
import { initializeStore } from '#Redux';
// Compnent code here...
export const getServerSideProps(context: any) {
const reduxStore = initializeStore();
// reduxStore = {
// dispatch: [Function (anonymous)],
// subscribe: [Function: subscribe],
// getState: [Function: getState],
// }
// Doing something with the storage...
const initialReduxState = storeInstance.getState(); // and get it state
return { props: { initialReduxState, ...someProps } };
}
Also don't forget that if you need to access the store in your _app.js, you must define store as:
const store = initializeStore(pageProps.initialReduxState);
Next.js is just a framework on top of React which simplifies Server Side Rendering setup, but it is still React. And React/Redux combo is very popular and still often used, also by me, so the answer is - it is not necessary, but totally possible! The bigger the app and the more you like functional programming, the better chance Redux will be a good option!

Handle URL Parameters with Gatsby

I'm using React & Gatsby to create a website and I've got everything laid out the way I want, I'm just having some problems understanding how to use URL Route parameters to change content that is displayed.
For example, say I have a page categories.js which listens to http://website.com/categories but I want to be able to dynamically handle any URL parameters like such:
http://website.com/categories/animals
When using gatsby-link like so: <Link to="/categories/animals" /> it wants me to create a file called animals.js in a categories folder. Instead, I want categories.js to be able to handle the rendering for this page and select the content appropriate based on the category passed in the URL parameters.
Other than the items displayed, this page is exactly the same through all categories, so it doesn't make sense for every category to have it's own static page.
I think you are wrong when you say:
Other than the items displayed, this page is exactly the same through all categories, so it doesn't make sense for every category to have it's own static page.
In fact, that's exactly where I find GatsbyJS so useful, as it is a static site generator.
It means that you can give Gatsby a template component that will have the same layout for all of your categories, and then Gatsby will fetch data and create static pages for you at build time.
Gatsby is not limited to making pages from files like many static site generators. Gatsby lets you use GraphQL to query your data and map the data to pages—all at build time. (from Gatsby official tutorial)
The idea would be something like this:
in /gatsby-node.js
const path = require(`path`); // you will need it later to point at your template component
exports.createPages = ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators;
// we use a Promise to make sure the data are loaded
// before attempting to create the pages with them
return new Promise((resolve, reject) => {
// fetch your data here, generally with graphQL.
// for example, let say you use your data from Contentful using its associated source plugin
graphql(`
{
allContentfulCategories {
edges {
node {
id
name
description
# etc...
}
}
}
}
`).then(result => {
// first check if there is no errors
if (result.errors) {
// reject Promise if error
reject(result.errors);
}
// if no errors, you can map into the data and create your static pages
result.data.allContentfulCategories.edges.forEach(({ node }) => {
// create page according to the fetched data
createPage({
path: `/categories/${node.name}`, // your url -> /categories/animals
component: path.resolve('./src/templates/categories.js'), // your template component
context: {
// optional,
// data here will be passed as props to the component `this.props.pathContext`,
// as well as to the graphql query as graphql arguments.
id: node.id,
},
});
});
resolve();
});
});
};
And then you can simply fetch the data on your template component
in /src/templates/categories.js
import React from "react";
export default ({ data: { contentfulCategories: category } }) => {
return (
<div>
<h1>{category.name}</h1>
<div>{category.description}</div>
</div>
);
};
// we can query the needed category according to the id passed in the
// context property of createPage() in gatsby-node.js
export const query = graphql`
query CategoryQuery($id: String!) {
contentfulCategories(id: { eq: $id }) {
name
description
}
}
`;
If you insist in dynamically rendering your categories pages, you can take example of this question that is sort of similar to yours, but note that you will have to manually handle re-rendering if the page's props changes in the componentWillReceiveProps lifecycle method of the React class component.
But I really don't think this is a reliable solution.
EDIT:
I've found a better answer after searching forever! -
https://github.com/gatsbyjs/gatsby/issues/13965#issuecomment-617364363
the example for your case I imagine would be something like this:
gatsby-node.js:
createPage({
path: `/categories/:id`,
matchPath: `/categories/:id`,
component: path.resolve(`./src/pages/categories.js`),
})
categories.js:
import React from "react"
export default function Booking({ id }) {
return <div>categories #{id}</div>
}
OLD ANSWER:
I seem to have come across exactly the same issue too and couldn't find any answers. My idea was to also use the gatsby-node.js file, but I don't query anything using graphQL.
My version of /src/templates/categories.js:
const path = require(`path`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
createPage({
path: `/categories/animals`,
component: path.resolve(`./src/pages/categories.js`),
// The context is passed as props to the component as well
// as into the component's GraphQL query.
context: {
id: `animals`, //use this context parameter to do the dynamic stuff in your page
},
})
}
I hope this is useful.

Resources