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;
Related
I have an SPA (reactjs) site that has some link like http://example.com/blog/:id
and I want to dynamically create a dynamic sitemap.
I know that an npm package like react-router-sitemap can generate a sitemap with a command but I want to create sitemap.xml every time that this URL gets visited: http://example.com/sitemap.xml. That way if I create a new blog post, I don't need to re-create the sitemap again.
How should I do this?
You can run a cron-job to achieve this. Load the blog ids you need and write to the sitemap.xml in the build folder.
Try like below. You need few packages installed; node-cron, sitemap and axios.
I have set up a simple express server to server-rendering react UIs and I call startSiteMapGeneratorJob function from there. This is working fine for me.
SiteMapGenerator.js
import cron from "node-cron";
import { SitemapStream, streamToPromise } from "sitemap";
import { Readable } from "stream";
import fs from "fs";
import axios from "axios";
import { BACKEND_API_URL } from "../constants/APIConstants";
const startSiteMapGeneratorJob = () => {
generateBlogSitemap();
console.log("site map job started");
cron.schedule("* * 12 * *", function() {
console.log("Running a task every 12 hours (twice a day)");
generateBlogSitemap();
});
};
export const generateBlogSitemap = async () => {
try{
const res = await axios.get(`${BACKEND_API_URL}/blog/getAllIDs`);
const { success, blogIDs = [] } = res.data;
if (success && blogIDs.length > 0) {
// An array with the links
const links = blogIDs.map(({ _id }) => {
return { url: `/blog/${_id}` };
});
// Create a stream to write to
const stream = new SitemapStream({ hostname: "https://example.com" });
// Return a promise that resolves with your XML string
const data = await streamToPromise(Readable.from(links).pipe(stream));
fs.writeFileSync("./build/sitemap.xml", data.toString());
console.log("Blog Sitemap Generated");
} else {
console.log("No Blog Sitemap Generated");
}
} catch(error){
console.log("Sitemap generator error", error)
}
};
export default startSiteMapGeneratorJob;
I've been working on a Next.JS web application for the past couple of days but I've reached a problem. The app has an API call (/api/settings) which returns some settings about the application from the database. Currently, I have a function which returns these settings and access to the first component:
App.getInitialProps = async () => {
const settingsRequest = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/settings`
);
const settingsResponse = await settingsRequest.json();
return { settings: settingsResponse };
};
This does work and I am able to pass in settings to components but there are two problems with this:
I need to nest the prop through many components to reach the components that I need
This request runs every time a page is reloaded/changed
Essentially, I need to create a system that does this:
runs a function in the _app.tsx getInitialProps to check if the data is already in localStorage, if not make the API request and update localStorage
have the localStorage value accessible from a custom hook.
Right now the problem with this is that I do not have access to localStorage from the app.tsx getInitialProps. So if anyone has an alternative to run this function before any of the page loads, please let me know.
Thanks!
I found a solution, it might be a janky solution but I managed to get it working and it might be useful for people trying to achieve something similar:
First we need to create a "manager" for the settings:
export const checkIfSettingsArePresent = () => {
const settings = localStorage.getItem("app_settings");
if (settings) return true;
return false;
};
export const getDataAndUpdateLocalStorage = async () => {
const r = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/settings`);
const response = await r.json();
localStorage.setItem("app_settings", JSON.stringify(response));
};
With that created we can add a UseEffect hook combined with a useState hook that runs our function.
const [doneFirst, setDoneFirst] = useState<boolean>(false);
useEffect(() => {
const settingsPreset = checkIfSettingsArePresent();
if (performance.navigation.type != 1)
if (settingsPreset) return setDoneFirst(true);
const getData = async () => {
await getDataAndUpdateLocalStorage();
setDoneFirst(true);
};
getData();
}, []);
//any other logic
if (!doneFirst) {
return null;
}
The final if statement makes sure to not run anything else before the function.
Now, whenever you hot-reload the page, you will see that the localStorage app_settings is updated/created with the values from the API.
However, to access this more simply from other parts of the app, I created a hook:
import { SettingsType } from "#sharex-server/common";
export default function useSettings() {
const settings = localStorage.getItem("app_settings") || {
name: "ShareX Media Server",
};
//#ts-ignore
return JSON.parse(settings) as SettingsType;
}
Now I can import useSettings from any function and have access to my settings.
I am currently building a Ruby on Rails Webpacker application with a React front end. I am at the point where I would like to create all the quires I need to make calls to my Rails API. I was loosely following this tutorial https://www.youtube.com/watch?v=0bKc_ch6MZY (https://github.com/daryanka/react-query-tutorial/blob/master/src/containers/Post.js, https://github.com/daryanka/react-query-tutorial/blob/master/src/Queries.js), in order to write some axios based query functions that I could use with react-query. I had no problem with getting the queries to behave as expected when the url for the endpoint was a hard coded string. When I attempted to pass in a parameter to make dynamic urls I ran into the issue of not having access to said parameter; specifically the "prodId" parameter. I did however notice that the "prodId" was inside the "key" parameter array like so:
queryKey: Array(2)
0: "product"
1: "1"
length: 2
enter code here
I could just access it from there but that approach does seem a little off, I also did not find any examples or documentation that attempted to access a parameter from the query key array. I would like to know what it is I am doing incorrectly with regards to passing in parameters? Were there some syntax changes in react-query that I am not taking into account?
react-query#^3.17.2
webpacker (5.2.1)
axios#^0.21.1
//Product.js
import axios from "axios"
import { getProduct } from "../../queries/products"
import { useQuery } from "react-query"
const prodId= '1'
const { data } = useQuery(['product', prodId], getProduct)
//queries/products.js
import axios from 'axios'
export const getProduct = async (key, { prodId }) => {
console.log(opid)
const { data } = await axios.get(`/api/v1/products/${prodId}`)
return data
}
The query function that you pass to react-query gets a queryContext injected, which is an object that consists of the queryKey (and some more information if you are using an infinite query). So yes, one correct way to access dependencies is through the queryKey:
export const getProduct = async ({ queryKey }) => {
const [_, prodId] = queryKey
const { data } = await axios.get(`/api/v1/products/${prodId}`)
return data
}
const { data } = useQuery(['product', prodId], getProduct)
Another way is to use inline anonymous functions, which is well documented in the docs in: If your query function depends on a variable, include it in your query key
export const getProduct = async (prodId) => {
const { data } = await axios.get(`/api/v1/products/${prodId}`)
return data
}
const { data } = useQuery(['product', prodId], () => getProduct(prodId))
I'm using the following (typescript) to send parameters to my custom useQuery hook.
import { useQuery } from 'react-query'
import service from '../api'
const queryKey = 'my-query-key'
type useProductsParams = Parameters<typeof service.listProducts>
const useProducts = (...params: useProductsParams) => {
return useQuery(queryKey, () => service.getProduct(...params))
}
export default useProducts
I am looking for a pattern for a react component. It should include lowdb. Someone already build one?
Okay, I dont access the lowdb by frontend. I use Axios to send a request to my node/express backend and respond with the lowdb-data. Requires you know how to interact with a react frontend and a node/express backend on different ports. I dont let express render my react-app... looks something like this and axios as a dependency in the package.json + import Axios from 'axios':
export default class Status extends Component {
constructor(props) {
super(props);
this.state = "";
}
componentWillMount() {
Axios.get("http://localhost:4000/dbUserAuth").then((res) => {
let authState = res.data;
if (authState) {
this.setState({ authState });
}
});
}
This sends a request to my node.js/express backend which runs on port 4000. The frontend runs on 2000.
The backend can look something like this:
server.get("/dbUserAuth", (request, response) => {
function resAuthData() {
let array = [];
const dbUserAuth = db.get("UserAuth[0]").value();
const dbChatIdFound = db.get("UserAuth[1]").value();
const botActive = db.get("UserAuth[2]").value();
array.push(dbUserAuth, dbChatIdFound, botActive);
response.send(array);
}
resAuthData();
});
Hopefully someone needs theese snippets.
best regards
I'm new to react and react-admin.
I'm, using jsonServerProvider (in my App.js I have the following):
import jsonServerProvider from 'ra-data-json-server';
I'd like to create a custom bulk action. In a list, select many items and click a button to "connect" them. I tried to use UPDATE_MANY, but this calls my endpoint multiple times, so it's not suitable. Ideally I need the request to call my endpoint like so: url.to.myendpoint?ids=1,2,3 or even better pass an array of IDs in the body and use a PUT request.
Just to understand how things work and debug network calls, I tried also the GET_MANY, in the dataproviders page, the request seems to get the IDs like so: { ids: {mixed[]}, data: {Object} }
But the request is sent to the server like so: url.to.myendpoint?id=1&id=2&id=3 which in my python/flask backend is not nice to parse.
I've spent a bunch of time reading the docs, e.g.:
https://github.com/marmelab/react-admin/blob/master/docs/Actions.md
https://react-admin.com/docs/en/actions.html
https://marmelab.com/react-admin/Actions.html
I tried different approaches and I could not achieve what I want. So again please help me to make my custom bulk button work.
My bulk button is called ConnectItemsButton and the code looks like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Button, crudUpdateMany } from 'react-admin';
import { showNotification, GET_MANY } from 'react-admin';
import dataProvider from './dataProvider';
class ConnectItemsButton extends Component {
handleClick = () => {
const { selectedIds } = this.props;
dataProvider(GET_MANY, 'items/connect', { ids: selectedIds })
.then(() => {
showNotification('Connected!');
})
.catch((e) => {
showNotification('Error.', 'warning')
});
};
render() {
return (
<Button label="Associate" onClick={this.handleClick} />
);
}
}
export default connect(undefined, { crudUpdateMany })(ConnectItemsButton);
Note that the contents of ./dataProvider (it's the same provider used in the App.js file and passed to the <Admin> in the props):
import jsonServerProvider from 'ra-data-json-server';
export default jsonServerProvider('http://127.0.0.1:5000/api');
In my list I created it, the button is displayed properly, so here I share the code snippet:
const PostBulkActionButtons = props => (
<Fragment>
<ConnectItemsButton {...props} />
</Fragment>
);
...
export const ItemsList = props => (
<List {...props} bulkActionButtons={<PostBulkActionButtons />}>
...
In my backend endpoint items/connect I simply need to get a comma separated list of IDs to parse, that's it.
A simple working solution would be awesome, or at least point me in the right direction. Thanks for your help.
The way I would do this is by using react-admin's dataActions. Your action would be something like this:
crudCreate('items/connect', { selectedIds: selectedIds }, basePath , redirectUrl)
I recommend using a custom dataProvider (e.g. if you use jsonDataProvider, in your App.js import where you see ra-data-json-server: if you use WebStorm Ctrl + click on it and copy the code e.g. to customJsonDataProvider.js and fix eventual warnings, e.g. import lines should be moved at the top) and pass it as props to your Admin component. In your customJsonDataProvider you will have a convertDataRequestToHTTP, or something similar, which manages the CRUD actions and returns the url and HTTP method that you want to use.
An example of what you want to do would be:
const convertDataRequestToHTTP = (type, resource, params) => {
let url = '';
const options = {};
switch (type) {
...
case CREATE: {
...
if (type === 'items/connect') {
const { data: { selectedIds } } = params;
url = `${apiUrl}?${selectedIds.reduce((acc, id) => `${acc};${id}`)}`
options.method = 'GET';
}
...
break;
}
...
}
return { url, options };
}
In your dataProvider.js, modify the import to use the custom provider you created, e.g.:
import jsonServerProvider from './customJsonServer';
The code in your AssociateTradesButton.js should work fine as is.
You can find the documentation for creating you own dataProvider here.
I think using Promise.all will solve the issue, see this link for reference https://github.com/marmelab/react-admin/blob/master/packages/ra-data-simple-rest/src/index.js#L140