I have a next.js application which uses context for state management, we are are moving to redux as context triggers a re-render on on all consumers of a context provider whose value is updated... and we use a global context provider. We are unable to change to co-located context providers as requires a lot of work / we have been burnt by context.
So redux.
I build redux store, and in local development - at app level i see redux tool kit, the actions and state being updated.
const buildStore = () => {
const devMode = process.env.NODE_ENV === 'development';
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
// https://redux-toolkit.js.org/api/configureStore
reducer: rootReducer,
middleware: (getDefaultMiddleware) => [
...getDefaultMiddleware({
thunk: false, // we use redux-saga, not the default, thunk
serializableCheck: false, // redux toolkit issues
}),
sagaMiddleware,
],
devTools: devMode, // enable devTools in dev env
});
sagaMiddleware.run(rootSaga);
return store;
};
ie
const Widget = ({ formSettings }) => {
const client = getClient();
const store = buildStore();
return (
<ReduxProvider store={store}>
<StateProvider formSettings={formSettings}>
<ApolloProvider client={client}>
<ThemeProvider>
<Form />
<BrowserMessages />
</ThemeProvider>
</ApolloProvider>
</StateProvider>
</ReduxProvider>
);
};
Yes we also have apollo, which has a redux store internally. It is our intention to remove context.
Issue i have is that this application is built within an iframe, so when i load the app locally (within iframe) its showing the redux actions, but if i load the application in staging then we are outside the iframe and i do not see the action in RTK or store being updated.. although can seen the network request which is triggered by browser messages, so i know that the application is working as expected (and the component value in the context provider has been updated).
How do i connect RTK to the store in the iframe?
I started integrated nextjs to my react app, when I tried to make configuration for redux, but I found that it requires additional configuration for store like get preloadState and other stuff If someone makes a favor for me explains it to me setup by step coz I don't like to copy and past without understanding to be easier for me to edit and maintain
function initStore(preloadedState = initialState) {
return createStore(
reducer,
preloadedState,
composeWithDevTools(applyMiddleware())
)
}
export const initializeStore = (preloadedState) => {
let _store = store ?? initStore(preloadedState)
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
})
store = undefined
}
if (typeof window === 'undefined') return _store
if (!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
With nextjs you can prefetch stuff before rendering the page, by using getServerSideProps or getStaticProps functions.
Basically, that code is checking before initializing the store if there's already something in the store, if there's something it will try to merge it with the initial store values, if not it will just return the initial state of the store.
There's some validation to check if you're not running this from the browser:
if (typeof window === 'undefined') return _store
This typically means that you're using the redux store in a data fetching function of nextjs like in this example https://github.com/vercel/next.js/blob/canary/examples/with-redux/pages/ssr.js.
Also the useStore function is memoizing the state to see if it should re run initializeStore, only if the initial state changes.
I trying to load all the asynchronous data like user objects from asynchronous storage when the store first-time initialize. But I do not have any idea how to do that. I got one of the suggestions to create a middleware to do this but I don't know-how.
Current Store and its initialization in app
const store = createStore(rootReducer, {}, applyMiddleware(ReduxThunk,
//actionCallbackOnceMiddleware(INITIAL_AJAX_END, AppSafeAreaProvider)
))
const AppSafeAreaProvider = () => {
return <Provider store={store}>
<PaperProvider>
<SafeAreaProvider>
<App />
</SafeAreaProvider>
</PaperProvider>
</Provider>
}
AppRegistry.registerComponent(appName, () => AppSafeAreaProvider);
I'm not sure about the method for setting up redux on React Native since I only use React.js but if you are going to setup your store to handle asynchronous call handlers, your best bet would be to look into middlewares.
https://redux.js.org/api/applymiddleware
Here is an excellent example of how the async requests can be called using the middleware and redux-thunk
https://redux.js.org/advanced/async-actions
At the end of the example, it shows how the redux-thunk can be used to initialize your store with an async API call.
You don't need default state globally because reducers already have a default state parameter on redux
For example
const someReducer = (defaultState = {foo: 'bar'}, action) => {
switch(action.parameter)
{
...
default return defaultState
}
}
Your state
{
someReducer: { foo: 'bar' }
}
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!
I am trying to test a workflow with a React app. When all fields are filled withing a workflow step, user is able to click to the "next" button. This action registers a state in a reducer and changes the URL to go to the next workflow step.
According to the RTL documentation, I wrap my component under test in a store provider and a connected router using this function:
export const renderWithRedux = (ui: JSX.Element, initialState: any = {}, route: string = "/") => {
// #ts-ignore
const root = reducer({}, { type: "##INIT" })
const state = mergeDeepRight(root, initialState)
const store = createStoreWithMiddleWare(reducer, state)
const history = createMemoryHistory({ initialEntries: [route]})
const Wrapper = ({ children }: any) => (
<Provider store={store}>
<ConnectedRouter history={history}>{children}</ConnectedRouter>
</Provider>
)
return {
...render(ui, { wrapper: Wrapper }),
// adding `store` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
history,
store
}
}
Unlike in the documentation, I amm using connected-react-router, not react-router-dom, but I've seen some people using connected-react-router with RTL on the web so I don't think the problem come from here.
The component under test is wrapped in a withRouter function, and I refresh the URL via the connected react router push function, dispatching via the redux connectfunction:
export default withRouter(
connect<any, any, any>(mapStateToProps, mapDispatchToProps, mergeProps)(View)
)
Everything work well in production, but the page doesn't refresh when I fire a click event on the "next" button. Here is the code of my test (to make it easier to read for you, I have filled all field and enable the "next" button):
const {
container,
queryByText,
getAllByPlaceholderText,
getByText,
debug,
getAllByText
} = renderWithRedux(<Wrapper />, getInitialState(), "/workflow/EXAC")
await waitForElement(
() => [getByText("supplierColumnHeader"), getByText("nextButton")],
{ container }
)
fireEvent.click(getByText("nextButton"))
await waitForElement(
() => [getByText("INTERNAL PARENT"), getByText("EXTERNAL PARENT")],
{ container }
)
Any clue of what is going wrong here?