Can home_url() be dynamically passed to a theme built with React? - wordpress

Testing out and building a WordPress theme in React I've typically seen the URL hard coded in a componentDidMount like:
class Home extends React.Component{
componentDidMount(){
const API_URL = 'https://foobar/wp-json/wp/v2/posts/';
this.serverRequest = $.get(API_URL, function (result) {
this.setState({
result:result,
loaded:true
});
}.bind(this));
}
}
export default Home;
but I was curious to know if there was a way in the functions.php -> Home.js I can pass home_url, in the example https://foobar, without having to manually modify Home.js?
Per querying the site with the paramters [wordpress][reactjs] I've been unable to find if this is asked. Can this be done or a way to pass the wp-json/wp/v2 to React dynamically?
Edit:
There appears to be some confusion in what I'm trying to do. If you were to use Axios with React instead of fetch, example from Using Axios with React, you'd have to use a GET to pull the WP_JSON from WordPress. The example link uses:
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/users`)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
}
the URL, https://jsonplaceholder.typicode.com/users, is static and every time I were to load this theme to a new site, the URL will need to be changed. How can I change the present URL to match the theme's hosted URL dynamically so that if the domain were to change from:
https://jsonplaceholder.typicode.com/users
to
https://jsonwinners.typicode.com/users
I wouldn't have to do it manually.

Judging from what you're trying to achieve, i would rather use document.location.origin
however, to answer your question, you CAN get php to send a variable to javascript....
Method 1 (dirty) - custom script tag in the head
add_action('wp_head', function(){
echo '<script>var MY_OBJ.users_url = "'.home_url('/users').'";</script>';
});
and then in your js file...
componentDidMount() {
axios.get(MY_OBJ.users_url)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
}
Method 2 - localized script
wp_enqueue_script( 'home-js', get_theme_file_uri('/assets/js/Home.js'), array(), '1.0', true );
$my_php_array = array(
'home_url' => home_url('')
'users_url' => home_url('/users')
);
wp_localize_script( 'home-js', 'MY_OBJ', $my_php_array );
and then in your js file...
componentDidMount() {
axios.get(MY_OBJ.users_url)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
}

Related

React Query - useQuery callback dependent on route parameter? [duplicate]

When page is refreshed query is lost, disappears from react-query-devtools.
Before Next.js, I was using a react and react-router where I would pull a parameter from the router like this:
const { id } = useParams();
It worked then. With the help of the, Next.js Routing documentation
I have replaced useParams with:
import { usePZDetailData } from "../../hooks/usePZData";
import { useRouter } from "next/router";
const PZDetail = () => {
const router = useRouter();
const { id } = router.query;
const { } = usePZDetailData(id);
return <></>;
};
export default PZDetail;
Does not work on refresh. I found a similar topic, but manually using 'refetch' from react-query in useEffects doesn't seem like a good solution. How to do it then?
Edit
Referring to the comment, I am enclosing the rest of the code, the react-query hook. Together with the one already placed above, it forms a whole.
const fetchPZDetailData = (id) => {
return axiosInstance.get(`documents/pzs/${id}`);
};
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {});
};
Edit 2
I attach PZList page code with <Link> implementation
import Link from "next/link";
import React from "react";
import TableModel from "../../components/TableModel";
import { usePZSData } from "../../hooks/usePZData";
import { createColumnHelper } from "#tanstack/react-table";
type PZProps = {
id: number;
title: string;
entry_into_storage_date: string;
};
const index = () => {
const { data: PZS, isLoading } = usePZSData();
const columnHelper = createColumnHelper<PZProps>();
const columns = [
columnHelper.accessor("title", {
cell: (info) => (
<span>
<Link
href={`/pzs/${info.row.original.id}`}
>{`Dokument ${info.row.original.id}`}</Link>
</span>
),
header: "Tytuł",
}),
columnHelper.accessor("entry_into_storage_date", {
header: "Data wprowadzenia na stan ",
}),
];
return (
<div>
{isLoading ? (
"loading "
) : (
<TableModel data={PZS?.data} columns={columns} />
)}
</div>
);
};
export default index;
What you're experiencing is due to the Next.js' Automatic Static Optimization.
If getServerSideProps or getInitialProps is present in a page, Next.js
will switch to render the page on-demand, per-request (meaning
Server-Side Rendering).
If the above is not the case, Next.js will statically optimize your
page automatically by prerendering the page to static HTML.
During prerendering, the router's query object will be empty since we
do not have query information to provide during this phase. After
hydration, Next.js will trigger an update to your application to
provide the route parameters in the query object.
Since your page doesn't have getServerSideProps or getInitialProps, Next.js statically optimizes it automatically by prerendering it to static HTML. During this process the query string is an empty object, meaning in the first render router.query.id will be undefined. The query string value is only updated after hydration, triggering another render.
In your case, you can work around this by disabling the query if id is undefined. You can do so by passing the enabled option to the useQuery call.
export const usePZDetailData = (id) => {
return useQuery(["pzs", id], () => fetchPZDetailData(id), {
enabled: id
});
};
This will prevent making the request to the API if id is not defined during first render, and will make the request once its value is known after hydration.

Display the user-disconnected page in NextJs

I'm looking for a way to show to user, like WhatsApp web, a page that when connection is poor or user has no internet, says "Your internet connection is down".
How to do this in Next Js ?
You have to add this custom hook to your _app.js file :
function useNetwork(){
const [isOnline, setNetwork] = useState(window.navigator.onLine);
useEffect(() => {
window.addEventListener("offline",
() => setNetwork(window.navigator.onLine)
);
window.addEventListener("online",
() => setNetwork(window.navigator.onLine)
);
});
return isOnline;
};
Add your logic :
const isOnline = useNetwork();
////////////////////////
useEffect(()=>{
if(!isOnline){
// show your going offline message here
}
},[isOnline])

Caching into subcomponents

I'm building a site in nextjs but I came across a problem.
I have the cover of the site, where there is a list of products, and on the top menu the list of product categories.
The products are looking via getStaticProps (So that it is done by the servideor and is cached).
However, the categories are inside a separate component, where inside I need to load the category listing from my API.
getStaticProps does not work in this case as it is not a page but a component.
Fetching inside a useEffect is bad, as each access loads the api.
So the question remains, how can I do this server-side fetch and deliver the cached (json) return? (Simulating getStaticProps)
As your component is listed on every page, you could consider either using Context or local caching in the browser within the shared Category component.
Context provides a way to pass data through the component tree without
having to pass props down manually at every level.
But there are performance considerations using Context and may be overkill here. If you really don't want to hit the API, data is not changing often, and you don't need to pass functions through the component tree, then you could consider some basic browser caching using localStorage.
import React, { useState, useEffect } from 'react';
const mockAPI = () => {
return new Promise((resolve) => {
setTimeout(() => {
return resolve([
{
id: '1',
name: 'One'
},
{
id: '2',
name: 'Two'
}
]);
}, 1000);
});
};
const Component = () => {
const [categories, setCategories] = useState(null);
useEffect(() => {
(async () => {
if (categories === null) {
let data = window.localStorage.getItem('categories');
if (data === null) {
console.info('calling api...');
data = await mockAPI();
window.localStorage.setItem('categories', JSON.stringify(data));
}
setCategories(JSON.parse(data));
}
})();
}, []);
return <nav>{categories && categories.map((category) => <li key={category.id}>{category.name}</li>)}</nav>;
};
export default Component;
The caveat here is you need to know where to clear localStorage. There are many ways to implement this from using basic timers to looking at SWR
You could also consider Redux but is probably overkill for something elementary like this.

Change layout according to wordpress page template NuxtJs

I want to get change layout according to wordpress page template (i get data with rest api)
API Data
{
"id": 47,
"template": "test.php", // need vue template according this
}
export default {
validate ({ params }) {
return !isNaN(+params.id)
},
async asyncData ({ params, error }) {
return fetch('http://wordpress.local/wp-json/wp/v2/'+params.postType+'/'+params.id)
.then(response => response.json())
.then(res => {
return { users: res }
})
},
layout () {
return 'blog' // Change here according to wordpress page template
},
}
I found a way to pass something from middleware to store which you can use inside the layout function. Here is the basic example I put together.
middleware/get-layout.js I simulate an async call here, could also be result of axios.post() for example
export default async (ctx) => {
return new Promise((resolve, reject) => {
// you can also access ctx.params here
ctx.store.commit('setLayout', 'new');
resolve();
});
}
store/index.js nothing crazy here
export const state = () => ({
layout: ''
})
export const mutations = {
setLayout(state, layout) {
state.layout = layout;
}
}
Middleware can either be registered globally for every route in nuxt.config.js or only for pages where you need this logic.
Finally using it in page component layout property:
layout(ctx) {
return ctx.store.state.layout;
}
I tested it with new.vue inside layout folder.

UI not update (have to click somewhere) & router not work in Angular2-Meteor

I cannot update the UI immediately after subscribing the data from database, I have to click somewhere
Also if I use the router to go to another page, it does not work
#Component({
selector: 'foo-component',
template: `
{{foo}}
`
})
export class FooComponent extends MeteorComponent {
foo: string ;
constructor() {
super();
this.subscribe('messages', () => {
// I cannot change foo value immediately, I have to click somewhere
this.foo = 'foo';
// Also if I use the router to go to another page, it does not work
// this.router.navigate(['Home']);
});
}
}
How to solve this?
Note the new angular2-meteor version autoBind is set to true by default. So you probably won't meet this issue again.
But you still need use NgZone in Accounts.changePassword or other similar Accounts.foo() functions.
This problem is because that part of code run out of Angular 2 zone, you need run it inside of zone to update UI immediately or use router to go to another page.
Where do these problems usually happen?
Most time you don't do this. So when do you need this? Usually in callback of Meteor functions:
this.autorun(() => {
// here you need
});
this.subscribe('messages', () => {
// here you need
});
this.call('newMessage', (error, result) => {
// here you need
});
Accounts.changePassword(currentPassword, newPassword, error => {
// here you need
});
How to solve?
Take
this.call('newMessage', (error, result) => {
this.router.navigate(['Home']);
});
for example, you need change to:
import { NgZone } from '#angular/core';
constructor(private zone: NgZone) {}
this.call('newMessage', (error, result) => {
this.zone.run(() => {
this.router.navigate(['Home']);
});
});
Is there a clean way?
Luckily, Angular2-Meteor helps you do this dirty work.
Check Angular2-Meteor API document, there is an autoBind parameter for this.subscribe and this.autorun.
So now you don't need use this.zone.run, instead you can just add a true:
this.autorun(() => {
// your code
}, true);
this.subscribe('messages', () => {
// your code
}, true);

Resources