I am converting my project from flux to redux.
The root component is very straight forward redux :
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux'
import AppReducer from './AppReducer'
import { createStore } from 'redux'
const store = createStore(AppReducer)
ReactDOM.render(
<Provider store={store}>
//rest of application goes here
</Provider>,
app);
Using mapToProps, mapToDispatch I can deal with behaviour at the compoment level just fine.
Now I am coming to server responses, ie asynch calls.
In my flux implementation I have a file a call 'Server' I have my AJAX call and a promise is returned which then calls AppDispatcher.dispatch :
function handleResponse(endpoint) {
return function (err, res) {
if (res && res.ok) {
receiveData(endpoint, res);
}
};
}
function receiveData(endpoint, responseData) {
AppDispatcher.dispatch(
{
type: ActionTypes.SERVER_RESPONSE,
endpoint: endpoint,
state: "ReponseIsOK",
payload: responseData.body
}
);
dispatch(endpoint, "CommunicationState.RESPONSE_OK", responseData.body);
}
How should I convert this into redux?
I need to somehow get to the dispatcher, and I was hoping something like this would work :
function receiveData(endpoint, responseData) {
let store = Provider.getStore();
store.dispatch(
{
type: ActionTypes.SERVER_RESPONSE,
endpoint: endpoint,
state: "ReponseIsOK",
payload: responseData.body
}
);
}
I have tried provider.store, provider.context.store and they also seem to not work.
I really just need access to the store so that I can fire events, but not sure how to access it.
I'm pretty sure this is a situation to use Redux-Thunk, which allows you to dispatch actions asynchronously.
Here is the explanation from the docs:
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
I think you would first need to modify your creation of the store and pass in redux-thunk as a middleware and then you can modify your receiveData function to be something like this:
function receiveData(endpoint, responseData) {
return (dispatch, getState) => {
// DISPATCH YOUR ACTION WITH RESPONSE DATA
// AND ACCESS THE REDUX STORE
}
}
You just need to save a reference to the store when you create it with configureStore. You could just set up a getter if you'd like to access this from outside.
Pseudo code:
var store = configureStore()
// ...
export function getStore() {
return store
}
// ...
<Provider store={ store } />
Related
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
}
}
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 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'm very new to react and redux.
Now I want to rewrite my post request with a redux process.
my current request looks like this:
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
this.setState({
shortenInfos: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
now I created a store:
export default function url(state = 0, action) {
switch (action.type) {
case 'CREATE_URL':
// maybe axios request?!
return `${action.url}/test`
case 'CREATED_URL':
return `${action.url}/created`
default:
return state
}
}
so where I must use my store.dispatch()? Should I make my _handleSubmit something like this?
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
store.dispatch({
type: 'CREATED_URL',
url: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
I think this is wrong? And where I must use mapStateToProps method? Or should I do the axios-request in my CREATE_URL in my reducer?
Introduction
Using React with Redux gives you high freedom on how you can do things. The downside of this is that it can be hard to find out how things should be done properly, mainly because there is no standard or comprehensive guide to the use of the many dependency you need for a properly implemented project. This answer will guide you through the basics with links to references that will help you to find out wheres next and how to deeper your knowledge.
Reducer
Reducers should be pure, meaning that they have no side effects (like making axios requests) and they should always return a new object/array/value instead of changing the previous state. It is also a good practice to use action types as constants. You can place action types wherever you want, but for simplicity I will put them into the reducer's file, but there are better ways to organize them like using ducks.
export const CREATED_URL = 'CREATE_URL';
export default const url = (state = '', action) => {
switch (action.type) {
case CREATED_URL:
return action.url;
default:
return state;
}
};
Asynchronous actions
Everything that causes a side effect should be an action, so XHR should happen there. Because XHR should be asynchronous it is recommended to use a middleware: redux-thunk and redux-saga are two popular solutions. I will go with thunk so install it first.
First (because const has temporal dead zone) you need an action that will "load" the result of the XHR to the store:
import { CREATED_URL } from './reducer';
const createdUrl = url => ({
type: CREATED_URL,
url, // ES6 trailing comma for prettier git diffs
});
Then you can create the action that will fire the XHR, wait for the response then load it to the store using the action created previously. We need to return a function that will receive dispatch as the parameter. This technique is used in functional programming and is called currying.
export const createUrl = url => dispatch => { // with only 1 parameter the parentheses can be omited
axios
.post('/createUrl', { url }) // ES6 Shorthand property name in { url }
.then(response => {
dispatch(createdUrl({
url: response.data,
})
})
.catch(error => {
// #TODO dispatch an action that will show a message
// notifying the user that the request failed
console.log(error);
});
}
Usage in the React component.
Preparation
For ease of use, you need to connect your React component with Redux. react-redux comes to the rescue. Read the API documentation and add the <Provider> component to the root of your React component tree.
Now, in the top of your React component's file, import all the necessary stuff:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { createUrl } from './reducer';
mapStateToProps and mapDispatchToProps
Then create the two helper functions for connect:
const mapStateToProps = store => ({ url: store.url })
const mapDispatchToProps = dispatch => bindActionCreators({ createUrl }, dispatch)
With the help of mapStateToProps you can subscribe to store updates and inject the important parts of the Redux store to your components props. mapStateToProps should return an object that will be merged to the component's props. Usually we just do something like store => store.url but because our example is so simple that the reducer returns a plain string instead of something more complex in an object, we need to wrap that string into an object over here.
mapDispatchToProps with the help of bindActionCreators will inject the passed actions to the component's props so we can call and pass them down to subcomponents with ease: this.props.createUrl().
The component itself
Now we can create the component itself. I will use an ES6 class to show an example with componentDidMount, but if you don't need that and you have a stateless component, you can go with a function too.
class Example extends React.Component {
componentDidMount() {
// If you need to load data from a remote endpoint place the action call here, like so:
// this.props.createUrl('your-url');
}
render() {
return (
<div>
<div>URL injected from the store, automatically refreshed on change: {this.props.url}</div>
<div onClick={event => {this.props.createUrl('your-url');}}>Click me to fetch URL</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Example)
tl;dr: I need an example of an asynchronous redux-thunk action shows how to make an async call (e.g. fetch), and trigger a state update. I also need to see how someone might chain multiple such actions together, like: (1) see if user exists in cloud, then (2) if no, register them, then (3) use the new user record to fetch more data.
All the examples I've found makes the assumption that the redux store can be imported directly into the module that defines the actions. It's my understanding that this is a bad practice: the calling component is responsible for providing access to the store, via this.props.dispatch (which comes from the store being injected via the <Provider>).
Instead, every action in the redux world should return a function that will receive the appropriate dispatch; that function should do the work, and return... something. Obv, it matters what the something is.
Here's the pattern I've tried, based on the documentation, that has proven to be a failure. Nothing in the docs makes it clear why this doesn't work, but it doesn't -- because this action doesn't return a promise.
/**
* pushes a new user into the cloud; once complete, updates the store with the new user row
* #param {hash} user - of .firstName, .lastName
* #return {promise} resolves with user { userId, firstName, lastName, dateCreated }, or rejects with error
*/
Actions.registerUser = function(user) {
return function reduxAction(dispatch) {
return API.createUser(user) // API.createUser just does return fetch(...)
.then(function onUserRegistered(newUser) {
return dispatch({
type: 'ADD_USERS',
users: [newUser]
});
});
};
};
I have a reducer that responds to the ADD_USERS event; it merges the incoming array of one or more users with the array of users already in memory. Reducers are easy to write. That's why I switched to redux: one store, pure functions. But this thunk business is an absolute nightmare.
The error I receive is that .then is undefined on Actions.registerUser -- i.e. that Actions.registerUser doesn't return a promise.
I think the problem is obviously that I'm returning a function -- the reduxAction function -- but that doesn't seem to be negotiable. The only way to shoot data at the store is to use the dispatch method that is provided, and that means I can't return a promise.
Changing the onUserRegistered to simply invoke dispatch and then return the desired value doesn't work either, nor does having it return an actual promise.
PLZ HALP. I really don't get it. I can't believe people put up with all this.
EDIT: To provide some context, here's the kind of action composition I think I'm supposed to be able to perform, and which these thunk actions are frustrating:
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId() // looks for userId in local storage, or generates a new value
.then(Actions.storeUserId) // pushes userId into local storage
.then((userId) => {
return Actions.fetchUsers(userId) // fetches the user, by id, from the cloud
.then((user) => {
// if necessary, pushes the user into the cloud, too
return user || Actions.postUser({ userId: userId, firstName: 'auto-registered', lastName: 'tbd'});
});
})
.then((user) => {
console.log(`boot sequence complete with user `, user);
return dispatch({ type: 'ADD_OWNER', user });
});
};
};
I would expect that Actions.storeUserId and Actions.fetchUsers would, in addition to returning promises that resolve with values of my choosing, dispatch data to the store as a side-effect. I think the dispatch is occurring, but the chain breaks because none of these actions return promises - they return plain functions.
Not only does this seem much worse than Flux, it seems incomprehensible. I can't believe that all this madness was necessary just to consolidate app state into a single reducing store.
And yes -- I have tried the new version of flux, with its ReducerStore, but it has some inappropriate dependencies on CSS libraries that are incompatible with react-native. The project maintainers have said they don't intend to resolve the issue. I guess their state container is dependent on CSS functionality.
EDIT: my store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import Reducers from './reducers';
const createStoreWithMiddleWare = applyMiddleware(thunk)(createStore);
export const initialState = {
users: [] // will hold array of user objects
};
const store = createStoreWithMiddleWare(Reducers);
export default store;
EDIT: Here's the calling code. This is the root-level react-native component.
// index.ios.js
import Store from './store';
class myApp extends Component {
componentDidMount() {
Store.dispatch(Actions.bootSetup())
.then(() => {
console.log('*** boot complete ***');
});
}
render() {
return (
<Provider store={Store}>
<ApplicationRoutes />
</Provider>
);
}
}
My assumption is that Store.dispatch expects a function, and provides it with a reference to the store's dispatch method.
I can see one mistake right off the bat
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId()
You aren't chaining thunk actions correctly. If your actions returns a function, you need to pass dispatch to that action.
Take a look at this action creator(this is a fully functional real-world app, feel free to poke around), look at the 9th line, where loginUser is called.
export function changePassword(credentials) {
return (dispatch, getState) => {
dispatch(changePasswordStart(credentials))
return Firebase.changePassword(credentials)
.then(() => {
return logout()
})
.then(() => {
return loginUser(credentials.email, credentials.newPassword)(dispatch)
})
.then(() => {
dispatch(changePasswordSuccess(credentials))
toast.success('Password successfully changed')
}).catch(error => {
dispatch(changePasswordError(error.code))
toast.error('An error occured changing your password: ' + error.code)
})
}
}
Because loginUser is also a thunk action, it needs to have dispatch passed to the result of calling it. It makes sense if you think about it: the thunk doesn't do anything, it just creates a function. You need to call the function it returns to get it to do the action. Since the function it returns takes dispatch as an argument, you need to pass that in as well.
Once that's done, returning a promise from a thunk action will work. In fact, the example I gave above does exactly that. loginUser returns a promise, as does changePassword. Both are thenables.
Your code probably needs to look like this (though I am not sure, I don't have the actions being called)
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId()(dispatch) // pass dispatch to the thunk
.then(() => Actions.storeUserId(dispatch)) // pass dispatch to the thunk
.then((userId) => {
return Actions.fetchUsers(userId)(dispatch) // pass dispatch to the thunk
.then((user) => {
// pass dispatch to the thunk
return user || Actions.postUser({ userId: userId, firstName: 'auto-registered', lastName: 'tbd'})(dispatch);
});
})
.then((user) => {
console.log(`boot sequence complete with user `, user);
return dispatch({ type: 'ADD_OWNER', user });
});
};
};