React admin: Can I create two resources using one form? - reactjs

I have two resources: Hardware and Product.
The hardware depends on the Product.
All examples suggest using one Create/Edit form for one resource.
Can I change this behavior?
For better UX, I want to create a product seamlessly.
Users should see just one form with inputs from the two resources.
Of course, the product must be created before using it's identifier as a foreign key while creating hardware.
Do you have similar examples?

All examples suggest using one Create/Edit form for one resource.
Can I change this behavior?
The wonderful team at Marmelab write the documentation to focus on showing the key aspects so that you appreciate the basic building blocks of react-admin.
But yes, it's possible to one Create/Edit form with multiple resources.
And all the magic lies in the Toolbar, and here's what the team says:
At the bottom of the form, the toolbar displays the submit button.You can override this component by setting the toolbar prop, to display the buttons of your choice.
And once you have buttons of your choice, you have all the freedom to handle multiple <Resource>s (of course, you must declare those resources within the <Admin> component).
Here's an example of what i've done multiple times.
And i'll use it in context of - hardware and product - your resources.
import React from 'react';
import * as request from 'superagent'; // HTTP request library
import { useHistory } from 'react-router-dom';
import { useFormState } from 'react-final-form'; // react-admin forms are based on this
import {
Create,
SaveButton,
SimpleForm,
Toolbar,
useRedirect,
useNotify
} from 'react-admin';
// main "create' component
export const ProductCreate = props => {
return (
<Create {...props}>
// we override the "toolbar" prop by passing a custom toolbar
<SimpleForm toolbar={<ProductCreateToolbar/>} {...props}>
...
</SimpleForm>
</Create>
);
};
// Handling "create" mode
const ProductCreateToolbar = props => {
return (
<Toolbar {...props}>
<ProductSaveButton {...props} />
</Toolbar>
)
};
// define the "save/create" button
const ProductSaveButton = props => {
const formState = useFormState();
const redirectTo = useRedirect();
const notify = useNotify();
const token = sessionStorage.getItem('token'); // often created after "login" success
const {
// form fields related to "product" e.g.
productName,
productSize,
productCost,
// form fields related to "hardware" e.g.
hardwareName,
hardwareLocation
...other
} = formState && formState.values;
// Note: useFormState() gets fields based on the "source" prop of the inputs.
const handleSave = () => {
// based on any HTTP request library e.g. superagent
apiCall(
{ ... }, // data to create "product"
token, // needed if the endpoints are protected
'post', // basically the HTTP POST
'products/' // replace with actual endpoint to create a product
).then(res => {
// check for a response
if (response) {
const { status, body } = res;
// if the "product" has been created
if (status === 200 || status === 201) {
// pick the "product id"
// or specific foreign-key to create a "hardware"
let productId = body['id'];
apiCall(
{ product_id: productId, ...other } // data to create "hardware"
token,
'post',
'hardwares/' // replace with actual endpoint to create a hardware
).then(res => {
if (res) {
const { status } = res;
if (status === 200 || status === 201) {
// display toast message to user
notify(`Product and hardware have been created successfully`, "info");
// redirect to products list
redirectTo('list', basePath);
} else {
// any action if create fails
};
};
}).catch(
error => console.error('Error while saving:', error)
);
};
};
}).catch(
error => console.error('Error while saving:', error)
);
}
return (
<SaveButton
{...props}
label="Save"
submitOnEnter={false}
saving={loading}
onSave={handleSave} // handles the create
/>
);
};
// for making the HTTP requests (used within the toolbar buttons)
const apiCall = (data, header, type, url) => {
return new Promise((resolve, reject) => {
request[type](`http://localhost:8000/api/${url}`)
.send(data)
.set('Content-Type', 'application/json')
.set('authorization', 'Bearer ' + header)
.end((err, res) => {
if (res) {
return resolve(res.body);
}
return reject(err);
});
});
};
Please note that react-admin provides specialized hooks.
And you can do that above by calling the useCreate hook twice (for "product" and 'hardware") but you WILL NOT have as much control over the process as what I have demonstrated above.

Related

Stripe payment button for ReactJs with a simple Snippet Code

I have found a lot of repositories and examples about installing an Stripe complete checkout, but I cannot just use the simple Snippet code that Stripe offers for a product. How can I use that code on a page in my React Project? Here is the code. I just want to redirect the user to the Stripe checkout page for that product, I don't want to use my own formulary and I don't want either to collect data in my app. Thanks a lot.
<!-- Load Stripe.js on your website. -->
<script src="https://js.stripe.com/v3"></script>
<!-- Create a button that your customers click to complete their purchase. Customize the styling to suit your branding. -->
<button
style="background-color:#6772E5;color:#FFF;padding:8px 12px;border:0;border-radius:4px;font-size:1em"
id="checkout-button-price_1Heree568gerg54rtretrt"
role="link"
type="button"
>
Checkout
</button>
<div id="error-message"></div>
<script>
(function() {
var stripe = Stripe('pk_live_t5tyutrytutruytyutyufake....');
var checkoutButton = document.getElementById('checkout-button-price_1Heree568gerg54rtretrt');
checkoutButton.addEventListener('click', function () {
// When the customer clicks on the button, redirect
// them to Checkout.
stripe.redirectToCheckout({
lineItems: [{price: 'price_1Heree568gerg54rtretrt', quantity: 1}],
mode: 'subscription',
// Do not rely on the redirect to the successUrl for fulfilling
// purchases, customers may not always reach the success_url after
// a successful payment.
// Instead use one of the strategies described in
// https://stripe.com/docs/payments/checkout/fulfill-orders
successUrl: 'https://myweb.com/success',
cancelUrl: 'https://myweb.com/canceled',
})
.then(function (result) {
if (result.error) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer.
var displayError = document.getElementById('error-message');
displayError.textContent = result.error.message;
}
});
});
})();
</script>
You can create a dedicated component for that. As stated in the documentation, I am using StripeJS to import it as a module.
// npm install #stripe/stripe-js
import React from 'react';
import {loadStripe} from '#stripe/stripe-js';
const StripeButton = (props) => {
const [stripeError, setStripeError] = React.useState(null);
const [stripe, setStripe] = React.useState(null);
useEffect( async () => {
if (!stripe) {
// Here, you can use some `props` instead of hardcoding the API key
const stripeTmp = await loadStripe('pk_live_t5tyutrytutruytyutyufake....');
setStripe(stripeTmp);
}
});
const handleClick = () => {
// Reset error holder
setStripeError(null);
// When the customer clicks on the button, redirect
// them to Checkout.
stripe.redirectToCheckout({
// Here you can use another `prop` instead of hard coding it
lineItems: [{price: 'price_1Heree568gerg54rtretrt', quantity: 1}],
mode: 'subscription',
// Do not rely on the redirect to the successUrl for fulfilling
// purchases, customers may not always reach the success_url after
// a successful payment.
// Instead use one of the strategies described in
// https://stripe.com/docs/payments/checkout/fulfill-orders
successUrl: 'https://myweb.com/success',
cancelUrl: 'https://myweb.com/canceled',
})
.then(function (result) {
if (result.error) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer.
setStripeError(result.error.message);
}
});
}
return (
<>
{ stripe ? (
<button
style="background-color:#6772E5;color:#FFF;padding:8px 12px ;border:0;border-radius:4px;font-size:1em"
id="checkout-button-price_1Heree568gerg54rtretrt"
role="link"
type="button"
onClick={ handleClick }
>
Checkout
</button>
) : "Loading..."
}
{ stripeError ? <div id="error-message">{ stripeError }</div> : null }
</>
)
}
export default StripeButton;

