I'd like to make a very simple REST controller in my react.js application that sends back a hard-coded array of strings. I know there's tutorials out there about react and spring, but I'm not ready to get into all that yet. Is there a way I can do this just with react?
Here's what I've written so far (in src/controllers/FetchUsers.js)
import { connect } from 'react-redux';
export const FetchUsers = async () => {
console.log('fetching user names');
let results = [];
results = ["Alice", "Bob", "Charlie"];
return { results };
}
// ========================================
export default FetchUsers;
Currently, when socket.io emits a 'gmessage' from my server, and the socket in my component catches it, my entire state is replaced.
So the current flow is like this:
I emit a message from the component, it goes to my server, then to the watson API. The API sends a response to my server, and the server sends that to the component.
So that first message creates the connection to socket.io, then the second socket.io event is caught on the same connection.
Is there a better way to set up the connection to socket.io and handle both the emit and the "on gmessage" parts of this? Thank you for any tips in advance. I'm still new to react, so anything you see that I should do differently is helpful!!
...
import {Launcher} from 'react-chat-window'
import io from 'socket.io-client';
import { getUser } from '../actions/userActions';
class Workshop extends Component {
constructor() {
super();
this.state = {
messageList: []
};
}
_onMessageWasSent(message) {
this.setState({
messageList: [message, ...this.state.messageList]
})
var socket = io.connect('http://localhost:5000');
socket.emit('message', { message });
var myThis = this
var myState = this.state
socket.on('gmesssage', function (data) {
console.log(data);
myThis.setState({
messageList: [{
author: 'them',
type: 'text',
data: data
}, ...myState.messageList]
})
})
}
render() {
return (
<Grid container className="DashboardPage" justify="center">
<Grid item xs={12}>
<div>Welcome to your Workshop</div>
<TeamsTaskLists />
</Grid>
<Launcher
agentProfile={{
teamName: 'Geppetto',
imageUrl: 'https://geppetto.ai/wp-content/uploads/2019/03/geppetto-chat-avi.png'
}}
onMessageWasSent={this._onMessageWasSent.bind(this)}
messageList={this.state.messageList}
showEmoji
/>
</Grid>
);
}
}
const mapStatetoProps = state => ({
user: state.user
});
export default connect(
mapStatetoProps,
{ getUser }
)(Workshop);
Fareed has provided a working Redux solution and suggested improvements for a non-Redux approach, so I'd like to address the issues in your current code:
You're reinitializing the socket variable and creating its listener every time a new message is received instead of configuring and initializing the socket object just once inside a separate file and then consuming it by your Workshop component and possibly other components across your project.
You're appending the newly received message by calling setState({ messages: [...] }) twice in _onMessageWasSent.
To solve the first issue, you can create a separate file and move your socket related imports and initialization to it like:
// socket-helper.js
import io from 'socket.io-client';
export const socket = io.connect('http://localhost:5000');
Bear in mind, this is a minimal configuration, you can tweak your socket object before exporting it as much as the socket.io API allows you to.
Then, inside the Workshop component's componentDidMount subscribe to this socket object like:
import { socket } from 'path/to/socket-helper.js';
class Workshop extends Component {
constructor(props) {
super(props);
...
}
componentDidMount() {
// we subscribe to the imported socket object just once
// by using arrow functions, no need to assign this to myThis
socket.on('gmesssage', (data) => {
this._onMessageWasSent(data);
})
}
...
}
React docs say
If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
componentDidMount will run only once, so your listener will not get redefined multiple times.
To solve the second issue, _onMessageWasSent handler function should only receive the new message and append it to previous messages using setState, like this:
_onMessageWasSent(data) {
this.setState(previousState => ({
messageList: [
{
author: 'them',
type: 'text',
data: data,
},
...previousState.messageList,
]
}))
}
Since you are already using redux it's better to make socket.io dispatch redux actions for you, you can use redux saga same as this article describes or you can use this implementation without saga but you need to have redux-thunk middleware :
1- Create a file lets say lib/socket.js
then inside this file create socket.io client lets call it socket and create initSocketIO function where you can fire any redux actions in response to socket.io:
import socket_io from "socket.io-client";
// our socket.io client
export const socket = socket_io("http://localhost:5000");
/**
* init socket.io redux action
* #return {Function}
*/
export const initSocketIO = () => (dispatch, getState) => {
// connect
socket.on('connect', () => dispatch(appActions.socketConnected()));
// disconnect
socket.on('disconnect', () => dispatch(appActions.socketDisconnected()));
// message
socket.on('message', message => {
dispatch(conversationActions.addOrUpdateMessage(message.conversationId, message.id, message));
socket.emit("message-read", {conversationId: message.conversationId});
});
// message
socket.on('notifications', ({totalUnread, notification}) => {
dispatch(recentActions.addOrUpdateNotification(notification));
dispatch(recentActions.setTotalUnreadNotifications(totalUnread));
});
};
2- then inside your App component call this action once your App is mounted:
import React, {Component} from "react";
import {initSocketIO} from "./lib/socket.js";
export class App extends Component {
constructor(props) {
super(props);
this.store = configureStore();
}
componentDidMount() {
// init socket io
this.store.dispatch(initSocketIO());
}
// ... whatever
}
3- now you can also fire any socket.io action from any component using this:
import React, {Component} from "react";
import {socket} from "./lib/socket.js"
MyComponent extends Components {
myMethod = () => socket.emit("message",{text: "message"});
}
or from any redux action using thunk for example:
export const sendMessageAction = (text) => dispatch => {
socket.emit("my message",{text});
dispatch({type: "NEW_MESSAGE_SENT",payload:{text}});
}
Notes:
1-This will perfectly work, but still, I prefer the redux-saga method for managing any side effects like API and socket.io.
2-In your components, you don't need to bind your component methods to this just use arrow functions for example:
myMethod = () => {
// you can use "this" here
}
render = (){
return <Launcher onMessageWasSent={this.myMethod} />
}
Depending on your application, I think Redux would be overkill for this.
I would change to a lambda expression here though to avoid having to save this references:
socket.on('gmesssage', data => {
console.log(data);
this.setState({
messageList: [{
author: 'them',
type: 'text',
data: data
}, ...this.messageList]
})
})
You might consider using the new React Hooks also e.g. turn it into a stateless component and use useState:
const Workshop = () => {
const [messageList, setMessageList] = useState([]);
...
I have some problems with the auth process on Nextjs project. From the example, it stores the token in cookies, but in checkLoggedIn.js it queries DB instead of get the token from cookie.
I would like to get the token from cookie or localstorage in getInitialProps, but in getInitialProps, it can NOT see localstorage because it's still in server side. Is there any better way that I could auth user before component render?
Not sure is it possible to get the token from getToken in apollo client.
My current code is
class DashBoard extends React.Component {
constructor(props) {
super(props)
}
componentDidMount () {
const decodeToken = verifyToken(localStorage.getItem('KEY'));
if (!decodeToken.mail) {
Router.push('/login');
} else {
this.props.loginSuccess(decodeToken.name, decodeToken.mail);
}
}
render () {
return (<div></div>)
}
}
Thank you
I solved this by using nookies. It works both server side (for getInitialProps) and client side in Next.js
Here is what I did to solve it after few days of research (honestly, not the best documentation around)
In the getInitialProps, I took the headers from the requests and copied them over in my fetch request.
Profile.getInitialProps = async ({ req }) => {
const headers = { ...req.headers }
const url = `http://localhost:3000/api/profile`
const data = await fetch(url, { headers }).then(res => res.json())
return { data }
}
Keep in mind that this only works on the server. For the client, you can just pass {credentials: 'include'} to fetch
I am working with the CryptoCompare API to get data about cryptocurrencies for my project. I've made a few requests to the API and have had no issue getting a response.
Some of the queries for the API look like this:
https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=BTC,USD,EUR
And others look like this:
https://www.cryptocompare.com/api/data/coinsnapshot/?fsym=BTC&tsym=USD
When I make requests to URLs that look like the first I am able to get a response from the API and retrieve the data. When I make the same request but for one of the URLs that look like the second I get an error. Error: Network error is all it says.
Here's what my code looks like:
import React, { Component } from 'react';
import axios from "axios";
class CoinInfo extends React.Component {
constructor(props) {
super(props);
this.state = {
coinInfo: []
}
}
componentDidMount() {
axios.get(`https://www.cryptocompare.com/api/data/coinsnapshot/?fsym=BTC&tsym=USD`)
.then(res => {
const info = res.data;
this.setState({ coinInfo: info});
console.log(info);
});
}
render() {
return (
<div className="container">
</div>
)
}
}
export default CoinInfo;
If I swap out the URL in the Axios request and replace it with the other API endpoint/URL it works perfectly fine. It also works fine with any of the other CryptoCompare endpoints that have the root "min-api.cryptocompare.com".
However all the endpoints that follow the "www.cryptocompare.com/" pattern don't work.
I am not getting a CORS error. Just an error that says "Error: Network error" in Firefox.
Is this a problem with the API itself? Or is there something on my end I am overlooking?
axios.get(`https://www.cryptocompare.com/api/data/coinsnapshot/?fsym=BTC&tsym=USD`)
its incorrect format I think so please retype it as
axios.get("https://www.cryptocompare.com/api/data/coinsnapshot/?fsym=BTC&tsym=USD")
I try to migrate my project to Next.js framework for having SSR (Server side rendering) feature. Here is my simple page:
class Example extends React.Component {
static getInitialProps() {
// api getting data. Need authentication data under local storage
}
render() {
return <div>{this.props.data}</div>;
}
}
The problem I met is: I want my data is from getInitialProps first (that is the purpose of SSR). But when sending, I need a information about user for backend API. But at this function, rendering is happened on server so I cannot get data from local storage. How can you solve this problem.
Thanks
You can put the call to localStorage into a react component lifecycle method such as componentDidMount and then setState() with the result.
It's not great, but will work.
Note that this would obviously not use SSR.
This works for me:
const USER = 'user'
const URL = 'http://www.example.com/user'
// here you can also use isServer
if (req) {
const {data: user} = await Axios.get(URL)
localStorage.setItem(USER, JSON.stringify(user))
} else {
const user = await JSON.parse(localStorage.getItem(USER))
}