I have a Redux middleware that requires some data to be configured via a server call (eg, fetch) which is async / requires promises.
However, every example of createStoresuch as this one seems to use a Singleton pattern for the store. This means that the store gets initialized before my fetch is complete. Because it's middleware, I can't "reconfigure" it after createStore is called.
How can i configure createStore without using a singleton pattern to configure middleware?
How do you fetch those data? If it's just a simple API call. You can easily wait for the data to be returned then pass the data to createStore. Something like this:
const fetchData = () => {
return Promise.resolve({
data: 'Your data here'
});
}
const initialState = window.__INITIAL_STATE__;
fetchData()
.then((data) => {
initialState.somethingYouNeed = data;
const store = createStore(initialState);
// Do the rest here
});
Related
I'm replacing getState with an enhancer as follows:
interface ArtificialStateEnhancerProps {
getArtificialStateGetter: StateGetterFn;
}
export const getArtificialStateEnhancer = ({
getArtificialStateGetter
}: ArtificialStateEnhancerProps) => {
return (createStore: typeof createStoreOriginal) => {
return (rootReducer, preloadedState, enhancers) => {
const store = createStore(rootReducer, preloadedState, enhancers);
const { getState } = store;
if (getArtificialStateGetter) {
return { ...store, getState: getArtificialStateGetter(getState) };
}
return { ...store };
};
};
};
When using store.getState() somewhere in my code it works an my custom getStage method is used. However within an Action or Sage (using select()) the original, native getState from redux is used.
Example action:
export function myAction(
slug: string
): ReduxAction<any> {
return async (
dispatch: Dispatch,
getState: () => any // Here getState is used but the native version of redux
): Promise<any> => {
const state = getState();
const foo = ...do something with `state`
};
}
Is this intended behavior?
It most likely depends on the ordering of where the middleware enhancer is added vs your custom enhancer.
If the middleware enhancer is added after the custom enhancer, then it is dealing with the base-level store methods. Each enhancer kind of "stacks" on top of each other.
My guess is that you've got something like:
compose(myCustomEnhancer, applyMiddleware(saga))
meaning that the middleware enhancer is after the custom one.
You'd need to flip those if that's the case.
(As a side question: could you give more details as to why you're writing a custom enhancer? It's a valid technique, but very rarely used, and it's even more rare that you would ever need or want to override getState.)
Also, note that we generally recommend against using sagas in almost all use cases, and especially if you're looking at using sagas for basic data fetching. Today, Redux Toolkit's "RTK Query" data fetching and caching API does a vastly better job of handling data fetching for you, and the RTK "listener" middleware handles the same reactive logic use case as sagas but with a simpler API, smaller bundle size, and better TS support.
See my talk The Evolution of Redux Async Logic for comparisons and explanations of why we recommend against sagas.
I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.
New to Jest and Redux and I'm having trouble with testing functions that are dispatching to the store but don't yield a return value. I'm trying to follow the example from the Redux website does this
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
however I have several "fetchtodos" functions that don't return anything which causes the error TypeError:
Cannot read property 'then' of undefined due to returning undefined
I'm wondering what I can do to test that my mock store is correctly updating. Is there a way to dispatch the function, wait for it to finish and then compare the mock store with expected results?
Thanks
Edit: We're using typescript
action from tsx
export function selectTopic(topic: Topic | undefined): (dispatch: Redux.Dispatch<TopicState>) => void {
return (dispatch: Redux.Dispatch<TopicState>): void => {
dispatch({
type: SELECT_Topic,
payload: topic,
});
dispatch(reset(topic));
};
}
test.tsx
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('Select Topic action', () => {
it('should create an action to select .', () => {
const topic: Topic = mockdata.example[0];
const expectedAction = {
type: actions.SELECT_TOPIC,
payload: topic,
};
const store = mockStore(mockdata.defaultState);
return store.dispatch(actions.selectTopic(topic)).then(() => {
expect(store.getState()).toEqual(expectedAction);
});
});
});
The action is what I'm given to test(and there are many other functions similar to it. I'm getting that undefined error when running the test code, as the function isn't returning anything.
In Redux, the store's dispatch method is synchronous unless you attach middleware that changes that behavior, ie: returns a promise.
So this is likely a redux configuration problem. Be sure you are setting up your test store with the same middleware that allows you to use the promise pattern in production.
And as always, be sure to mock any network requests to avoid making api calls in test.
I've used redux saga before but i'm still fairly new to it. Anyways, I seem to be running into a problem in the code below.
At the top, you will see the action creator I am using to fire off this AJAX request. Redux is properly dispatching this action and is logging the LOAD_USER_REQUEST type in my console however the function chain stops there. In the code below, you will see that LOAD_USER_REQUEST should call the loadUserDetails generator which should then call the userLogin with the payload received in my action creator.
Let me know if I can supply any additional info that may help. Thanks in advance :)
// Action Creator for LOAD_USER_REQUEST.
export const getExistingUser = ({email = 'tt#gmail.com', password = '12345'} = {}) => ({
type: LOAD_USER_REQUEST,
payload: {email, password}
})
// API call being used in loadUserDetails Saga
export const userLogin = ({email = 'tt#gmail.com', password = '12345'} = {}) => {
return axios.post(`${API}auth/login`, {
email,
password
})
.then(res => {
console.log(res);
localStorage.setItem('token', res.data.token);
let user = res.data.user;
console.log(user);
return user;
})
.catch(err => new Error('userLogin err', err));
}
// Sagas
// loadUserDetails Saga - Should call fn above userLogin with payload from action creator
function* loadUserDetails(payload) {
const user = yield call(userLogin(payload));
yield put({type: LOAD_USER_SUCCESS, user}); // Yields effect to the reducer specifying the action type and user details
}
export function* watchRequest() {
yield* takeLatest(LOAD_USER_REQUEST, loadUserDetails);
}
At first, does your entry point to saga configured well? You should add saga-middleware in store creation, and don't forget to invoke saga process manager by runSaga method.
At second, why you re-delegate generator instance to up-level? Maybe it's meant to yield takeLatest(LOAD_USER_REQUEST, loadUserDetails); without yield* operator? Is has quite different semantics.
At third, by API reference, call effect takes function or generator reference, but you provide promise object. Maybe it's meant const user = yield call(() => userLogin(payload)); or const user = yield call(userLogin, payload);?
I'm designing a messaging application with redux as my state manager and firebase to store my data. I've started writing my database listeners in this fashion:
const fetchMessages = roomKey => async dispatch => {
const db = firebase.database();
let { messages } = await db.ref(`messages/${roomKey}`).on('value');
dispatch({
type: SET_MESSAGES,
payload: messages,
})
};
All this basically does is fetch messages by a room key and then dispatch an action that sets the messages in the redux state.
Traditionally, this would be written as such:
db.ref(`messages/${roomKey}`).on('value', snapshot => {
const messages = snapshot.messages;
dispatch({
type: SET_MESSAGES,
payload: messages,
})
});
And everytime something changes in messages/${roomKey}, my dispatch function would be executed. I'm wondering if this will work the same using the async await syntax, and if not, how I could make it work.
Hope this was enough detail!
The reference's on method does not return a promise. The callback it's passed can be invoked multiple times, so a promise does not fit with the method's contract.
However, the reference's once method method does return a promise, as the (optional) callback it's passed is invoked only once - after which the promise resolves. The once method is likely the one you want to use.