I have a rails + react app (separate projects) and I've made an ActionCable websocket for sending simple messages from backend to the frontend. The websocket works, i can see everything on the frontend but I can't see the updates in real-time, only after refresh. I don't know how to implement the real time update.
Here is my code:
import PropTypes from 'prop-types'
import { Switch, Route } from 'react-router-dom'
import actionCable from 'actioncable'
import { DUMMY_QUERY } from 'Queries'
// app/javascript/packs/messages.js
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
import MessagesChannel from './channels/messages_channel'
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => value + 1); // update the state to force render
}
function Dummy() {
const [messages, setMessages] = useState([])
const [message, setMessage] = useState('')
const [rerender, setRerender] = useState(false);
useEffect(() => {
MessagesChannel.received = (data) => {
setMessages(data.messages)
console.log(data)
}
}, [])
/*const handleSubmit = async (e) => {
e.preventDefault()
// Add the X-CSRF-TOKEN token so rails accepts the request
await fetch('http://localhost:3000/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message }),
})
setMessage('')
}*/
return (
<div>
<ul>
{messages.map((message) => (
<li key={message.id}>{message.content}</li>
))}
</ul>
</div>
)
}
export default Dummy
Active cable also hat a frontend component. I have never used it together with react, but with pure js it looks something like that:
Import the lib (consumer.js):
import { createConsumer } from "#rails/actioncable"
export default createConsumer()
Load all channels defined in your repo (index.js):
const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)
Define a js file for each channel (message_channel.js):
import consumer from "./consumer"
consumer.subscriptions.create("MessagesChannel", {
connected() {
// Called when the subscription is ready for use on the server
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
// Called when there's incoming data on the websocket for this channel
console.log(data)
}
});
Edit: Just found out there is also a npm package for the frontend components.
Related
I am trying to learn React by making a motor cycle spec search web application.
I am making two axios requests in /api/index.js, and I am getting an error saying
'429 (Too Many Requests)'.
What am I doing wrong here?
/api/index.js
import axios from "axios";
const options1 = {
method: 'GET',
url: 'https://motorcycle-specs-database.p.rapidapi.com/model/make-name/Yamaha',
headers: {
'X-RapidAPI-Host': 'motorcycle-specs-database.p.rapidapi.com',
'X-RapidAPI-Key': 'MyAPIKey'
}
};
const options2 = {
method: 'GET',
url: 'https://motorcycle-specs-database.p.rapidapi.com/make',
headers: {
'X-RapidAPI-Host': 'motorcycle-specs-database.p.rapidapi.com',
'X-RapidAPI-Key': 'MyAPIKey'
}
};
export const makeList = async()=>{
try{
const {data} = await axios.request(options2);
console.log('list of all makes is like this now', data);
return data;
}
catch(error){
}
}
export const fetchData = async ()=>{
try{
const {data} = await axios.request(options1);
return data;
}
catch(error){
}
}
and this is where I'm trying to use the data.
App.js
import logo from './logo.svg';
import './App.css';
import {fetchData, makeList} from './api/index';
import React, {Component} from 'react';
class App extends React.Component{
state = {
data:[],
makes:[],
}
async componentDidMount(){
const fetchedData = await fetchData();
const fetchedMakeList = await makeList();
this.setState({data:fetchedData, makes:fetchedMakeList});
//this.setState({makes:fetchedMakeList});
console.log('list of all makes in componentDIDMOUNT is like ', fetchedMakeList);
//why is this undefined??
}
render(){
return (
<div className="App">
<header className="App-header">
<h1>Some line-ups from YAMAHA</h1>
{partOfTheArray.map(data=>{
return <p>{data.name}</p>
})}
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Open React
</a>
</header>
</div>
);
}
}
export default App;
I am only requesting 2 requests, but I am getting this error message.
Assuming that you're trying to fetch data when component mounts, here is a better approach to do so:
// Import useState and useEffect
import React, {useState, useEffect} from 'react';
export default function SomeComponent() {
let [data, setData] = useState(null)
// use an useEffect with empty dependecy(empty [] as a dependecy) to fetch the data
// empty [] makes sure that you're fetching data only once when the component mounts
useEffect(() => {
fetchData().then(res => {
// check status for response and set data accordingly
setData(res.data)
// log the data
console.log(res.data)
})
},[])
return (
<div className="App">
</div>
);
}
You need to update your fetchData() function as well.
export const fetchData = async ()=>{
try{
const response = await axios.request(options1);
// return the whole response object instead of only the data.
// this helps in error handling in the component
return response;
}
catch(error){}
}
I hope it helps!
I am implementing Server side rendering to a react and redux SPA application . So for SSR I fetch data on server and make page by using renderToString method and then on client side I fetch data in useEffect as shown in below example.
Now issue is that after fetching data on server, client is again calling fetchRequestQuery(dispatch) in useEffect. so it is calling same API 2 times which is wrong. I can't remove useEffect as when I navigate page(react router Link) then data should be fetch on client side only for SPA.
So how could I disable api call in useEffect on client side when open page first time but when we navigate page then it should be working normal and make API call in useEffect?
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchRequestQuery } from '../actions';
const loadData = dispatch => (
fetchRequestQuery(dispatch)
);
const Content = () => {
const dispatch = useDispatch();
useEffect(() => {
loadData(dispatch);
}, []);
const { request } = useSelector(state => state);
return (
<span>{JSON.stringify(request)}</span>
);
};
export default {
component: Content,
loadData,
};
import { matchRoutes } from 'react-router-config';
import Routes from './Routes';
import renderer from './helpers/renderer';
import createStore from './store';
const app = express();
app.use(express.static('dist'));
app.get('*', (req, res) => {
const store = createStore();
const { dispatch } = store;
const routes = matchRoutes(Routes, req.path);
const promises = routes.map(
({ route }) => (route.loadData ? route.loadData(dispatch) : null),
);
Promise.all(promises).then(() => {
const content = renderer(req, store);
res.send(content);
});
});
const port = process.env.PORT || 3001;
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
I think you can define a variable and set it to true when your component render on client only and , if this variable is true your data while be fetched something like this:
import { useSelector, useDispatch } from 'react-redux';
import { fetchRequestQuery } from '../actions';
const loadData = dispatch => (
fetchRequestQuery(dispatch)
);
const Content = () => {
const initial = useRef(false);
const dispatch = useDispatch();
useEffect(() => {
if(window && initial.current){
// this should run in second render on client
loadData(dispatch);
} else if(window) {
// this should run in first render on client
initial.current = true;
} else{
// this should run on server side
loadData(dispatch);
}
}, []);
const { request } = useSelector(state => state);
return (
<span>{JSON.stringify(request)}</span>
);
};
export default {
component: Content,
loadData,
};
I'm new to React and I have this function.
import Axios from "axios";
const UserService = {
getUserRole: (access_token: string = "") => {
return Axios({
method: "get",
url: "https://<url>/user/role",
headers: {
"Authorization": `Bearer ${access_token}`
}
}).then((response) => {
return response.data;
}).catch((error) => {
console.log(error);
});
}
}
export default UserService
The getUserRole is used constantly by another component, for example
import UserService from "../../../services/authentication/userService";
import { useAuth } from "react-oidc-context";
...
const auth = useAuth();
UserService.getUserRole(auth.user?.access_token);
As you can see, I have to constantly pass the access_token from useAuth. Is there any way I can call useAuth inside my UserService so I don't have to constantly pass the access_token from my component?
The premise of the question is backward, as we shouldn't try to use hooks outside of React, but instead use outside code inside of React.
Quick solution: Custom hook
If the roles are used all over the place, a quick custom hook will get you started. This is the easiest way to wrap custom logic as hooks are meant to wrap stateful logic for reuse in components.
import { useState, useEffect } from "react";
import { useAuth } from "react-oidc-context";
import UserService from "../../../services/authentication/userService";
/**
* Custom hooks that fetches the roles for the logged in user.
*/
const useRoles = () => {
const auth = useAuth();
const [roles, setRoles] = useState();
useEffect(() => {
if (!user) return; // pre-condition
UserService
.getUserRole(auth.user.access_token)
.then(setRoles);
}, [auth.user]);
return roles;
}
Then in any component:
import useRoles from "../useRoles";
const MyExampleComponent = () => {
const roles = useRoles();
if (!roles) return <span>Please login (or something) to see the roles!</span>
return <div>{/* use roles here */}</div>
}
Better solution: Service provider
If there's a lot of different methods on the user service that needs to be used all over the app, then wrapping the whole service and providing a ready-to-use version through React's context would be best in my opinion.
But first, let's rework the UserService a little so that it uses a local axios instance instead of the global axios instance.
// I also made it a class, but it would also work with an object.
class UserService {
constructor(axios) {
this.axios = axios;
}
getUserRole(){
// use the local axios instance
return this.axios({
method: "get",
// Use the default URL from local axios instance
url: "user/role",
})
.then(({ data }) => data)
.catch(console.log),
}
getSomethingElse() {
// ...
}
}
Then, we can setup the React's context for the user service.
// UserServiceContext.js
import React from 'react';
import { useAuth } from "react-oidc-context";
import UserService from "../../../services/authentication/userService";
// Local axios instance
const axiosInstance = axios.create({
baseURL: 'https://<url>', // set the base URL once here
});
const userServiceInstance = new UserService(axiosInstance);
const UserServiceContext = React.createContext(userServiceInstance);
// Convenience hook
export const useUserService = () => useContext(UserServiceContext);
export const UserServiceProvider = (props) => {
const auth = useAuth();
useEffect(() => {
// If the user changes, update the token used by our local axios instance.
axiosInstance.defaults.headers
.common['Authorization'] = `Bearer ${auth.user?.access_token}`;
}, [auth.user]);
return (
<UserServiceContext.Provider value={userServiceInstance} {...props} />
);
}
Then anywhere, but commonly at the App's root:
import { AuthProvider } from "react-oidc-context";
import { UserServiceProvider } from "./UserServiceContext";
const App = () => (
<AuthProvider>
<UserServiceProvider>
<Content />
</UserServiceProvider>
</AuthProvider>
);
Now everything is ready to be used in any component!
import { useUserService } from '../UserServiceContext';
const MyExampleComponent = () => {
const userService = useUserService();
const [roles, setRoles] = useState();
// e.g. load roles once on mount.
useEffect(() => {
userService // use the service from the context
.getUserRole() // no auth token needed anymore!
.then(setRoles);
}, []);
if (!roles) return <span>Please login (or something) to see the roles!</span>
return <div>{/* use roles here */}</div>
}
Note that a custom hook could still be used to wrap the roles fetching logic. Both the context and hooks can be used together to wrap logic to each's own preferences.
// Here's what the hook could look like if it used the new provider above.
const useRoles = () => {
const userService = useUserService();
const [roles, setRoles] = useState();
// e.g. load roles once on mount.
useEffect(() => {
userService // use the service from the context
.getUserRole() // no auth token needed anymore!
.then(setRoles);
}, []);
return roles;
}
I consider the provider solution to be better since it provides more flexibility while keeping control over the exposed API.
In my solution, I suggest using the UserService instance as the provided value, but the provider could be changed to expose only parts of the API, or it could provide the roles and other data automatically. It's up to you!
Disclaimer: I've used minimal code to demonstrate a working solution and my answer may not address all constraints of your situation. For example, the axios instance could be created inside the provider as a lazy initialized useRef, same thing goes for the UserService instance, etc.
I am using axios in my create-react-app. Which is the best way to use axios:
Method 1:
ajax.js
import axios from 'axios';
const axiosInstance = axios.create({});
export default axiosInstance;
app.js
import ajax from './ajax.js';
ajax.post('url');
Method 2:
ajax.js
import axios from 'axios';
class AjaxService{
constructor(apiConfig){
this.service = axios.create(apiConfig);
}
doGet(config){
return this.service.get(config.url);
}
...
}
export default AjaxService;
app.js:
import AjaxService from './ajax';
const service1 = new AjaxService();
service.doGet({url:'url'});
app2.js
import AjaxService from './ajax';
const service2 = new AjaxService();
service.doGet({url:'url'});
In method 2, we have to initialize the service wherever we make a call, which may or may not be a best practice. If we follow method 2, Is there a way to make it as a common service across the application?
i've seen a way in here and i came up with another solution like i explained below:
1 - i created my service with axios
import axios from 'axios';
const instance = axios.create({
baseURL: process.env.REACT_APP_BASE_URL
// headers: { 'X-Custom-Header': 'foobar' }
});
// Add a request interceptor
instance.interceptors.request.use(
(config) => {
// Do something before request is sent
return config;
},
(error) => {
// Do something with request error
return Promise.reject(error);
}
);
// Add a response interceptor
instance.interceptors.response.use(
(response) => {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
(error) => {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
}
);
export default instance;
2- i use that service to create a function for api call.
in here i can add a new AbortController for later use in useEffect.
import axios from 'services/request';
export function getMarket(type, callBack) {
const controller = new AbortController();
axios
.get(`https://dev.zh1.app/api/market/map?type=${type}`, {
signal: controller.signal
})
.then((res) => {
callBack(true, res.data);
})
.catch((res) => {
callBack(false, res.response);
});
return controller;
}
export default {
getMarket
};
3- in the hooks folder i created a hook called useApi. the controller from step 2 used in here. if you check the link above you can see the author add request function because you may have some props to pass to api call. i think it is valid but ugly. so i decided to create a closure for useApi to pass any params i want to the Axios in step 2.
import { useEffect, useState } from 'react';
// in useStrict mode useEffect call twice but will not in production
export default function useApi(apiFunc) {
return function useApiCall(...params) {
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const apiCall = useCallback(() => {
setLoading(true);
// controller is new AbortController which set each api function
const controller = apiFunc(...params, (ok, data) => {
setLoading(false);
if (ok) {
setResult(data);
} else {
setError(data.message);
}
});
return () => {
controller.abort();
};
}, []);
useEffect(() => {
apiCall();
}, []);
return {result, loading, error, [apiFunc.name]: apiCall};
};
}
4- finally in my react component
import { IconButton } from '#mui/material';
import useApi from '#share/hooks/useApi';
import { Refresh } from '#share/icons';
import { getCaptcha as CaptchaApi } from 'api/oauth/captcha';
import CaptchaStyle from './style';
export default function Captcha() {
const { result: captcha, getCaptcha } = useApi(CaptchaApi)();
return (
<CaptchaStyle>
<img src={`data:image/png;base64,${captcha?.base64}`} alt="captcha" />
<IconButton onClick={getCaptcha}>
<Refresh />
</IconButton>
</CaptchaStyle>
);
}
i think this approach i quite good and if you dont need to pass any props just call useApi([yourfunction])() with empty function.
and you can have access to the function inside of useApi if you need to call it again.
It totally depends on your project. If your project relies more on the function component then go ahead and use the first approach.
If you use classes for the majority of your components go for the second approach.
I generally use the first approach, it's easy and avoids this altogether. Also, it's easy to target multiple instances.
// Ajax.js file
import axios from "axios";
export function updateData=(body,callback){
le url= 'your api to call'
axios
.put(url, body)
.then((response) => response.data)
.then((res) => {
callback(res);
})
.catch((error) => {
console.log(error);
callback('error occurred');
});
}
// app.js file
import {updateData} from './ajax.js'
//Place your code where you need
updateData(yourBodyToPass,res=>{
//Stuff after the response
)
Note:- pass your data as first argument and get response of api from second
I was using this tutorial https://css-tricks.com/build-a-dynamic-jamstack-app-with-gatsbyjs-and-faunadb/ as a guide to do some dynamic content updates with faunadb and gatsbyjs. Now I have everything working to a certain extent. The issue I am having is if I update the database manually, or eventually in a lambda function, I want the graphql query to cause a state change and a rerender in react. What happens is I can see the new data only on a page refresh. According to this article this is supposed to be client side hydration, but I am not getting the desired effect, when the db changes.
Any help would greatly be appreciated
Here is my gatsby page
import React, { useState } from 'react';
import Layout from "../components/layout"
import { gql } from "apollo-boost"
import { useQuery } from "#apollo/react-hooks"
import { graphql } from "gatsby"
const HIT_COUNTER = gql`
query HitCounter{
findHitCounterByID(id: "0248392") {
amount
}
}
`
const IndexPage = () => {
const { loading, data } = useQuery(HIT_COUNTER);
return (
<Layout>
{data.findHitCounterByID.amount}
</Layout>
)
}
export default IndexPage
In gatsbybrowser.js I am doing the following
import React from "react"
import ApolloClient from "apollo-boost"
import { ApolloProvider } from "react-apollo"
const client = new ApolloClient({
uri: "https://graphql.fauna.com/graphql",
request: operation => {
operation.setContext({
headers: {
Authorization: `Bearer ${process.env.GATSBY_FAUNA_DB}`,
},
})
},
})
export const wrapRootElement = ({ element }) => (
<ApolloProvider client={client}>{element}</ApolloProvider>
)