How to use RTK query and redux thunk in same react app? - javascript

I have requirement where I have to use RTK query for data fetching and Redux-thunk for global state management. For the same I have created 2 separate stores, one for RTK and another for redux. Now I am facing issue as we should not have 2 stores in our application.
store 1:-
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import combineReducer from './combineReducer';
export const configureStore = () : any => {
const middleWares = [thunk];
const middlewareEnhancer = applyMiddleware(...middleWares);
const enhancers = [middlewareEnhancer];
const composedEnhancers: any = compose(...enhancers);
const store = createStore(combineReducer, composedEnhancers);
return store;
};
store 2:-
import { configureStore } from "#reduxjs/toolkit";
import { setupListeners } from "#reduxjs/toolkit/query";
import { postApi } from "../services/posts";
export const store = configureStore({
reducer: {
[postApi.reducerPath]: postApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(postApi.middleware),
});
setupListeners(store.dispatch);
App.tsx:-
const App: React.FC = () => (
<Provider store={store}>
<Provider store={rtkStore}>
<Suspense fallback={<div>Loading...</div>}>
<BrowserRouter>
<Routes />
</BrowserRouter>
</Suspense>
</Provider>
</Provider>
);
I am sure, there is something fishy in this. Any solution for requirement would be appreciable.

Redux Toolkit comes with thunk per default. You do not need to set it up. RTK Query internally is based on thunks. You cannot have multiple Redux stores in one application.
Just create one store with configureStore, also add your other reducers there and use that for everything.
Generally we recommend in the Redux style guide to use Redux Toolkit for all your Redux logic, as helpers like createSlice massively reduce your boilerplate code and guard against most common bugs.
This is the default recommendation for over two years now.
I would highly recommend you to read the official Redux Essentials tutorial if you haven't already.

Related

Redux Toolkit HMR in React Native does not update reducers

TL;DR: Configuring the Redux Toolkit store in React Native for HMR properly refreshes app on changes, but reducer behaviour does not change!
As seen in the React Native Reloading Announcement, HMR is supported for Redux within React Native, but the steps listed do not appear to work anymore. I have configured everything as mentioned in the announcement; however, the reducer reloading does not actually seem to change anything. When I edit a reducer, the app updates (reloading notification) but the reducer behaviour does not change!
// store/rootReducer.js
import { combineReducers } from "#reduxjs/toolkit";
import eventsReducer from "./slices/events";
import peopleReducer from "./slices/people";
const rootReducer = combineReducers({
events: eventsReducer,
people: peopleReducer,
});
export default rootReducer;
// store/index.js
import { configureStore } from "#reduxjs/toolkit";
import rootReducer from "./rootReducer";
const store = configureStore({
reducer: rootReducer,
});
if (process.env.NODE_ENV === "development" && module.hot) {
module.hot.accept(() => {
const newRootReducer = require("./rootReducer").default;
store.replaceReducer(newRootReducer);
});
}
export default store;
// App.js
import React from "react";
import { Provider } from "react-redux";
import store from "./store";
const App = () => {
return (
<Provider store={store}>
// ...ThemeProvider, Navigation scenes, etc
</Provider>
);
};
export default registerRootComponent(App);
Any ideas why this may be happening? I've been searching for related questions but cannot find anything from the recent past (ie. this year). Any older issues do not work either. My understanding is that I do not have to handle App HMR, as this is handled automatically by React Native. The announcement above makes it appear that adding HMR to Redux in React Native is super easy; however, it is has proven to not be the case.

Initiate data with redux better approach

hello guys I am trying to fetch initial data for redux store from given api !
here is my first approach from using store.dispatch:
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer";
import { fetchNews } from "./actions";
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
store.dispatch(fetchNews());
export default store;
and second approach is dispatching action from the component lifecycle method like this:
import React from "react";
import axios from "axios";
import InfiniteScroll from "react-infinite-scroller";
import { useSelector, useDispatch } from "react-redux";
import New from "./New";
import { fetchNews } from "../store/actions";
const Main = () => {
const news = useSelector(state => state.news);
const dispatch = useDispatch();
React.useEffect(() => {
dispatch(fetchNews());
}, [dispatch]);
return (
<>
{news.length > 0 ? (
news.map(data => (
<New key={data.id} subTitle={data.created_at}>
{data.title}
</New>
))
) : (
<p>News not found</p>
)}
</>
);
};
OK both method works but i really want to know what is the better approach here or is there any other better method! thank you!
Dispatching action from the component lifecycle method is the recommended approach and is what should be followed in order to maintain the separation of concern design on which Redux is based on please check the attached image.
The second one. The file in which you create the store should always be all about creating the store and exporting it as is.
By the way, on your useEffect, try removing the dispatch from the variables array:
React.useEffect(() => {
dispatch(fetchNews());
}, []);
This way it will fetch the data on component mount, just like ComponentDidMount.
I wouldn't say dispatching from component lifecycle is the recommended approach , it certainly is used by many developers, but it couples in you component with data fetching (is that a good idea) ? Or should your component not know about how data is fetched and just consume it in some way. Your choice.
You could use your own custom middleware to send the request.
With that approach you have greater flexibility , you could dispatch actions like type:LOADING_DATA and any others depending on setting state while loading your data. Once the data is returned dispatch other action(s) which could be handled by other custom middleware(s) to normalise data etc and finally send it to your reducer.
This approach is described very well in https://leanpub.com/thinking-in-Redux or https://www.youtube.com/watch?v=JUuic7mEs-s
I personally liked his ideas and perhaps you would find them useful.

Cannot access redux store in actions/api service (without connect() func)

I'm trying to fetch the token from my auth reducer in my app for making subsequent requests for more resources.
The problem is I can't access the store.getState() or store anywhere outside my components. Like actions/api service.
I remember earlier making an app where I was able to without any problems.
Here's a contrived example : https://stackblitz.com/edit/react-redux-app-1wxxab?file=index.js I've made a basic todo app and you can see in actions/index.js that when I console.log(store) I get undefined.
Update: I've updated the example to emphasise the problem, where I can't access it in a separate file api.js
Another Example: https://stackoverflow.com/a/43944684/1356046 they say it works like this but I'm not able to reproduce it.
Anyway to fix this and access the store state? Have tried everything since yesterday. Thanks.
Export store when you created it, then use it eg store.dispatch(action); or create api specific middleware
See What is the best way to access redux store outside a react component? for many examples
Update:
https://stackblitz.com/edit/react-redux-app-1mswrv
store.js:
import { configureStore } from "redux-starter-kit";
import rootReducer from './reducers'
export const store = configureStore({
reducer: rootReducer,
});
index.js:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import { store } from "./store";
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
api.js:
import {store} from './store';
// Some promise which dispatches a fetch request after getting the token from the store
export const getSomething = () => {
console.log('store:', store);
return store;
}
Cloned your stackblitz and made the following changes:
In your api file do the following:
import store from './store';
Add a store.js with the content:
import { configureStore } from "redux-starter-kit";
import rootReducer from './reducers'
const store = configureStore({
reducer: rootReducer,
});
export default store;
And changed your index.js to:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import store from './store'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
I would still opt for saving a token in local storage, if the user is logged in and opens another tab the user needs to log in again because the other tab has no access to the token, same when the user goes to another site and then back to your site.
If you are using redux-thunk and passing your api with withExtraArgument() to the thunk middleware passed to createStore() then you can use a lazy callback function to inject selectors for your API token (or whatever else you'd like) to a wrapped API helper function.
Personally, I prefer this approach as it decouples the api helper from redux. The API helper does not have to know about redux at all for this to work. Insread of imports, you simply inject the selector into the API helper, which then calls them when needed (long after the store is initialized).
api.js
// function accepting callback that returns a pretty
// standard post function
export const post = (getHeaders) => (url, body) => {
return fetch(url, {
method: 'POST',
headers: getHeaders(),
...
}
}
store.js
import * as api from './utils/api';
import { selectAuthHeaders } from './features/auth'
const store = createStore(
rootReducer,
initialState,
applyMiddleware(
thunk.withExtraArgument({
post: api.post(() => {
return selectAuthHeaders(store.getState());
}
})
)
)

In react-admin get access to redux store

My question is related to react-admin repo.
I want to dispatch an action, outside of scope of a component, in order to do that, I've read that I need to get access to the actual redux store itself, and dispatch on in directly,
so I know that the Admin component has an initialState prop, but it only accepts default state object, not the store. So I can't make a store and pass it in.
My question is:
How do I access redux store of an Admin component?
How can I dispatch an action outside of a component, when using Admin as my main app component?
my current app entry looks like this:
<AppLayoutDirection>
<Admin
title="My App"
locale="en"
dataProvider={dataProvider}
authProvider={authProvider}
i18nProvider={i18nProvider}
theme={themeProvider}
customSagas={customSagas}
appLayout={AppLayout}
>
{DynamicResource}
</Admin>
</AppLayoutDirection>
When you say that you need to dispatch an action outside the scope of a component, I suppose that it's in reaction to another action that was dispatched in the past.
In that case, that's what react-admin calls a side effect. React-admin handles side effects using redux-saga. Here is how to create a custom saga:
// in src/bitcoinSaga.js
import { put, takeEvery } from 'redux-saga/effects';
import { showNotification } from 'react-admin';
export default function* bitcoinSaga() {
yield takeEvery('BITCOIN_RATE_RECEIVED', function* () {
yield put(showNotification('Bitcoin rate updated'));
})
}
Register this saga in the <Admin> component as follows:
// in src/App.js
import React from 'react';
import { Admin } from 'react-admin';
import bitcoinSaga from './bitcoinSaga';
const App = () => (
<Admin customSagas={[ bitcoinSaga ]} dataProvider={simpleRestProvider('http://path.to.my.api')}>
...
</Admin>
);
export default App;
This is documented in the react-admin documentation, in the <Admin> chapter.
You could also simply use custom reducers if the computation is no async
// in src/App.js
import React from 'react';
import { Admin } from 'react-admin';
import reducers from './reducers';
const App = () => (
<Admin customReducers={customReducers} dataProvider={simpleRestProvider('http://path.to.my.api')}>
...
</Admin>
);
export default App;

Promise Redux : TypeError: next is not a function

I have a little app running on React + Redux with redux-promise middleware in order to deal with promise API call. I would like to manage the state of each api call (loading, success & error). So, as the official documentation says I change the applyMiddleware argument.
import React from 'react';
import ReactDOM from 'react-dom';
import promiseMiddleware from 'redux-promise';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, browserHistory } from 'react-router';
import reducers from './reducers';
import routes from './routes';
const createStoreWithMiddleware = applyMiddleware(
promiseMiddleware({
promiseTypeSuffixes: ['LOADING', 'SUCCESS', 'ERROR'],
}),
)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={browserHistory} routes={routes} />
</Provider>
, document.querySelector('.container'));
But when I change the appyMiddleware my app doesn't work anymore, I have this error showing on console:
TypeError: next is not a function
If I change back this line, everything works again as intended.
const createStoreWithMiddleware = applyMiddleware(promiseMiddleware)(createStore); // App works again
What's wrong?
Thank you,
I strongly believe that the way you create the store is incorrect.
In creating a store, there are 3 arguments,
createStore(reducer, preloadedState, enhancer) {...}
(1) reducer -> (required)
(2) preLoadedState -> (optional)
(3) enhancer -> (optional) // This is where your applyMiddleware should be passed. Hence, the minimal creation of a store is,
const store = createStore(() => {});
To answer your question, creating a store with a middleware should be,
const store = createStore(yourReducer, {}, applyMiddleware(promiseMiddleware))
or simply
const store = createStore(yourReducer, applyMiddleware(promiseMiddleware))
// This should also work because if the 2nd parameter is a function then it will be treated as the enhancer.

Categories