Using socketio in redux actions - javascript

I'm trying to build an application using React, Redux and socket.io !
My question is : how do you initialize event listeners (which are actions in Redux) like this one :
export const addNotif = () => (dispatch) => {
socket.on('notification', (message) => {
dispatch(notification(message))
})
}
in React without having access to the componentWillMount since I'm using functional programing on components.
My current problem is that if I pass the method
addNotif
to my component, every time a new notification comes in, the store is updated, and so the component is re-rendered and therefore it adds another socket.on event and this keeps on going.
Any ideas on how to fix that in a clean way ?
Thanks !

Try to define and export the function like this:
export const addNotif = (dispatch) => {
socket.on('notification', (message) => {
dispatch(notification(message))
})
}

As you mention, you don't want to attach an event listener inside a stateless function.
You wanna invert this: notification comes in > dispatch event. This can live outside the lifecycle of react since you can dispatch arbitrary redux actions from anywhere.
If the action is not consumed because there are no clients that's fine. You can fine tune this by absorbing/demuxing events in a redux middleware and/or have components dispatch subscriptions.
index.js
// start() should be called ONCE wherever you have access to the store
const notifications = require('./notifications');
notifications.start(store.dispatch);
notifications.js
export default {
start(dispatch) {
socker.on('connect', () => {
console.log('Subscribed to notifications');
});
socket.on('notification', (payload) => {
dispatch(({ type: "NOTIF", payload }));
});
socket.on('error', (message) => {
console.log('TODO: handle error');
})
},
};
It might be a good idea to abstract away the socketio interface, hence the two files although it's not necessary.

Related

How to listen to socket.io events across all components/screens or globally in react-native?

