I have a React Native app I'm going to be building that uses WebSockets. I have a WebSocket library written in JavaScript and I'm simply re-using it for this project, which is fantastic.
My question is, being new to React/React Native, what is the best practice for setting up and maintaining all of the traffic going through the WebSocket?
Initially my idea was to create the websocket in the main App component, something like this:
export default class App extends Component {
constructor(props) {
super(props);
this.ws = new WebSocket;
}
componentWillMount() {
console.log(this.ws);
}
render() {
console.log("We are rendering the App component.....");
return (
<View style={styles.container}>
<Text style={styles.welcome}>Hello, world</Text>
</View>
);
}
}
The actual WebSocket class would contain all of the respective connection handling:
ws.onopen = () => {
// connection opened
ws.send('something'); // send a message
};
ws.onmessage = (e) => {
// a message was received
console.log(e.data);
};
ws.onerror = (e) => {
// an error occurred
console.log(e.message);
};
ws.onclose = (e) => {
// connection closed
console.log(e.code, e.reason);
};
My question is, since the data coming through WebSocket will be applicable for state through many components in the React Native app, but it is not a class that will extend React.Component, do I not interact with Redux in the WebSocket class? Do I move all of the WebSocket connection handling to the App component and dispatch actions there to Redux?
What's the common pattern here to instantiate my WebSocket class and ensure that all traffic in it is properly getting passed to Redux so all component's state will funnel correctly?
Great answers here so far. Just wanted to add that where you keep your data should really be a decision based on what type of data it is. James Nelson has an excellent article on this topic that I refer to regularly.
For your case, let's talk about the first 3 types of state:
Data
Communication State
Control State
Data
Your WebSocket connection is generic and could technically return anything, but it's likely that the messages you're receiving are data. For example, let's say you're building a chat app. Then, the log of all messages that have been sent and received would be the data. You should store this data in redux with a messages reducer:
export default function messages(state = [], action) {
switch (action.type) {
case 'SEND_MESSAGE':
case 'RECEIVE_MESSAGE': {
return [ ...state, action.message ];
}
default: return state;
}
}
We don't have to (and we shouldn't) have any WebSocket logic in our reducers, as they are generic and don't care where the data is coming from.
Also, note that this reducer is able to handle sending and receiving in exactly the same way. This is because the network communication is handled separately by our communication state reducer.
Communication State
Since you're using WebSockets, the types of communication state you want to track may differ from my example. In an app that uses a standard API, I would track when a request is loading, failed, or successful.
In our chat app example, you'll probably want to track these request states whenever you send a message, but there could be other things you want to classify as communication state as well.
Our network reducer can use the same actions as the messages reducer:
export default function network(state = {}, action) {
switch (action.type) {
case 'SEND_MESSAGE': {
// I'm using Id as a placeholder here. You'll want some way
// to tie your requests with success/failure receipt.
return {
...state,
[action.id]: { loading: true }
};
} case 'SEND_MESSAGE_SUCCESS': {
return {
...state,
[action.id]: { loading: false, success: true }
};
} case 'SEND_MESSAGE_FAILURE': {
return {
...state,
[action.id]: { loading: false, success: false }
};
}
default: return state;
}
}
This way, we can easily find the status of our requests, and we don't have to bother with loading/success/failure in our components.
However, you might not care about the success/failure of any given request since you're using WebSockets. In that case, your communication state might just be whether or not your socket is connected. If that sounds better to you, then just write a connection reducer that responds to actions on open/close.
Control State
We'll also need something to initiate the sending of messages. In the chat app example, this is probably a submit button that sends whatever text is in an input field. I won't demonstrate the whole component, as we'll use a controlled component.
The takeaway here is that the control state is the message before it's sent. The interesting bit of code in our case is what to do in handleSubmit:
class ChatForm extends Component {
// ...
handleSubmit() {
this.props.sendMessage(this.state.message);
// also clear the form input
}
// ...
}
const mapDispatchToProps = (dispatch) => ({
// here, the `sendMessage` that we're dispatching comes
// from our chat actions. We'll get to that next.
sendMessage: (message) => dispatch(sendMessage(message))
});
export default connect(null, mapDispatchToProps)(ChatForm);
So, that addresses where all of our state goes. We've created a generic app that could use actions to call fetch for a standard API, get data from a database, or any number of other sources. In your case, you want to use WebSockets. So, that logic should live in your actions.
Actions
Here, you'll create all of your handlers: onOpen, onMessage, onError, etc. These can still be fairly generic, as you've already got your WebSocket utility set up separately.
function onMessage(e) {
return dispatch => {
// you may want to use an action creator function
// instead of creating the object inline here
dispatch({
type: 'RECEIVE_MESSAGE',
message: e.data
});
};
}
I'm using thunk for the async action here. For this particular example, that might not be necessary, but you'll probably have cases where you want to send a message then handle success/failure and dispatch multiple actions to your reducers from within a single sendMessage action. Thunk is great for this case.
Wiring It All Together
Finally, we have everything set up. All we have to do now is initialize the WebSocket and set up the appropriate listeners. I like the pattern Vladimir suggested--setting up the socket in a constructor--but I would parameterize your callbacks so that you can hand in your actions. Then your WebSocket class can set up all the listeners.
By making the WebSocket class a singleton, you're able to send messages from inside your actions without needing to manage references to the active socket. You'll also avoid polluting the global namespace.
By using the singleton set up, whenever you call new WebSocket() for the first time, your connection will be established. So, if you need the connection to be opened as soon as the app starts, I would set it up in componentDidMount of App. If a lazy connection is okay, then you can just wait until your component tries to send a message. The action will create a new WebSocket and the connection will be established.
You can create dedicated class for WebSocket and use it everywhere. It's simple, concise and clear approach. Moreover you will have all stuff related to websockets encapsulated in one place! If you wish you can even create singleton out of this class, but the general idea is this:
class WS {
static init() {
this.ws = new WebSocket('ws://localhost:5432/wss1');
}
static onMessage(handler) {
this.ws.addEventListener('message', handler);
}
static sendMessage(message) {
// You can have some transformers here.
// Object to JSON or something else...
this.ws.send(message);
}
}
You have only run init somewhere in index.js or app.js:
WS.init();
And now you can loosely send message from any application layer, from any component, from any place:
WS.sendMessage('My message into WebSocket.');
And receive data back from WebSocket:
WS.onMessage((data) => {
console.log('GOT', data);
// or something else or use redux
dispatch({type: 'MyType', payload: data});
});
So you can use it everywhere even in redux in any action or somewhere else!
There are no official guidelines about that. I think using a component is confusing because it will not be rendered, and I guess if you use Redux you want to share the data from websocket anywhere in the application.
You can give the dispatch function to your Websocket manager.
const store = createStore(reducer);
const ws = new WebSocketManager(store.dispatch, store.getState);
And use this.dispatch inside your class methods.
// inside WebSocketManager class
constructor(dispatch, getState) {
this.dispatch = dispatch;
this.getState = getState;
}
You can also use middlewares to handle side effects, I think it is the recommended way. There are two great libraries that you can look :
redux-saga
redux-observable
Related
In React-Redux project, people usually create multiple actions & reducers for each connected component. However, this creates a lot of code for simple data updates.
Is it a good practice to use a single generic action & reducer to encapsulate all data changes, in order to simplify and fasten app development.
What would be the disadvantages or performance loss using this method. Because I see no significant tradeoff, and it makes development much easier, and we can put all of them in a single file! Example of such architecture:
// Say we're in user.js, User page
// state
var initialState = {};
// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// define component
var User = React.createClass({
render: function(){
// Here's the magic...
// We can just call the generic setState() to update any data.
// No need to create separate dispatchers and reducers,
// thus greatly simplifying and fasten app development.
return [
<div onClick={() => setState({ someField: 1 })}/>,
<div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
<div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
]
}
});
// register component for data update
function mapStateToProps(state){
return { ...state.user };
}
export default connect(mapStateToProps)(User);
Edit
So the typical Redux architecture suggests creating:
Centralized files with all the actions
Centralized files with all the reducers
Question is, why a 2-step process? Here's another architectural suggestion:
Create 1 set of files containing all the setXField() that handle all the data changes. And other components simply use them to trigger changes. Easy. Example:
/** UserAPI.js
* Containing all methods for User.
* Other components can just call them.
*/
// state
var initialState = {};
// generic action
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// API that we export
let UserAPI = {};
// set user name
UserAPI.setName = function(name){
$.post('/user/name', { name }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ name });
});
};
// set user picture URL
UserAPI.setPicture = function(url){
$.post('/user/picture', { url }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ url });
});
};
// logout, clear user
UserAPI.logout = function(){
$.post('/logout', {}, function(){
setState(initialState);
});
};
// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods,
// like some helper methods unrelated to Redux, or Ajax getters.
// Now you have everything related to User available in a single file!
// It becomes much easier to read through and understand.
// Finally, you can export a single UserAPI object, so other
// components only need to import it once.
export default UserAPI
Please read through the comments in the code section above.
Now instead of having a bunch of actions/dispatchers/reducers. You have 1 file encapsulating everything needed for the User concept. Why is it a bad practice? IMO, it makes programmer's life much easier, and other programmers can just read through the file from top to bottom to understand the business logic, they don't need to switch back and forth between action/reducer files. Heck, even redux-thunk isn't needed! And you can even test the functions one by one as well. So testability is not lost.
Firstly, instead of calling store.dispatch in your action creator, it should return an object (action) instead, which simplifies testing and enables server rendering.
const setState = (obj) => ({
type: 'SET_USER',
data: obj
})
onClick={() => this.props.setState(...)}
// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)
You should also use ES6 class instead of React.createClass.
Back to the topic, a more specialised action creator would be something like:
const setSomeField = value => ({
type: 'SET_SOME_FIELD',
value,
});
...
case 'SET_SOME_FIELD':
return { ...state, someField: action.value };
Advantages of this approach over your generic one
1. Higher reusability
If someField is set in multiple places, it's cleaner to call setSomeField(someValue) than setState({ someField: someValue })}.
2. Higher testability
You can easily test setSomeField to make sure it's correctly altering only the related state.
With the generic setState, you could test for setState({ someField: someValue })} too, but there's no direct guarantee that all your code will call it correctly.
Eg. someone in your team might make a typo and call setState({ someFeild: someValue })} instead.
Conclusion
The disadvantages are not exactly significant, so it's perfectly fine to use the generic action creator to reduce the number of specialised action creators if you believe it's worth the trade-off for your project.
EDIT
Regarding your suggestion to put reducers and actions in the same file: generally it's preferred to keep them in separate files for modularity; this is a general principle that is not unique to React.
You can however put related reducer and action files in the same folder, which might be better/worse depending on your project requirements. See this and this for some background.
You would also need to export userReducer for your root reducer, unless you are using multiple stores which is generally not recommended.
I mostly use redux to cache API responses mostly, here are few cases where i thought it is limited.
1) What if i'm calling different API's which has the same KEY but goes to a different Object?
2) How can I take care if the data is a stream from a socket ? Do i need to iterate the object to get the type(as the type will be in the header and response in the payload) or ask my backend resource to send it with a certain schema.
3) This also fails for api's if we are using some third party vendor where we have no control of the output we get.
It's always good to have control on what data going where.In apps which are very big something like a network monitoring application we might end up overwriting the data if we have same KEY and JavaScript being loosed typed may end this to a lot weird way this only works for few cases where we have complete control on the data which is very few some thing like this application.
Okay i'm just gonna write my own answer:
when using redux ask yourself these two questions:
Do I need access to the data across multiple components?
Are those components on a different node tree? What I mean is it isn't a child component.
If your answer is yes then use redux for these data as you can easily pass those data to your components via connect() API which in term makes them containers.
At times if you find yourself the need to pass data to a parent component, then you need to reconsider where your state lives. There is a thing called Lifting the State Up.
If your data only matters to your component, then you should only use setState to keep your scope tight. Example:
class MyComponent extends Component {
constructor() {
super()
this.state={ name: 'anonymous' }
}
render() {
const { name } = this.state
return (<div>
My name is { name }.
<button onClick={()=>this.setState({ name: 'John Doe' })}>show name</button>
</div>)
}
}
Also remember to maintain unidirectional data flow of data. Don't just connect a component to redux store if in the first place the data is already accessible by its parent component like this:
<ChildComponent yourdata={yourdata} />
If you need to change a parent's state from a child just pass the context of a function to the logic of your child component. Example:
In parent component
updateName(name) {
this.setState({ name })
}
render() {
return(<div><ChildComponent onChange={::this.updateName} /></div>)
}
In child component
<button onClick={()=>this.props.onChange('John Doe')}
Here is a good article about this.
Just practice and everything will start to make sense once you know how to properly abstract your app to separate concerns. On these matter composition vs ihhertitance and thinking in react are a very good read.
I started writing a package to make it easier and more generic. Also to improve performance. It's still in its early stages (38% coverage). Here's a little snippet (if you can use new ES6 features) however there is also alternatives.
import { create_store } from 'redux';
import { create_reducer, redup } from 'redux-decorator';
class State {
#redup("Todos", "AddTodo", [])
addTodo(state, action) {
return [...state, { id: 2 }];
}
#redup("Todos", "RemoveTodo", [])
removeTodo(state, action) {
console.log("running remove todo");
const copy = [...state];
copy.splice(action.index, 1);
return copy;
}
}
const store = createStore(create_reducer(new State()));
You can also even nest your state:
class Note{
#redup("Notes","AddNote",[])
addNote(state,action){
//Code to add a note
}
}
class State{
aConstant = 1
#redup("Todos","AddTodo",[])
addTodo(state,action){
//Code to add a todo
}
note = new Note();
}
// create store...
//Adds a note
store.dispatch({
type:'AddNote'
})
//Log notes
console.log(store.getState().note.Notes)
Lots of documentation available on NPM. As always, feel free to contribute!
A key decision to be made when designing React/Redux programs is where to put business logic (it has to go somewhere!).
It could go in the React components, in the action creators, in the reducers, or a combination of those. Whether the generic action/reducer combination is sensible depends on where the business logic goes.
If the React components do the majority of the business logic, then the action creators and reducers can be very lightweight, and could be put into a single file as you suggest, without any problems, except making the React components more complex.
The reason that most React/Redux projects seem to have a lot of files for action creators and reducers because some of the business logic is put in there, and so would result in a very bloated file, if the generic method was used.
Personally, I prefer to have very simple reducers and simple components, and have a large number of actions to abstract away complexity like requesting data from a web service into the action creators, but the "right" way depends on the project at hand.
A quick note: As mentioned in https://stackoverflow.com/a/50646935, the object should be returned from setState. This is because some asynchronous processing may need to happen before store.dispatch is called.
An example of reducing boilerplate is below. Here, a generic reducer is used, which reduces code needed, but is only possible the logic is handled elsewhere so that actions are made as simple as possible.
import ActionType from "../actionsEnum.jsx";
const reducer = (state = {
// Initial state ...
}, action) => {
var actionsAllowed = Object.keys(ActionType).map(key => {
return ActionType[key];
});
if (actionsAllowed.includes(action.type) && action.type !== ActionType.NOP) {
return makeNewState(state, action.state);
} else {
return state;
}
}
const makeNewState = (oldState, partialState) => {
var newState = Object.assign({}, oldState);
const values = Object.values(partialState);
Object.keys(partialState).forEach((key, ind) => {
newState[key] = values[ind];
});
return newState;
};
export default reducer;
tldr It is a design decision to be made early on in development because it affects how a large portion of the program is structured.
Performance wise not much. But from a design perspective quite a few. By having multiple reducers you can have separation of concerns - each module only concerned with themselves. By having action creators you add a layer of indirection -allowing you to make changes more easily. In the end it still depends, if you don't need these features a generic solution helps reduce code.
First of all, some terminology:
action: a message that we want to dispatch to all reducers. It can be anything. Usually it's a simple Javascript object like const someAction = {type: 'SOME_ACTION', payload: [1, 2, 3]}
action type: a constant used by the action creators to build an action, and by the reducers to understand which action they have just received. You use them to avoid typing 'SOME_ACTION' both in the action creators and in the reducers. You define an action type like const SOME_ACTION = 'SOME_ACTION' so you can import it in the action creators and in the reducers.
action creator: a function that creates an action and dispatches it to the reducers.
reducer: a function that receives all actions dispatched to the store, and it's responsible for updating the state for that redux store (you might have multiple stores if your application is complex).
Now, to the question.
I think that a generic action creator is not a great idea.
Your application might need to use the following action creators:
fetchData()
fetchUser(id)
fetchCity(lat, lon)
Implementing the logic of dealing with a different number of arguments in a single action creator doesn't sound right to me.
I think it's much better to have many small functions because they have different responsibilities. For instance, fetchUser should not have anything to do with fetchCity.
I start out by creating a module for all of my action types and action creators. If my application grows, I might separate the action creators into different modules (e.g. actions/user.js, actions/cities.js), but I think that having separate module/s for action types is a bit overkill.
As for the reducers, I think that a single reducer is a viable option if you don't have to deal with too many actions.
A reducer receives all the actions dispatched by the action creators. Then, by looking at the action.type, it creates a new state of the store. Since you have to deal with all the incoming actions anyway, I find it nice to have all the logic in one place. This of course starts to be difficult if your application grows (e.g. a switch/case to handle 20 different actions is not very maintainable).
You can start with a single reducer, the move to several reducers and combine them in a root reducer with the combineReducer function.
I stumbled upon a requirement on a section of a vanilla JS webapp that requires a single JSON "definitions" object to render. Definitions are loaded via an HTTP request at the very beginning, read, parsed and handed down to another layer of the app. The object itself never changes throughout its life cycle.
I'm now trying to model this scenario in ReactJS, using Redux + redux-thunk. I created a thunk/async action the fetches the JSON object, extracts what it needs and ends up updating the state with that -
but it does not store the object itself in the state. This seems like the right, logical approach since, as mentioned, the definitions are never modified in any way. I'd argue it's simply not state, in a strict sense.
However, by taking that decision I ended up struggling while implementing the actual React.Component. Almost every single example I've seen out there in the wild for async cases like this one:
Defines a thunk action that fires some API call.
Stores whatever they got back (or after some alterations) in a state property.
Maps that property to this.props in the Component with mapStateToProps and connect.
In my case, I don't really have a state property to bind to. So I ended up returning the definitions object in my async action and using the component's local state to get what I needed.
class ContainerComponent extends React.Component {
state = { definitions: {} };
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchDefinitions())
.then((definitions) => this.setState({ definitions }));
}
render() {
return (<PresentationalComponent definitions={this.state.definitions} />);
}
}
export default connect()(ContainerComponent);
Not saying that this.setState should be avoided, but this looks an awful lot like what I had before even introducing Redux: an API call returning a promise - only with a lot more meddling indirections.
componentDidMount() {
const { dispatch } = this.props;
fetch(`${API_URL}/definitions`)
.then((res) => res.json())
.then((definitions) => this.setState({ definitions }));
}
So, how should I go about this? Is there any particular thing I am missing here? Any pattern I should be following? Perhaps, avoiding Redux entirely for this matter?
You are right in that having a component state isn't necessarily a bad thing, but I believe you are confused on where to store that data once the API call is made.
You mention that it is not necessarily state, but I would argue otherwise. Prior to making the API call, your application does not have that data. You may have certain UX/UI indications at the start up of your application that, for example could indicate on if the data is being fetched: definitions.all.isFetching.
In your componentDidMount, dispatching the action to fetch the data is correct. Once the action is fired, and the success response is received, your reducer should save the definitions to your redux store like
import { assign, get } from 'lodash';
const all = (
state = { isFetching: false, data: [] },
action,
) => {
switch (action.type) {
case types.LIST_DEFINITIONS:
return assign({}, state, { isFetching: true });
case types.LIST_DEFINITIONS_SUCCESS:
return assign({}, state, get(action, 'result.data'), { isFetching: false });
default: return state;
}
};
Then in your component, you would connect your redux store
function mapStateToProps(state){
return {
definitions: state.definitions.all.data
};
}
export default connect(mapStateToProps, { listDefinitions })(ContainerComponent);
Also note I moved the action out in my example and am placing it into the connect with mapDispatchToProps shorthand.
I want to create a websocket on demand when certain components want to subscribe to data. How can I share the websocket instance in a redux fashion?
action.js
export function subscribeToWS(url) {
return dispatch => {
let websocket = new WebSocket(url)
websocket.on('connect', () => {
websocket.send("subscribe")
}
websocket.on('message', (message) => {
dispatch(storeNewData(message))
}
}
}
I could do something like this, but this would require a new instance for every new subscription.
The standard place to put things like persistent connection objects is inside middleware. And, in fact, there's literally dozens of existing middlewares that demonstrate that approach, with most of them listed over at https://github.com/markerikson/redux-ecosystem-links/blob/master/middleware.md#sockets-and-adapters . You should be able to use some of those as-is, or at least as examples.
You can look at redux-websocket-bridge. It unfold Web Socket messages into Redux action, and relay Redux action to Web Socket.
Upside of this approach: you can use Redux on your server as API endpoint, replacing standard REST API with less code.
Also, if your server do not send Flux Standard Action, you can still use redux-websocket-bridge for raw messages. It works with string, ArrayBuffer, and Blob. Of course, you can always write a small middleware to translate them into Flux Standard Action, e.g. messages from Slack RTM API.
Although this is quite an old question by now, it popped up several times when looking for an example. As #matthewatabet and #abguy mention, https://github.com/luskhq/redux-ws just mentions it has been deprecated and you can use Redux Thunk, without an example specific for web sockets.
For future reference, I found this article that outlines an example, that is implemented in a Github repo, starting on this file. This is for socket.io, but using web sockets directly should be similar.
Summarizing, in the Component call dispatch with addNewItemSocket:
<RaisedButton
label="Click to add!" primary={true}
onTouchTap={ () => {
const newItem = ReactDOM.findDOMNode(this.refs.newTodo.input).value
newItem === "" ? alert("Item shouldn't be blank")
: dispatch(addNewItemSocket(socket,items.size,newItem))
{/*: dispatch(addNewItem(items.size,newItem))*/}
ReactDOM.findDOMNode(this.refs.newTodo.input).value = ""
}
}
/>
In the actions file, declare addNewItemSocket as:
export const addNewItemSocket = (socket,id,item) => {
return (dispatch) => {
let postData = {
id:id+1,
item:item,
completed:false
}
socket.emit('addItem',postData)
}
}
To handle incoming messages from the socket, in the constructor of the component:
socket.on('itemAdded',(res)=>{
console.dir(res)
dispatch(AddItem(res))
})
And in the actoins file, declare AddItem as:
export const AddItem = (data) => ({
type: "ADD_ITEM",
item: data.item,
itemId:data.id,
completed:data.completed
})
For me this is still new, so any feedback is appreciated. I will also file a PR with https://github.com/luskhq/redux-ws to have an example listed there.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I'm new to React/Redux. I use a fetch api middleware in Redux app to process the APIs. It's (redux-api-middleware). I think it's the good way to process async api actions. But I find some cases which can't be resolve by myself.
As the homepage (Lifecycle) say, a fetch API lifecycle begins with dispatching a CALL_API action ends with dispatching a FSA action.
So my first case is showing/hiding a preloader when fetching APIs. The middleware will dispatch a FSA action at the beginning and dispatch a FSA action at the end. Both the actions are received by reducers which should be only doing some normal data processing. No UI operations, no more operations. Maybe I should save the processing status in state then render them when store updating.
But how to do this? A react component flow over the whole page? what happen with store updating from other actions? I mean they are more like events than state!
Even a worse case, what should I do when I have to use the native confirm dialog or alert dialog in redux/react apps? Where should they be put, actions or reducers?
Best wishes! Wish for replying.
I mean they are more like events than state!
I would not say so. I think loading indicators are a great case of UI that is easily described as a function of state: in this case, of a boolean variable. While this answer is correct, I would like to provide some code to go along with it.
In the async example in Redux repo, reducer updates a field called isFetching:
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
The component uses connect() from React Redux to subscribe to the store’s state and returns isFetching as part of the mapStateToProps() return value so it is available in the connected component’s props:
function mapStateToProps(state) {
const { selectedReddit, postsByReddit } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByReddit[selectedReddit] || {
isFetching: true,
items: []
}
return {
selectedReddit,
posts,
isFetching,
lastUpdated
}
}
Finally, the component uses isFetching prop in the render() function to render a “Loading...” label (which could conceivably be a spinner instead):
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
: <div style={{ opacity: isFetching ? 0.5 : 1 }}>
<Posts posts={posts} />
</div>
}
Even a worse case, what should I do when I have to use the native confirm dialog or alert dialog in redux/react apps? Where should they be put, actions or reducers?
Any side effects (and showing a dialog is most certainly a side effect) do not belong in reducers. Think of reducers as passive “builders of state”. They don’t really “do” things.
If you wish to show an alert, either do this from a component before dispatching an action, or do this from an action creator. By the time an action is dispatched, it is too late to perform side effects in response to it.
For every rule, there is an exception. Sometimes your side effect logic is so complicated you actually want to couple them either to specific action types or to specific reducers. In this case check out advanced projects like Redux Saga and Redux Loop. Only do this when you are comfortable with vanilla Redux and have a real problem of scattered side effects you’d like to make more manageable.
Great answer Dan Abramov!
Just want to add that I was doing more or less exactly that in one of my apps (keeping isFetching as a boolean) and ended up having to make it an integer (which ends up reading as the number of outstanding requests) to support multiple simultaneous requests.
with boolean:
request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> spinner off -> request 2 ends
with integer:
request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> request 2 ends -> spinner off
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: state.isFetching + 1,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: state.isFetching - 1,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
I'd like to add something. The real world example uses a field isFetching in the store to represent when a collection of items is being fetched. Any collection is generalized to a pagination reducer that can be connected to your components to track the state and show if a collection is loading.
It happened to me that I wanted to fetch details for an specific entity that doesn't fit in the pagination pattern. I wanted to have a state representing if the details are being fetched from the server but also I didn't want to have a reducer just for that.
To solve this I added another generic reducer called fetching. It works in a similar fashion to the pagination reducer and it's responsibility is just to watch a set of actions and generate new state with pairs [entity, isFetching]. That allows to connect the reducer to any component and to know if the app is currently fetching data not just for a collection but for an specific entity.
I didn't happen upon this question until now, but since no answer is accepted I'll throw in my hat. I wrote a tool for this very job: react-loader-factory. It's got slightly more going on than Abramov's solution, but is more modular and convenient, since I didn't want to have to think after I wrote it.
There are four big pieces:
Factory pattern: This allows you to quickly call the same function to set up which states mean "Loading" for your component, and which actions to dispatch. (This assumes that the component is responsible for starting the actions it waits on.) const loaderWrapper = loaderFactory(actionsList, monitoredStates);
Wrapper: The component the Factory produces is a "higher order component" (like what connect() returns in Redux), so that you can just bolt it onto your existing stuff. const LoadingChild = loaderWrapper(ChildComponent);
Action/Reducer interaction: The wrapper checks to see if a reducer it's plugged into contains keywords that tell it not to pass through to the component that needs data. The actions dispatched by the wrapper are expected to produce the associated keywords (the way redux-api-middleware dispatches ACTION_SUCCESS and ACTION_REQUEST, for example). (You could dispatch actions elsewhere and just monitor from the wrapper if you wanted, of course.)
Throbber: The component you want to appear while the data your component depends on isn't ready. I added a little div in there so you can test it out without having to rig it up.
The module itself is independent of redux-api-middleware, but that's what I use it with, so here's some sample code from the README:
A component with a Loader wrapping it:
import React from 'react';
import { myAsyncAction } from '../actions';
import loaderFactory from 'react-loader-factory';
import ChildComponent from './ChildComponent';
const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates);
const LoadingChild = loaderWrapper(ChildComponent);
const containingComponent = props => {
// Do whatever you need to do with your usual containing component
const childProps = { someProps: 'props' };
return <LoadingChild { ...childProps } />;
}
A reducer for the Loader to monitor (although you can wire it differently if you want):
export function activeRequests(state = [], action) {
const newState = state.slice();
// regex that tests for an API action string ending with _REQUEST
const reqReg = new RegExp(/^[A-Z]+\_REQUEST$/g);
// regex that tests for a API action string ending with _SUCCESS
const sucReg = new RegExp(/^[A-Z]+\_SUCCESS$/g);
// if a _REQUEST comes in, add it to the activeRequests list
if (reqReg.test(action.type)) {
newState.push(action.type);
}
// if a _SUCCESS comes in, delete its corresponding _REQUEST
if (sucReg.test(action.type)) {
const reqType = action.type.split('_')[0].concat('_REQUEST');
const deleteInd = state.indexOf(reqType);
if (deleteInd !== -1) {
newState.splice(deleteInd, 1);
}
}
return newState;
}
I expect in the near future I'll add things like timeout and error to the module, but the pattern's not going to be very different.
The short answer to your question is:
Tie rendering to rendering code--use a wrapper around the component you need to render with the data like the one I showed above.
Add a reducer that makes the status of requests around the app you might care about easily digestible, so you don't have to think too hard about what is happening.
Events and state aren't really different.
The rest of your intuitions seem correct to me.
Am I the only one thinking that loading indicators don't belong in a Redux store? I mean, I don't think it's part of an application's state per se..
Now, I work with Angular2, and what I do is that I have a "Loading" service which exposes different loading indicators via RxJS BehaviourSubjects.. I guess the mechanism is the same, I just don't store the information in Redux.
Users of the LoadingService just subscribe to those events they want to listen to..
My Redux action creators call the LoadingService whenever things need to change. UX components subscribe to the exposed observables...
You can add change listeners to your stores, using either connect() from React Redux or the low-level store.subscribe() method. You should have the loading indicator in your store, which the store change handler can then check and update the component state. The component then renders the preloader if needed, based on the state.
alert and confirm shouldn't be a problem. They are blocking and alert doesn't even take any input from the user. With confirm, you can set state based on what the user has clicked if the user choice should affect component rendering. If not, you can store the choice as component member variable for later use.
We have three types of notifications in our app, all of which are designed as aspects:
Loading indicator (modal or non-modal based on prop)
Error Popup (modal)
Notification snackbar (non-modal, self closing)
All three of these are at the top level of our app (Main), and wired through Redux as shown in the below code snippet. These props control display of their corresponding aspects.
I designed a proxy that handles all our API calls, thus all isFetching and (api) errors are mediated with actionCreators I import in the proxy. (As an aside, I also use webpack to inject a mock of the backing service for dev so we can work without server dependencies.)
Any other place in the app that needs to provide any type of notification simply imports the appropriate action. Snackbar & Error have params for messages to be displayed.
#connect(
// map state to props
state => ({
isFetching :state.main.get('isFetching'), // ProgressIndicator
notification :state.main.get('notification'), // Snackbar
error :state.main.get('error') // ErrorPopup
}),
// mapDispatchToProps
(dispatch) => { return {
actions: bindActionCreators(actionCreators, dispatch)
}}
)
export default class Main extends React.Component{
I'm saving the urls such as::
isFetching: {
/api/posts/1: true,
api/posts/3: false,
api/search?q=322: true,
}
And then I have a memorised selector (via reselect).
const getIsFetching = createSelector(
state => state.isFetching,
items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false
);
To make the url unique in case of POST, I pass some variable as query.
And where I want to show an indicator, I simply use the getFetchCount variable
(Note: My question was not clearly written, and I was thinking about some things wrong. The current version of the question is just an attempt to write something that could make the accepted answer useful to as many people as possible.)
I want to have an action that adds an item to a store and registers it with an external dependency.
I could use the thunk middleware and write
export function addItem(item) {
return dispatch => {
dispatch(_addItemWithoutRegisteringIt(item));
externalDependency.register(item);
};
}
But the subscribers would be notified before the item was registered, and they might depend on it being registered.
I could reverse the order and write
export function addItem(item) {
return dispatch => {
externalDependency.register(item);
dispatch(_addItemWithoutRegisteringIt(item));
};
}
But I track the item in the external dependency by a unique id that it is natural to only assign in the reducer.
I could register the item in the reducer, but I am given to understand that it is very bad form to do side effects in a reducer and might lead to problems down the line.
So what is the best approach?
(My conclusion is: there are a number of approaches that would work, but probably the best one for my use case is to store a handle into the external dependency in Redux rather than a handle into Redux in the external dependency.)
If you use Redux Thunk middleware, you can encapsulate it in an action creator:
function addItem(id) {
return { type: 'ADD_ITEM', id };
}
function showNotification(text) {
return { type: 'SHOW_NOTIFICATION', text };
}
export function addItemWithNotification(id) {
return dispatch => {
dispatch(addItem(id));
doSomeSideEffect();
dispatch(showNotification('Item was added.');
};
}
Elaborating, based on the comments to this answer:
Then maybe this is the wrong pattern for my case. I don't want subscribers invoked between dispatch(addItem(id)) and doSomeSideEffect().
In 95% cases you shouldn't worry about whether the subscribers were invoked. Bindings like React Redux won't re-render if the data hasn't changed.
Would putting doSomeSideEffect() in the reducer be an acceptable approach or does it have hidden pitfalls?
No, putting side effects into the reducer is never acceptable. This goes against the central premise of Redux and breaks pretty much any tool in its ecosystem: Redux DevTools, Redux Undo, any record/replay solution, tests, etc. Never do this.
If you really need to perform a side effect together with an action, and you also really care about subscribers only being notified once, just dispatch one action and use [Redux Thunk] to “attach” a side effect to it:
function addItem(id, item) {
return { type: 'ADD_ITEM', id, item };
}
export function addItemWithSomeSideEffect(id) {
return dispatch => {
let item = doSomeSideEffect(); // note: you can use return value
dispatch(addItem(id, item));
};
}
In this case you'd need to handle ADD_ITEM from different reducers. There is no need to dispatch two actions without notifying the subscribers twice.
Here is the one point I still definitely don't understand. Dan suggested that the thunk middleware couldn't defer subscriber notification because that would break a common use case with async requests. I still don't understand this this.
Consider this:
export function doSomethinAsync() {
return dispatch => {
dispatch({ type: 'A' });
dispatch({ type: 'B' });
setTimeout(() => {
dispatch({ type: 'C' });
dispatch({ type: 'D' });
}, 1000);
};
}
When would you want the subscriptions to be notified? Definitely, if we notify the subscribers only when the thunk exits, we won't notify them at all for C and D.
Either way, this is impossible with the current middleware architecture. Middleware isn't meant to prevent subscribers from firing.
However what you described can be accomplished with a store enhancer like redux-batched-subscribe. It is unrelated to Redux Thunk, but it causes any group of actions dispatched synchronously to be debounced. This way you'd get one notification for A and B, and another one notification for C and D. That said writing code relying on this behavior would be fragile in my opinion.
I'm still in the process of learning Redux; however my gut instinct says that this is could be a potential candiate for some custom middleware?