I created a react app with many nested routes.
One of my nested route is using a backend api that returns a complete HTML content.
And I need to display that exact content with same HTML and styling in my UI.
I'm able to successfully achieve it by manipulating the DOM according to axios response using createElement and appendChild inside useEffect method.
But, the whole philosophy behind using react is, to NOT modify the DOM and let react work on it by simly updating the states or props.
My question is:
Is there a cleaner way to use api returned HTML in a react app?
Here is sample relevant code:
Item.js
...
...
useEffect( ()=>{
const fetchAndUpdateItemContent = async () => {
try {
const response = await axios.get(url);
var contentDiv = document.createElement("div");
contentDiv.innerHTML = response.data.markup;
document.getElementById(‘itemContent’)
.appendChild(contentDiv);
} catch (err) {
.....
console.error(err);
......
}
}
};
fetchAndUpdateItemContent();
},[itemId])
return (
<div id=‘itemContent'/>
);
}
What did NOT work
Ideally I should be able to have a state as itemContent in Item.js and be able to update it based upon server response like this. But when I do something like below, whole HTML markup is displayed instead of just the displayable content.
const [itemContent, setItemContent] = useState(‘Loading ...');
...
useEffect( ()=>{
const fetchAndUpdateItemContent = async () => {
try {
const response = await axios.get(url);
setItemContent(response.data.markup)
} catch (err) {
.....
console.error(err);
......
}
}
};
fetchAndUpdateItemContent();
},[itemId])
return (
<div id=‘itemContent'>
{itemContent}
</div>
You're actually trying to convert an HTML string to a JSX. You can assign it into react component props called dangerouslySetInnerHTML
Eg:
const Item = () => {
const yourHtmlStringResponse = '<h1>Heading 1</h1><h2>Heading 2</h2>'
return <div dangerouslySetInnerHTML={{__html: yourHtmlStringResponse}}></div>
}
You can try it here dangerouslySetInnerHTML-Codesandbox
I believe you can use dangerouslySetInnerHTML
Related
I want to go back to the previous page when Apollo Client error.graphQLErrors has an error with a specific message from the back-end server,
Below is the snippet of my code.
const Detail = () => { const { snackbar } = useSnackbar();
const history = useHistory();
return(
<Compo query={graphQLQuery}>
{({ data, error, }) => {
if(error?.graphQLErrors[0]?.extensions?.debugMessage.includes('Specific Error')){
history.goBack();
snackbar('Specific Error');
return <></>;
}
else{
//render another component
}
}
}
</Compo>);
Issue is since the render is called twice, when the error happens, history.goBack() is executed twice and I'm taken two pages back.
I'm able to avoid this by removing <React.StrictMode> encapsulating <App> component.
Is there a better way to do this?
I'm trying to avoid removing <React.StrictMode> since it's been there since a long time.
Issue
The issue here is that you are issuing an unintentional side-effect from the render method. In React function components the entire function body is considered to be the "render" method. Move all side-effects into a useEffect hook.
Solution
Since the code is using a children function prop you'll need to abstract what the "child" is rendering into a React component that can use React hooks.
Example:
const DetailChild = ({ data, error }) => {
const history = useHistory();
const { snackbar } = useSnackbar();
const isErrorCondition = error?.graphQLErrors[0]?.extensions?.debugMessage.includes('Specific Error'
useEffect(() => {
if (isErrorCondition)) {
history.goBack();
snackbar('Specific Error');
}
}, [error]);
return isErrorCondition
? null
: (
... render another component ...
);
};
...
const Detail = () => {
return (
<Compo query={graphQLQuery}>
{({ data, error }) => <DetailChild {...{ data, error }} />}
</Compo>
);
};
Hello and thanks for the help in advance.
I'm trying to get my Nuxt app to automatically loop through my Woocommerce API automatically so it can generate the pages without much work.
How do I get the loop to function. Right now, I'm having issues and get a Nuxt Fatal Error:
TypeError: Cannot read property 'forEach' of undefined
Screenshot of Error + Code
I'm using Woocommerce API and, as you can see in the screenshot above, the Woocommerce code is imported into this code I need help with using a standard import.
import WooCommerce from './woocommerce.js';
generate: {
routes() {
WooCommerce.get("products").then((response) => {
let totalPages = response.headers['x-wp-totalpages'];
let page = 1;
while(page <= totalPages) {
WooCommerce.get("products", page).then((response) => {
response.data.map(product => {
return '/product/' + product.slug
});
})
page++;
}
})
}
},
You are not returning any routes in your routes function. Because of that, nuxt fails as it tries to iterate over them in a later step.
Assuming your way of accessing your API is correct, you would only need to add an array to which you push your routes and then return it.
I'm usually using async/await, which is why my code looks slightly different. It is a bit easier in this case I think.
// Declare the routes function asynchronous
async routes() {
const productsResponse = await WooCommerce.get('products');
const totalPages = productsResponse.headers['x-wp-totalpages'];
// Add an array to collect your routes
const routes = [];
let page = 1;
while (page <= totalPages) {
const pagesResponse = await WooCommerce.get('products', page);
// The 'map' function returns the routes for this set of pages
const productRoutes = pagesResponse.data.map((product) => {
return '/product/' + product.slug;
});
// Push your routes to the created array-
routes.push(...productRoutes);
page++;
}
// Return your routes
return routes;
};
The Apify Puppeteer Scraper does not expose jquery in the context object. I need to access an external JSON data source within the Puppeteer Scraper pageFunction and then loop over one of the nodes. Here is what I would do if jquery was available:
$.get(urlAPI, function(data) {
$.each(data.feed.entry, function(index, value) {
var url = value.URL;
As the handlePageFunction runs in node js context, there is no jQuery. You can easily include jQuery into page.evaluate function using Apify SDK.
async function pageFunction(context) {
const { page, request, log, Apify } = context;
await Apify.utils.puppeteer.injectJQuery(page);
const title = await page.evaluate(() => {
// There is jQuery include as we incleded it using injectJQuery method
return $('title').text()
});
return {
title,
}
}
EDIT: Using requestAsBrowser.
async function pageFunction(context) {
const { page, request, log, Apify } = context;
const response = await Apify.utils.requestAsBrowser({ url: "http://example.com" });
const data = JSON.parse(response.body);
return {
data,
}
}
You don't need JQuery (you can if you are familiar with it) to access an external resource.
Usually, we extract external data via common libraries like request or Apify's own httpRequest from a standalone actor. Unfortunately, Puppeteer Scraper doesn't allow usage of libraries (only dynamically downloaded which is probably overkill).
I would just use a modern fetch browser call. It is nicer than JQuery's AJAX and doesn't require inject.
async function pageFunction(context) {
const { page, request, log, Apify } = context;
const json = await page.evaluate(() => {
// There is jQuery include as we incleded it using injectJQuery method
return await fetch('http://my-json-url.com').then((resp) => resp.json())
});
// Process the JSON
}
I have a list of components coming from the server with JSON which is inside a variable.
I want to call the component dynamically.
listOfcomponents.map(x => {
<Route path={x.slug} component={x.component} />;//(SomeComponent)
});
this is the component
export const SomeComponent = (props) => {
return <div>Some Component</div>
}
The server is deciding about the component that will be called.
How do I let Route have a dynamic component?
Thanks
This is possible! What you're looking for is called: Dynamic imports. And it rocks!
I've had a similar case where I needed to render components dynamically. However the biggest challenge was to keep my routes organized. This is how I implemented this logic:
async function getComponent(route) {
const {default: module} = await import(`../${route}`)
const element = document.createElement('div')
element.innerHTML = module.render()
return element
}
Im trying to display data that has been fetched. but i cannot seem to display nested objects properties in react. Any ideas? if i log the data i first get a undefined, then the correct data.
my guess is that i need to wait for the data to be loaded then display it. but it does work for the title that is not in a nested obj.
function SingleBeneficiary({ match }) {
const [data, setData] = useState({ data: []});
const id = match.params.id
useEffect(() => {
async function fetchData() {
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
setData(jsonData)
}
fetchData();
}, [])
return (
{data.title} // works
{data.address.careOf} // dont work
The data
{
"title":"myTitle",
"address":{
"careOf": "my adress"
}
}
Can you try like this?
I set initial data to null, and in return I check if it is not null.
If address can be null, additional null check is required.
function SingleBeneficiary({ match }) {
const [data, setData] = useState(null);
const id = match.params.id
useEffect(() => {
async function fetchData() {
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
setData(jsonData)
}
fetchData();
}, [])
return (
<div>
{data && (
<div>
<p>data.title</p>
<p>data.address.careOf</p>
</div>
)}
</div>
);
}
You should check if address has careOf property before using it because first time data will be undefined and in second render it will have the data after the api call.
{data.address && data.address.careOf}
For anyone who is having a similar issue(i.e. fetching data via api and only the first time it runs, it will show the data as undefined but after manual refreshing, it works fine), here is a quick and sketchy addition you might consider alongside with 1. "Inline If with Logical && Operator" method and 2. using useState for checking if the api loading is over. With those three, mine worked.
Try fetching the desired data in the previous page of your app; in this case, add the following lines in any page you'll see before "SingleBeneficiary".
const response = await fetch(`http://localhost:8081/v1/beneficiary/${id}`);
const jsonData = await response.json()
Maybe it has to do with npm cache, but not really sure what's going on.
replace
return (
{data.title}
{data.address.careOf}
)
with
return (
{data?.title}
{data?.address?.careOf}
)