I am using socket.io(server) and socket.io-client(client) in react-native app and I am listening to its events in useEffect hook,
all events are receiving correctly when I am on that specific component.
So how can can I receive these events on the other screens too??
Single instance for all events which will be used globally
e.g:
const app = () => {
useEffect(() => {
socket.on('event', (data) => {
console.log(data)
}
},[])
...................
rest of the code
...................
}
Now I want to listen to this event on all the screens (like DeviceEventEmitter addListner works)?
Thanks.

Where to put your websocket in React

I have been trying to use socket.io with react for some time and I still don't know where to initiate socket = io(endpoint). In most tutorials I have seen, they seem to put it at the component level (below the app level. I assume it disconnects whenever you switch routes and the component unmounts). But what if you want to listen to some events consistently regardless of which route you navigate to in your app? Here is what I mean:
let socket
const App = () => {
useEffect(() => {
socket = io(endpoint)
socket.on('Application wide event', callback)
}, [])
}
Do you then pass the socket via props? I have tried this approach and sometimes the child componenets wants to emit an event immediately on mount even before the App socket can be successfully created and thus throws an error. Perhaps something like this? :
/* socket initialization here */
const App = () => {}
What is the conventional practice?
I encountered a problem like yours that I need to listen to some events consistently regardless of which route you navigate to in my app. I manage to do it by initialized the socket on the top level component outside rendering function and exported it, so when I need to emit or listen to a socket I will just import it.
/* socket initialization here */
socket = io(endpoint);
const App = () => {
//return <Routes>...</Routes>
}
export default App;
export {socket};
This will lead to socket initialized once so the component that needs it can fire an emit or listen to it immediately.
and to use it:
import {socket} from "/App";
const OtherComponent = () => {
useEffect(() => {
socket.on("Application wide event", callback)
return () => {
socket.off("Application wide event");
}
}, [])
}
I still wondering if this is a conventional practice but it works on me, I will follow this question, maybe someone knows a better conventional practice.

Best practise to handle with responses and incoming props

with redux, we uses actions to handle with crud operations. But I stuck at some points. If we send async requests inside of component. We can easly handle with response. But when we send request through actions, we dont know what happened. Is request send successfully ? it took how much amount of time ? What kind of response is returned ? we don't know that
I will clarify question with samples..
lets update a post.
onClick () {
postsApi.post(this.state.post) | we know how much time
.then(res => res.data) | has took to execute
.then(res => { | request
console.log(res) // we have the response
})
.catch(err => console.log(error))
}
But if we use actions
onClick () {
this.props.updatePost(this.state.post) // we know nothing what will happen
}
or handling with incoming props. lets say I have fetchPost() action to retrieve post
componentDidMount(){
this.props.fetchPost()
}
render method and componentDidUpdate will run as well. It's cool. But what if I want to update my state by incoming props ? I can't do this operation inside of componentDidUpdate method. it causes infinity loop.
If I use componentWillUpdate method, well, things works fine but I'm getting this warning.
Warning: componentWillReceiveProps has been renamed, and is not
recommended for use. Move data fetching code or side effects to
componentDidUpdate. If you're updating state whenever props change,
refactor your code to use memoization techniques or move it to static
getDerivedStateFromProps
I can't use componentDidUpdate method for infinty loop. Neither getDerivedStateFromProps method because it's run everytime when state change.
Should I continue to use componentWillMethod ? Otherwise what should I use and why (why componentWillMethod is unsafe ?)
If I understand correcty, what you would like to do is to safely change your local state only when your e.g. updatePost was successful.
If indeed that is your case, you can pass a callback function on your updatePost and call this as long as your update was succefull.
successfulUpdate() {
// do your thing
this.setState( ... );
}
onClick () {
this.props.updatePost(this.state.post, this.successfulUpdate) // we know nothing what will happen
}
UPDATE:
You can also keep in mind that if your action returns a promise, then you can just use the then method:
onClick () {
this.props.updatePost(this.state.post).then(this.onFulfilled, this.onRejected)
}
I think we can use redux-thunk in this cases. What if we dispatch an async function instead of dispatch an action object?
"Neither getDerivedStateFromProps method because it's run everytime when state change." - does it matter? You can avoid setting state with every getDerivedStateFromProps call by using a simple condition inside.
Example:
static getDerivedStateFromProps(props, state) {
if (props.post !== state.post) { // or anything else
return {
post: props.post,
};
}
return null;
};
An infinite loop will not occur.
Here is my way for such cases. We can redux-thunk for asynchronous calls such as api call. What if we define the action that returns promise? Please check the code below.
actions.js
export const GetTodoList = dispatch => {
return Axios.get('SOME_URL').then(res => {
// dispatch some actions
// return result
return res.data;
});
}
TodoList.js
onClick = async () => {
const { GetTodoList } = this.props;
try {
const data = await GetTodoList();
// handler for success
this.setState({
success: true,
data
});
} catch {
// handler for failure
this.setState({
success: fail,
data: null
});
}
}
const mapStateToProps = state => ({
GetTodoList
});
So we can use actions like API(which returns promise) thanks to redux-thunk.
Let me know your opinion.

ReactJS warning "Can't perform a react state update on an unmounted component" on return of Firebase promise in .then handler

I want to save a user to Firebase's Realtime Database upon user creation in a sign-up form. If I return the Firebase function (value), for saving users, in a .then handler instead of just calling it (without return statement), I get an error message from React, saying "Can't perform a react state update on an unmounted component".
My code for the submit handler of the sign-up form looks something like the following:
const SignUpFormBase = (props) => {
const [credentials, setCredentials] = useState(INITIAL_STATE);
const [error, setError] = useState(null);
[some other code]
const handleSubmit = (e) => {
firebase
.doCreateUserWithEmailAndPassword(email, passwordOne)
.then(authUser => {
// create a user in the Firebase realtime database
return firebase.database().ref(`users/${authUser.user.uid}`)
.set({ username, email });
})
.then(() => {
setCredentials(INITIAL_STATE);
props.history.push(ROUTES.DASHBOARD);
})
.catch(error => {
setError(error);
});
e.preventDefault();
};
return (
[some jsx]
);
};
const SignUpForm = withRouter(SignUpFormBase);
export default SignUpForm;
The code actually works, whether you include or leave off the return statement. However, if you don't use a return statement, the warning won't show.
I just don't understand why I get the above-mentioned warning from firebase since I (seemingly) don't perform any state updates on the component after it has been unmounted.
Update:
The component actually unmounts before the setCredentials hook has the chance to update the state. This is not because of the push to history in the code above, but a Public Route I've implemented to show users only pages they are allowed to see. It uses the new useContext hook which triggers a re-render when the context value changes (the value is derived from subscribing to the firebase onAuthStateChanged method). I solved the issue by putting the setCredentials call into a useEffect hook:
useEffect(() => {
// set credentials to INITIAL_STATE before ComponentDiDUnmount Lifecycle Event
return () => {
setCredentials(INITIAL_STATE);
};
}, []);
However, I still don't get why a missing return statement (in the previous setup) lead to a vanishing react warning.
Telling from given code, I'm not sure what part lead to the warning. However, I'd like to provide some advices to help pin point it (Hard to write it clear in comment, so I just post as an answer).
firebasePromise
.then(() => {
// [1] async code
setCredentials(INITIAL_STATE);
// [2] sync code
// UNMOUNT happens after route change
props.history.push(ROUTES.DASHBOARD);
})
.catch(error => {
// [3] async code
setError(error);
});
I suspect the problem comes from the execution order of sync/async code. Two things you can try out to gain more info.
Check if [3] is invoked, use console.log maybe?
Wrap [2] in a setTimeout(() => {}) to make it async too.
My bet is on [3]. somehow some error is thrown after [2] is invoked.
Your problem is here:
setCredentials(INITIAL_STATE);
props.history.push(ROUTES.DASHBOARD);
most likely setCredentials is async function
and when it is trying to change state you already have different route because of props.history.push(ROUTES.DASHBOARD)
as the result you don't have component and you can't update the state
try:
setCredentials(INITIAL_STATE).then(
props.history.push(ROUTES.DASHBOARD);
)

Redux-thunk with Websockets

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.

Categories

Resources