I'm currently mastering redux and haven't really figured out how to output the data you get from the server using thunk. I in useEffect do a thunk dispatch, get server data, but they don't output because they come after a couple of seconds, so in useState just an empty array, and in store the desired data
// retrieve data from the store
const servicesData = useAppSelector((state) => state.services.services);
const [services, setServices] = useState(servicesData);
// store
export const store = createStore(
reducers,
{},
composeWithDevTools(applyMiddleware(thunk))
);
I understood that useState does not pick up data that comes after a couple of seconds, it turns out it is necessary to add data through setState, but then why thunk? is it necessary to use it at all when receiving any data? I'm completely confused
Your problem is the useState call. Never put the result from a useSelector into a useState. It will be used as the "initial state" of the useState and never update that state when the value from useSelector updates - useState doesn't work like that. Just directly use the value from store.
const services = useAppSelector((state) => state.services.services);
Redux Thunk is middleware that allows you to return functions,
rather than just actions, within Redux.
This allows for delayed actions, including working with promises.
One of the main use cases for this middleware is
for handling actions that might not be synchronous,
for example, using axios to send a GET request.
Redux Thunk allows us to dispatch those actions asynchronously
and resolve each promise that gets returned.
` import { configureStore } from '#reduxjs/toolkit'
import rootReducer from './reducer'
import { myCustomApiService } from './api'
const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
thunk: {
extraArgument: myCustomApiService
}
})
})
// later
function fetchUser(id) {
// The `extraArgument` is the third arg for thunk functions
return (dispatch, getState, api) => {
// you can use api here
}
}
Related
I created a react app using create-react-app and configured a redux store with reducers. I also added firebase and my project works fine. The components can trigger an action that fetches a collection from firestore, and it in return, updates the redux store.
What is the best way to integrate firebase and redux store?
The way I am currently doing it, is to have a separate action that triggers the fetch/delete/onSnapshot from firebase, and handing a reference to dispatch so that firebase function can take its time executing the command, then it can call dispatch with an action that updates the store.
But I wanted all of my actions in a single file, for a better (separation of concerns). Therefor, firebase can call dispatch but the action creator lives in my actions.js file. This way, I can later decide to change the action names in a single file, if I decided to do that.
The problem with this approach, is I will require a separate action to trigger the async function with firebase, and another action creator for when the promise is fulfilled.
What is a better approach to what I am doing?
store.js
const rootReducer = combineReducers({
cards: cardsReducer,
});
const store = createStore( rootReducer , {}, applyMiddleware(thunk));
export default store;
myFirebase.js
// I need this to be called from an action in actions.js
// therefor, I am exporting it, and also, I am handing it dispatch
// so it will call store.dispatch once data is ready
export const fetchCardsFromFirebase = async (dispatch) => {
const cardsCollection = collection(db, "cards");
const cardsSnapshot = await getDocs(roomsCollection);
const cards = roomsSnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }));
// here I can either explicitly dispatch an action
/*
dispatch({
type: CARDS_FETCHED //this constant string will have to be imported
payload: cards
});
*/
// or I can let an action in actions.js do the above:
dispatch(cardsFetched(rooms)); //this function is imported from actions.js
}
actions.js
import { FETCH_CARDS , CARDS_FETCHED } from "./types";
import { fetchCardsFromFirebase } from "../myFirebase";
export const fetchCards = () => async (dispatch) => {
fetchCardsFromFirebase(dispatch); // give firebase access to dispatch
dispatch({
type: FETCH_CARDS,
payload: {message: "fetching cards... please wait"}
});
};
const cardsFetched = (cards) => ({
action: CARDS_FETCHED,
payload: cards
});
Generally, this is a very old style of Redux - modern Redux does not use switch..case reducers or ACTION_TYPES and switching to modern Redux will proably already save you 50% of your code.
That said, the official Redux Toolkit (RTK) also comes with RTK-Query, which is a data caching abstraction that should also work fine with firebase and will generate reducers, actions and even hooks automatically for you. (Hint: with firebase you will need to use queryFn). That would save you a lot more code as well.
I would recommend you to follow the official Redux Tutorial which first shows modern Redux and in later chapters goes into RTK Query.
I would like to create some middleware to check a tokens expires_at field refreshing the token if necessary and updating the state for the token fields.
I have used the redux toolkit to create the redux functionality, making use of slices. I have a slice that will update the state with token data and this works on the callback for the initial token.
When creating the middleware I can not get the slice to be called and therefore the state remains unchanged.
As a simple spike without an api call:
import { useSelector, useDispatch } from 'react-redux';
import { setTokens } from '../features/tokens/tokenSlice';
const refresher = () => ({ dispatch, getState }) => (next) => (action) => {
const exp_at = useSelector((state) => state.tokens.expires_at);
if(hasBreachedThreshold(exp_at)){
dispatch = useDispatch();
dispatch(
setTokens({
access_token: 'new token',
expires_at: 637423776000000000, -- hard coded date way in the future.
})
}
... else carry on.
);
I would expect when the middleware is called, on the first pass hasBreachedThreshold() returns true and the dispatch method would call the slice reducer and update the state. Any further runs would pass over as hasBreachedThreshold() would return false - for a while anyway.
What is happening though is that the hasBreachThreshold always returns false because the state is never updated, causing an indefinite loop.
The middleware is configured correctly and is called.
The expires_at value is extracted from the state.
hasBreachThreshold() is tested thoroughly and behaves correctly.
Being fairly new to React / Redux I expect my understanding of how and when to use dispatch is wrong. I assumed I could use dispatch similarly to how I do in my components, is this not the case? or am I going about this totally the wrong way?
When writing a middleware you have already the dispatch function and Redux store available through the function params:
// Use `dispatch` & `getState`
const refresher = () => ({ dispatch, getState }) => (next) => (action) => {
const exp_at = getState().tokens.expires_at;
if(hasBreachedThreshold(exp_at)){
dispatch(
setTokens({
access_token: 'new token',
expires_at: 637423776000000000, -- hard coded date way in the future.
})
}
);
Also you have essential mistake of when to use React hooks, refer to Rules of Hooks.
Hooks are not available in context of Redux middleware.
I have a function in utils.fetch_json that returns a promise of a json (or some error in json). I want to test that, when an action that uses fetch_json is dispatched, updateData here, the store state is updated with the payload of the json when the API's json is valid.
Case in point:
import {createStore, Â applyMiddleware} from "redux";
import Store from "../../reducers/foobar";
import { updateData } from "../../actions/foobar";
import utils from '../../utils'
import thunk from 'redux-thunk';
const data = {
a: ["A1", "A2"],
b: [20, 10],
}
describe("Test the reducer-actions", () => {
utils.fetch_json = jest.fn().mockResolvedValue(data);
it("updates the state when updateData is dispatched", () => {
const store = createStore(Store, applyMiddleware(thunk));
store.dispatch(updateData())
expect(store.getState().data).toEqual(data);
});
});
this fails because the execution of store.dispatch(updateData()) is async and thus the expect is executed before it, causing the initial state, not data, to be in the store.
I am trying to avoid a dependency for this, but I am willing to use one if the notation is similar to jest.
Note that I am not trying to:
test the reducer in isolation
test the action creator in isolation
test a thunk-action in isolation
I am interested in testing them in integration.
If updateData returns then you can do:
store.dispatch(updateData()).then(()=>test here)
That is assuming that updateData returns a promise.
I am trying to load data when my component loads using componentDidMount. However calling the Redux action, making the call with axios seems to freeze the UI. When I have a form with 12 inputs and one makes an API call I would assume I can type in the other inputs and not have them freeze up on me.
I've tried reading some other posts on the subject but they are all a little different and everything I have tried doesn't seem to resolve the issue.
I am working on linux using React 16.8 (when using RN I use 55.4)
I have tried making my componentDidMount async as well as the redux-thunk action. It didn't seem to help anything, so I must be doing something wrong.
I tried doing the following with no success. Just using short form for what I tried. Actual code listed below.
async componentDidMount() {
await getTasks().then();
}
And I tried this
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
Current Code:
Component.js
componentDidMount() {
const { userIntegrationSettings, getTasks } = this.props;
// Sync our list of external API tasks
if (!isEmpty(userIntegrationSettings)) {
getTasks(userIntegrationSettings.token)
// After we fetch our data from the API create a mapping we can use
.then((tasks) => {
Object.entries(tasks).forEach(([key, value]) => {
Object.assign(taskIdMapping, { [value.taskIdHuman]: key });
});
});
}
}
Action.js
export const getTasks = () => ((dispatch, getState) => {
const state = getState();
const { token } = state.integrations;
const URL = `${BASE_URL}/issues?fields=id,idReadable,summary,description`;
const AJAX_CONFIG = getAjaxHeaders(token);
dispatch(setIsFetchingTasks(true));
return axios.get(`${URL}`, AJAX_CONFIG)
.then((response) => {
if (!isEmpty(response.data)) {
response.data.forEach((task) => {
dispatch(addTask(task));
});
return response.data;
} else {
dispatch(setIsFetchingTasks(false));
}
})
.catch((error) => {
dispatch(setIsFetchingTasks(false));
errorConsoleDump(error);
errorHandler(error);
});
});
reducer.js
export default (state = defaultState, action) => {
switch (action.type) {
case ADD_TASK:
case UPDATE_TASK:
return update(state, {
byTaskId: { $merge: action.task },
isFetching: { $set: false }
});
default:
return state;
}
};
So in my answer what are you going to learn?
General data loading with Redux
Setting up a component lifecycle method such as componentDidMount()
Calling an action creator from componentDidMount()
Action creators run code to make an API request
API responding with data
Action creator returns an action with the fetched data on the payload property
Okay, so we know there are two ways to initialize state in a Reactjs application, we can either invoke a constructor(props) function or we can invoke component lifecycle methods. In this case, we are doing component lifecycle methods in what we can assume is a class-based function.
So instead of this:
async componentDidMount() {
await getTasks().then();
}
try this:
componentDidMount() {
this.props.fetchTasks();
}
So the action creators (fetchTasks()) state value becomes the components this.props.fetchTasks(). So we do call action creators from componentDidMount(), but not typically the way you were doing it.
The asynchronous operation is taking place inside of your action creator, not your componentDidMount() lifecycle method. The purpose of your componentDidMount() lifecycle method is to kick that action creator into action upon booting up the application.
So typically, components are generally responsible for fetching data via calling the action creator, but it's the action creator that makes the API request, so there is where you are having an asynchronous JavaScript operation taking place and it's there where you are going to be implementing ES7 async/await syntax.
So in other words it's not the component lifecycle method initiating the data fetching process, that is up to the action creator. The component lifecycle method is just calling the action creator that is initiating the data fetching process a.k.a. the asynchronous request.
To be clear, you are able to call this.props.fetchTasks() from your componentDidMount() lifecycle method after you have imported the action creator to your component like and you have imported the connect function like so:
import React from "react";
import { connect } from "react-redux";
import { fetchTasks } from "../actions";
You never provided the name of the component you are doing all this in, but at the bottom of that file you would need to do export default connect(null, { fetchTasks })(ComponentName)
I left the first argument as null because you have to pass mapStateToProps, but since I don't know if you have any, you can just pass null for now.
Instead of this:
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
try this:
export const fetchTasks = () => async dispatch => {
const response = await axios.get(`${URL}`, AJAX_CONFIG);
dispatch({ type: "FETCH_TASKS", payload: response.data });
};
There is no need to define getState in your action creator if you are not going to be making use of it. You were also missing the dispatch() method which you need when developing asynchronous action creators. The dispatch() method is going to dispatch that action and send it off to all the different reducers inside your app.
This is also where middleware such as Redux-Thunk comes into play since action creators are unable to process asynchronous requests out of the box.
You did not show how you wired up your redux-thunk, but it typically goes in your your root index.js file and it looks like this:
import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
Remember that connect function I said you needed to implement? That came into being as a result of implementing or you should have implemented the Provider tag. With the Provider tag, your components can all have access to the Redux store, but in order to hook up the data to your components you will need to import the connect function.
The connect function is what reaches back up to the Provider and tells it that it wants to get access to that data inside whatever component you have that lifecycle method in.
Redux-Thunk is most definitely what you needed to implement if you have corrected everything as I have suggested above.
Why is Redux-Thunk necessary?
It does not have anything intrinsically built into it, it's just an all-purpose middleware. One thing that it does is allow us to handle action creators which is what you need it to be doing for you.
Typically an action creator returns an action object, but with redux-thunk, the action creator can return an action object or a function.
If you return an action object it must still have a type property as you saw in my code example above and it can optionally have a payload property as well.
Redux-Thunk allows you to return either an action or function within your action creator.
But why is this important? Who cares if it returns an action object or a function? What does it matter?
That's getting back to the topic of Asynchronous JavaScript and how middlewares in Redux solves the fact that Redux is unable to process asynchronous JavaScript out of the box.
So a synchronous action creator instantly returns an action with data ready to go. However, when we are working with asynchronous action creators such as in this case, it takes some amount of time for it to get its data ready to go.
So any action creator that makes an network request qualifies as an asynchronous action creator.
Network requests with JavaScript are asynchronous in nature.
So Redux-Thunk, being a middleware which is a JavaScript function that is going to be called with every single action that you dispatch. The middleware can stop the action from proceeding to your reducers, modify the action and so on.
You setup dispatch(setIsFetchingTasks(true)) but when axios returns you never set it to false. Did you miss to add dispatch(setIsFetchingTasks(false)) before return response.data;?
This could be the reason if your UI waits for the fetchingTasks to finish
I am trying to integrate redux-simple-auth in my existing reudx API. My old implementation used the native fetch and I added headers etc.. This new module provides a fetch replacement however it returns a redux action and am having a bit of a struggle figuring out how to set things up.
My store:
function configureStore (initialState) {
const middlewares = [
authMiddleware, // This is from rediux-simple-auth
apiMiddleware, // My own middleware
thunk
]
const store = createStore(rootReducer, initialState, compose(
applyMiddleware(...middlewares), getInitialAuthState({ storage }))
)
}
My middleware simplified:
import { fetch } from 'redux-simple-auth'
export default store => next => action => {
...
next(my start action...)
return store.dispatch(fetch(API_ROOT + endpoint, { method }))
.then(response => {
next(my success/fail action...)
})
}
When I run this I can see my start and fail actions in redux inspector but not the fetch one (which does trigger a FETCH one)
If I call next instead of store.dispatch then it works in the sense that it tiggers the action but it does not return a promise I cannot get results.
How can I fix this flow?
next is not dispatch. Try this:
export default ({dispatch}) => next => action => ...
Use dispatch to dispatch your new action and use next(action) to pass the original action to the next middleware.