How to link to a show view from an index using react hooks with firestore data

I am trying to figure out how to define a link to reference that can use a firebase document id to link to a show view for that document. I can render an index. I cannot find a way to define a link to the document.
I've followed this tutorial - which is good to get the CRUD steps other than the show view. I can find other tutorials that do this with class components and the closest I've been able to find using hooks is this incomplete project repo.
I want to try and add a link in the index to show the document in a new view.
I have an index with:
const useBlogs = () => {
const [blogs, setBlogs] = useState([]); //useState() hook, sets initial state to an empty array
useEffect(() => {
const unsubscribe = Firebase
.firestore //access firestore
.collection("blog") //access "blogs" collection
.where("status", "==", true)
.orderBy("createdAt")
.get()
.then(function(querySnapshot) {
// .onSnapshot(snapshot => {
//You can "listen" to a document with the onSnapshot() method.
const listBlogs = querySnapshot.docs.map(doc => ({
//map each document into snapshot
id: doc.id, //id and data pushed into blogs array
...doc.data() //spread operator merges data to id.
}));
setBlogs(listBlogs); //blogs is equal to listBlogs
});
return
// () => unsubscribe();
}, []);
return blogs;
};
const BlogList = ({ editBlog }) => {
const listBlog = useBlogs();
return (
<div>
{listBlog.map(blog => (
<Card key={blog.id} hoverable={true} style={{marginTop: "20px", marginBottom: "20px"}}>
<Title level={4} >{blog.title} </Title>
<Tag color="geekblue" style={{ float: "right"}}>{blog.category} </Tag>
<Paragraph><Text>{blog.caption}
</Text></Paragraph>
<Link to={`/readblog/${blog.id}`}>Read</Link>
<Link to={`/blog/${blog.id}`}>Read</Link>
</Card>
))}
</div>
);
};
export default BlogList;
Then I have a route defined with:
export const BLOGINDEX = '/blog';
export const BLOGPOST = '/blog/:id';
export const NEWBLOG = '/newblog';
export const EDITBLOG = '/editblog';
export const VIEWBLOG = '/viewblog';
export const READBLOG = '/readblog/:id';
I can't find a tutorial that does this with hooks. Can anyone see how to link from an index to a document that I can show in a different page?
I did find this code sandbox. It looks like it is rendering a clean page in the updateCustomer page and using data from the index to do it - but the example is too clever for me to unpick without an explanation of what's happening (in particular, the updateCustomer file defines a setCustomer variable, by reference to useForm - but there is nothing in useForm with that definition. That variable is used in the key part of the file that tries to identify the data) - so I can't mimic the steps.
NEXT ATTEMPT
I found this blog post which suggests some changes for locating the relevant document.
I implemented these changes and while I can print the correct document.id on the read page, I cannot find a way to access the document properties (eg: blog.title).
import React, { useHook } from 'react';
import {
useParams
} from 'react-router-dom';
import Firebase from "../../../firebase";
import BlogList from './View';
function ReadBlogPost() {
let { slug } = useParams()
// ...
return (
<div>{slug}
</div>
)
};
export default ReadBlogPost;
NEXT ATTEMPT:
I tried to use the slug as the doc.id to get the post document as follows:
import React, { useHook, useEffect } from 'react';
import {
useParams
} from 'react-router-dom';
import Firebase from "../../../firebase";
import BlogList from './View';
function ReadBlogPost() {
let { slug } = useParams()
// ...
useEffect(() => {
const blog =
Firebase.firestore.collection("blog").doc(slug);
blog.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
doc.data();
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
});
return (
<div>{blog.title}
</div>
)
};
export default ReadBlogPost;
It returns an error saying blog is not defined. I also tried to return {doc.title} but I get the same error. I can see all the data in the console.
I really can't make sense of coding documentation - I can't figure out the starting point to decipher the instructions so most things I learn are by trial and error but I've run out of places to look for inspiration to try something new.
NEXT ATTEMPT
My next attempt is to try and follow the lead in this tutorial.
function ReadBlogPost(blog) {
let { slug } = useParams()
// ...
useEffect(() => {
const blog =
Firebase.firestore.collection("blog").doc(slug);
blog.get().then(function(doc) {
if (doc.exists) {
doc.data()
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
},
[blog]
);
return (
<div><Title level={4} > {blog.title}
</Title>
<p>{console.log(blog)}</p>
</div>
)
};
export default ReadBlogPost;
When I try this, the only odd thing is that the console.log inside the useEffect method gives all the data accurately, but when I log it form inside the return method, I get a load of gibberish (shown in the picture below).
NEXT ATTEMPT
I found this tutorial, which uses realtime database instead of firestore, but I tried to copy the logic.
My read post page now has:
import React, { useHook, useEffect, useState } from 'react';
import {
useParams
} from 'react-router-dom';
import Firebase from "../../../firebase";
import BlogList from './View';
import { Card, Divider, Form, Icon, Input, Switch, Layout, Tabs, Typography, Tag, Button } from 'antd';
const { Paragraph, Text, Title } = Typography;
const ReadBlogPost = () => {
const [loading, setLoading] = useState(true);
const [currentPost, setCurrentPost] = useState();
let { slug } = useParams()
if (loading && !currentPost) {
Firebase
.firestore
.collection("blog")
.doc(slug)
.get()
.then(function(doc) {
if (doc.exists) {
setCurrentPost(...doc.data());
console.log("Document data:", doc.data());
}
}),
setLoading(false)
}
if (loading) {
return <h1>Loading...</h1>;
}
return (
<div><Title level={4} >
{currentPost.caption}
{console.log({currentPost})}
</Title>
</div>
)
};
export default ReadBlogPost;
Maybe this blog post is old, or maybe it's to do with it using .js where I have .jsx - which I think means I can't use if statements, but I can't get this to work either. The error says:
Line 21:9: Expected an assignment or function call and instead saw
an expression no-unused-expressions
It points to the line starting with Firebase.
I got rid of all the loading bits to try and make the data render. That gets rid of the above error message for now. However, I still can't return the values from currentPost.
It's really odd to me that inside the return statement, I cannot output {currentPost.title} - I get an error saying title is undefined, but when I try to output {currentPost} the error message says:
Error: Objects are not valid as a React child (found: object with keys
{caption, category, createdAt, post, status, title}). If you meant to
render a collection of children, use an array instead.
That makes no sense! I'd love to understand why I can log these values before the return statement, and inside the return statement, I can log them on the object but I cannot find how to log them as attributes.
First of all: is your useBlog() hook returning the expected data? If so, all you need to do is define your <Link/> components correctly.
<Link
// This will look like /readblog/3. Curly braces mean
// that this prop contains javascript that needs to be
// evaluated, thus allowing you to create dynamic urls.
to={`/readblog/${blog.id}`}
// Make sure to open in a new window
target="_blank"
>
Read
</Link>
Edit: If you want to pass the data to the new component you need to set up a store in order to avoid fetching the same resource twice (once when mounting the list and once when mounting the BlogPost itself)
// Define a context
const BlogListContext = React.createContext()
// In a top level component (eg. App.js) define a provider
const App = () => {
const [blogList, setBlogList] = useState([])
return (
<BlogListContext.Provider value={{blogList, setBlogList}}>
<SomeOtherComponent/>
</BlogListContext.Provider>
)
}
// In your BlogList component
const BlogList = ({ editBlog }) => {
const { setBlogList } = useContext(BlogListContext)
const listBlog = useBlogs()
// Update the blog list from the context each time the
// listBlog changes
useEffect(() => {
setBlogList(listBlog)
}, [listBlog])
return (
// your components and links here
)
}
// In your ReadBlog component
const ReadBlogComponent = ({ match }) => {
const { blogList } = useContext(BlogListContext)
// Find the blog by the id from params.
const blog = blogList.find(blog => blog.id === match.params.id) || {}
return (
// Your JSX
)
}
There are other options for passing data as well:
Through url params (not recommended).
Just pass the ID and let the component fetch its own data on mount.
I found an answer that works for each attribute other than the timestamp.
const [currentPost, setCurrentPost] = useState([]);
There is an empty array in the useState() initialised state.
In relation to the timestamps - I've been through this hell so many times with firestore timestamps - most recently here. The solution that worked in December 2019 no longer works. Back to tearing my hair out over that one...

Best way to make exactly one API call in react-native (fetch, axios etc.)?

I am currently creating an app that includes a barcode scanner. Unfortunately, the only company I found - that seems to own 90+% of all food barcodes - provides an API starting at 500$/ year. So I tried to create a workaround which works but fires so many API calls that I get blocked immediately after one try. I inserted a console.warn to see how many calls are fired everytime I call the API and it's about 20 to 35 times. Here is the code:
async getApiData() {
let formData = new FormData();
formData.append('keyValue', 4015000961547);
formData.append('requestTradeItemType', 'ownership');
formData.append('keyCode', 'gtin');
formData.append('someRandomToken', '1');
const response = await fetch('http://gepir.gs1.org/index.php?option=com_gepir4ui&view=getkeylicensee&format=raw', {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': 'someRandomCookie'
},
body: formData
});
if (response.status == 200) {
const responseJson = JSON.parse(response._bodyText);
const productName = responseJson.gepirParty.partyDataLine.gS1KeyLicensee.partyName;
this.setState({ productName });
}
}
If you try it with this keyValue of 4015000961547 you will get Henkel AG in case you want to test (http://gepir.gs1.org/index.php/search-by-gtin). What I don't understand is: why is my function firing so many requests to the API once the barcode is read altough I am using async/ await? I read that axios is the better method but it didn't really work in my case ... is there a third/ fourth method I could try? It is very crucial that once I have the scan data I only send one request to the API otherwise I can't test it.
Just to provide all the information needed this is my code for getting the barcode data after the scan. I am using react-native-camera:
import { RNCamera } from 'react-native-camera';
... some more code ...
export default class Scanner extends Component {
... some code ...
async onBarCodeRead(scanResult) {
console.warn(scanResult.type);
if (scanResult.data != null) {
console.warn(scanResult.data);
const eanCode = await scanResult.data;
}
return;
}
...some code here...
render() {
return (
<View>
<RNCamera
ref={ref => {
this.camera = ref;
}}
barcodeFinderVisible={this.state.camera.barcodeFinderVisible}
defaultTouchToFocus
mirrorImage={false}
onBarCodeRead={this.onBarCodeRead.bind(this)}
onFocusChanged={() => {}}
onZoomChanged={() => {}}
type={this.state.camera.type}
/>
<View>
<Text>Please scan the barcode.</Text>
</View>
</View>
);
}
}
For simplicity, I removed any styling and unused props in the RNCamera tag.
This appears to be a feature -- that it continuously scans for a barcode.
There is an issue thread on the repo where it is suggested to set a flag the first time the event is emitted, like so:
onBarCodeRead = (...someArgs) => {
if (!this.isBarcodeRead) {
this.isBarcodeRead = true;
// Do your work
}
}

GatsbyJS getting data from Restful API

I am new in both React and GatsbyJS. I am confused and could not make figuring out in a simple way to load data from third-party Restful API.
For example, I would like to fetch data from randomuser.me/API and then be able to use the data in pages.
Let’s say something like this :
import React from 'react'
import Link from 'gatsby-link'
class User extends React.Component {
constructor(){
super();
this.state = {
pictures:[],
};
}
componentDidMount(){
fetch('https://randomuser.me/api/?results=500')
.then(results=>{
return results.json();
})
.then(data=>{
let pictures = data.results.map((pic,i)=>{
return(
<div key={i} >
<img key={i} src={pic.picture.medium}/>
</div>
)
})
this.setState({pictures:pictures})
})
}
render() {
return (<div>{this.state.pictures}</div>)
}
}
export default User;
But I would like to get the help of GraphQL in order to filter & sort users and etc…..
Could you please help me to find the sample to how I can fetch data and insert them into GraphQL on gatsby-node.js?
If you want to use GraphQL to fetch your data, you have to create a sourceNode. The doc about creating a source plugin could help you.
Follow these steps to be able to query randomuser data with GraphQL in your Gatsby project.
1) Create nodes in gatsby-node.js
In your root project folder, add this code to gatsby-node.js:
const axios = require('axios');
const crypto = require('crypto');
exports.sourceNodes = async ({ actions }) => {
const { createNode } = actions;
// fetch raw data from the randomuser api
const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
const res = await fetchRandomUser();
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last,
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail,
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto
.createHash(`md5`)
.update(JSON.stringify(userNode))
.digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
return;
}
I used axios to fetch data so you will need to install it: npm install --save axios
Explanation:
The goal is to create each node for each piece of data you want to use.
According to the createNode documentation, you have to provide an object with few required fields (id, parent, internal, children).
Once you get the results data from the randomuser API, you just need to create this node object and pass it to the createNode() function.
Here we map to the results as you wanted to get 500 random users https://randomuser.me/api/?results=500.
Create the userNode object with the required and wanted fields.
You can add more fields depending on what data you will want to use in your app.
Just create the node with the createNode() function of the Gatsby API.
2) Query your data with GraphQL
Once you did that, run gatsby develop and go to http://localhost:8000/___graphql.
You can play with GraphQL to create your perfect query. As we named the internal.type of our node object 'RandomUser', we can query allRandomUser to get our data.
{
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
3) Use this query in your Gatsby page
In your page, for instance src/pages/index.js, use the query and display your data:
import React from 'react'
import Link from 'gatsby-link'
const IndexPage = (props) => {
const users = props.data.allRandomUser.edges;
return (
<div>
{users.map((user, i) => {
const userData = user.node;
return (
<div key={i}>
<p>Name: {userData.name.first}</p>
<img src={userData.picture.medium} />
</div>
)
})}
</div>
);
};
export default IndexPage
export const query = graphql`
query RandomUserQuery {
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
`;
That is it!
Many thanks, this is working fine for me, I only change small parts of the gastbyjs-node.js because it makes an error when use sync & await, I think I need change some section of a build process to use babel to allow me to use sync or await.
Here is the code which works for me.
const axios = require('axios');
const crypto = require('crypto');
// exports.sourceNodes = async ({ boundActionCreators }) => {
exports.sourceNodes = ({boundActionCreators}) => {
const {createNode} = boundActionCreators;
return new Promise((resolve, reject) => {
// fetch raw data from the randomuser api
// const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
// const res = await fetchRandomUser();
axios.get(`https://randomuser.me/api/?results=500`).then(res => {
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto.createHash(`md5`).update(JSON.stringify(userNode)).digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
resolve();
});
});
}
The accepted answer for this works great, just to note that there's a deprecation warning if you use boundActionCreators. This has to be renamed to actions to avoid this warning.
You can get data at the frontend from APIs using react useEffect. It works perfectly and you will no longer see any error at builtime
const [starsCount, setStarsCount] = useState(0)
useEffect(() => {
// get data from GitHub api
fetch(`https://api.github.com/repos/gatsbyjs/gatsby`)
.then(response => response.json()) // parse JSON from request
.then(resultData => {
setStarsCount(resultData.stargazers_count)
}) // set data for the number of stars
}, [])
The answers given above work, except the query in step 2 seems to only return one node for me. I can return all nodes by adding totalCount as a sibling of edges. I.e.
{
allRandomUser {
totalCount
edges {
node {
id
gender
name {
first
last
}
}
}
}
}

How to check if a pariticular fileExists in reactjs

I am developing an app in React js, I'm having an issue to check whether a particular file exists in the directory or not.
Actually I have a header component i.e Header.js and its a common header. But for some clients I have to change the header according to their requirements. I've to do this by making a folder with client's id and then store new header component for that client in that directory. Now I've to check on run time if a header for a specific client exists then show that client's specific header else the common header. I also have to make some other client specific components i.e footer, aside or section etc. for some specific specific clients according to their requirements. But I'm unable to check in react whether a specific component/file exists or not??
You can try to require your file and then depending on the result display the correct component.
const tryRequire = (path) => {
try {
return require(`${path}`);
} catch (err) {
return null;
}
};
Then to use it :
render() {
const Header = tryRequire('yourPath') ? tryRequire('yourPath').default
: DefaultHeader;
return (
<Header />
);
}
There is another way using React.lazy but to do so you will need to create a component that is located at to root of your project (if you are using Create React App it will be placed at ./src/DynamicImport.js).
Here's the logic:
import React, { Suspense, useState, useEffect, lazy } from 'react';
const importCompo = (f, defaultComponentPath) =>
lazy(() =>
import(`./${f}`).catch((err) => {
// Simulate behaviour in Strapi
// Lazy only support default export so there's a trick to do here
when using a library that does not have a default export
// The example here uses the strapi-helper-plugin package
if (defaultComponentPath === 'strapi-helper-plugin') {
return import('strapi-helper-plugin').then((module) => {
const { Button } = module;
return {
// Here's the trick
// I am creating a new component here
default: () => <Button primary>Something</Button>,
};
});
}
return import(`${defaultComponentPath}`);
}),
);
const DynamicImport = ({ filePath, defaultComponentPath, ...rest }) => {
const [module, setModule] = useState(null);
useEffect(() => {
const loadCompo = () => {
const Compo = importCompo(filePath, defaultComponentPath);
setModule(<Compo {...rest} />);
};
loadCompo();
}, []);
return <Suspense fallback="loading">{module}</Suspense>;
};
DynamicImport.defaultProps = {
// defaultComponentPath: './components/DefaultCompo',
defaultComponentPath: 'strapi-helper-plugin',
};
export default DynamicImport;
Then to use it:
const MyCompo = props => {
return (
<DynamicImport
filePath="./components/Foo"
defaultComponentPath="./components/DefaultCompo"
/>
);
};

Resources