Render a react component on a wordpress page - wordpress

We currently have our website built with Wordpress and I have built a component in React that we want to conditionally render on the page.
I have the component built and bundled with Webpack for production but cannot figure out how to add it to our website and render the component on the page
edit I will also need to conditionally render the component (form submit, button click, etc.)
Has anyone successfully done this?

Since you've built and bundled your component already, you only need to do three things:
Add the bundled JavaScript (the Webpack output) as a script tag in whatever page you want the component to be on (preferably at the bottom of the <body>).
Example: <script src="wp-content/react/bundle.js"></script>
Add the root element that React will bind to to the same page.
Example: <div id="react-root"></div>
In your component file, render the component to the root element.
Example: ReactDOM.render(<Component />, document.getElementById('react-root'));
To dynamically show and hide the React component based on something that happens outside the component file, you need to create a way to connect the outside (the DOM) to the inside (React). The easiest way to do this is with a global variable attached to window.
In your React component, add the componentWillMount method that defines a global variable:
componentWillMount() {
window.showComponent = (option) => {
// "option" should be true or false
this.setState({ display: option });
}
}
Based on the value passed to setState above, you'll need to add the display property to your component's state:
constructor(props) {
super(props);
this.state = {
display: false
};
}
Now to make the component show or hide based on the value of this.state.display inside the render() method:
render() {
if (this.state.display) {
return (
...
)
} else {
return null;
}
}
All that's left to do is use showComponent(true) or showComponent(false) in your code that handles the form.

In your React project add the file to the global scope (window) like this:
window.myReactComponents = {
myFirstComponent: () => <MyFirstComponent/>
}
Once this is bundled reference the bundlejs file on your non-react page.
In your non react page in the global scope reference ReactDOM.render with the root element which it should use to render like this:
window.useComponent = {
renderMyFirstComponent : ReactDOM.render(
window.myReactComponents.myFirstComponent(),
document.getElementById('myReactElement')
)
};
That's it!
Live example
ReactDOM.render Documentation

Related

Why is a route dynamically rendered when it contains useSearchParams in Next 13?

I noticed that when a route contains at least one child client component that uses useSearchParams hook, the route becomes dynamically rendered.
In the docs, it states that a component is dynamically rendered only if it uses 1) dynamic functions or 2) fetch is made with {cache: "no-store"}. It also states that the dynamic functions in Next 13 are cookies and headers.
useSearchParams alone doesn't fulfil either criterion, so why would its containing route still be dynamically rendered?
Example:
// ClientComponent.tsx
"use client"
import { useSearchParams } from "next/navigation";
export default function ClientComponent() {
const searchParams = useSearchParams();
return <div>ClientComponent</div>;
}
// page.tsx
import ClientComponent from "./ClientComponent";
export default function Page() {
return (
<div>
<ClientComponent />
</div>
);
}

Passing data to props after asynchronous call in Vue

I have set up a bare bones vue project to show the problem. The only thing I added was the axios package. The problem is when I try to set the property of child component after an asynchronous call I cant read that property in the component. If you look at the code you can see I console log several times to show when I can get the data and when I cant. Please help me figure out what im missing here.
Parent
<template>
<div id="app">
<HelloWorld :test_prop="testData" :test_prop2="testData2" :test_prop3="testData3" test_prop4="I work also"/>
<div>{{testData5}}</div>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import axios from 'axios';
export default {
name: 'app',
components: {
HelloWorld
},
data() {
return {
testData: '',
testData2: 'I work just fine',
testData3: '',
testData5: ''
}
},
created: function(){
var self = this;
this.testDate3 = 'I dont work';
axios.get('https://jsonplaceholder.typicode.com/posts/42').then(function(response){
//I need this one to work
self.testData = 'I dont work either';
self.testData5 = 'I work also';
});
}
}
</script>
Child
<template>
</template>
<script>
export default {
name: 'HelloWorld',
props: ['test_prop', 'test_prop2', 'test_prop3', 'test_prop4'],
data() {
return {
comp_data: this.test_prop,
comp_data2: this.test_prop2,
comp_data3: this.test_prop3,
comp_data4: this.test_prop4
}
},
created: function(){
console.log(this.test_prop);
console.log(this.test_prop2);
console.log(this.test_prop3);
console.log(this.test_prop4);
}
}
</script>
Your console.log inside created hook will show you the initial state of this variables in Parent component. That's because Parent's created hook and Child's created hook will run at the same time.
So, when you solve your promise, Child component was already created. To understand this behavior, put your props in your template using {{ this.test_prop }}.
To solve it, depending on what you want, you can either define some default value to your props (see) or render your child component with a v-if condition. That's it, hope it helps!
On Vue created hook only the initial values of properties passed from main component. Therefore later updates (like your example "after ajax call") in main component will not effect to child component data variables because of that already child created hook take place.
If you want to update data later one way you can do like this:
watch: {
test_prop: function(newOne){
this.comp_data = newOne;
}
}
Adding watcher to property changes will update the last value of property from main component.
And also edit the typo this.testDate3. I guess it must be this.testData3

Implementing Theming in React Application

I got a requirement from my client that we need to have custom themes in our react application.
Where user can select one of the available themes of his choice which will change some css attribute like font-color, font-family etc.
Right now I apply css rules to my application using className attribute.
I tried searching some of the ways of implementing the same and found one way that is having alternate css
<link rel="stylesheet" href="main.css">
<link rel="stylesheet alternate" href="light.css" id="light" title="Light">
<link rel="stylesheet alternate" href="dark.css" id="dark" title="Dark">
<script>
function enableStylesheet (node) {
node.rel = 'stylesheet';
}
function disableStylesheet (node) {
node.rel = 'alternate stylesheet';
}
</script>
And toggle rel value based on user selection.
Can any one suggest me any other way of achieving the same.
Thanks in advance.
Your usage is the correct way of implementing. But what if you've numerous theme? Loading all the css files will hamper the website. So, I would suggest you to update the source instead of loading all css files and enabling/disabling them.
node.ref = 'dark.css';
This way, you're requiring the css file only when in use. Once, the file is being used, next time it's cached. So no need to worry about them later. Obviously, it might take some time and may impact on performance if the filesize is huge, at initial use. Even though the best thing is that you don't need to wait for all.
But wait!!!
You're using react. React provides us context api and I would obviously utilize this in such scenario. Here's an excerpted example:
import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
// State also contains the updater function so it will
// be passed down into the context provider
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// The entire state is passed to the provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}
ReactDOM.render(<App />, document.root);
Similar concept can be implemented using redux and with logger information to make our work easy.

VueJs child component props not updating instantly

I have a parent/child component setup where the parent is loading data from the server and passing it down to children via props. In the child I would like to instantiate a jQuery calendar with some of the data it receives from the parent.
In order to wait for the data before setting up the calendar, I broadcast an event in the parent that I have an event listener setup for in the child.
The listener is being fired in the child but if I this.$log('theProp'), it's undefined. However, if I inspect the components with the VueJs devtools, the parent/child relationship is there and the child has received the prop in the meantime.
The prop is defined on the child as a dynamic prop :the-prop="theProp". Since the child does receive the prop in the end, I'm assuming my setup is correct but there seems to be some sort of delay. The parent sets the props in the return function of the ajax call and again: it's working, just with a slight delay it seems.
I also tried registering a watch listener on the prop in the child so I could setup the calendar then and be sure that the prop is there. However, the watch listener fires, but this.$log('theProp') is still undefined.
If I pass the data along with the the broadcast call, like this.$broadcast('dataLoaded', theData) the child receives it just fine. But it seems wrong to do it that way as I'm basically building my own prop handler.
I'm not posting any code because the components are rather large and the VueJs devtools are telling me the parent/child situation is working.
Am I missing some information? Is there a slight delay between setting a value in the parent and the child receiving it? What would be the proper way to wait for parent data in the child?
Normally, when you're just rendering the data out into the template, the timing doesn't matter so much since the data is bound to the template. But in this case, I really need the data to be there to setup the calendar or it will be wrong.
Thanks.
edit 1: here's a jsfiddle: https://jsfiddle.net/dr3djo0u/1/
It seems to confirm that the data is not available immediately after the broadcast. However, the watcher does work, though I could almost swear that sometimes this.$log('someData') returned undefined when I setup that testcase.
But I guess my problem might be somewhere else, I'll have a look tonight, don't have the project with me right now.
edit 2: did some more tests. My problem was that a) event listeners do not seem to receive the data instantly and b) I was also trying to init the calendar in the route.data callback if someData was already around (e.g. when coming from parent), but that route callback is called before the component is ready, so it wasn't working there either.
My solution is now this:
// works when the child route is loaded directly and parent finishes loading someData
watch: {
someData() {
this.initCalendar();
}
},
// works when navigating from parent (data already loaded)
ready() {
if (this.someData && this.someData.length) {
this.initCalendar()
}
}
As far as I know, you should not need events to pass data from parent to child.
All you need is, in the child component: props: ['theProp']
And when using the child component in the parent: <child :theProp="someData"></child>
Now, wherever in the parent you change someData, the child component will react accordingly.
You don't need events, you don't need "watch", you don't need "ready".
For example: after an AJAX call, in the parent's "ready", you load some data:
// at the parent component
data: function () {
return {
someData: {}
}
},
ready: function () {
var vm = this;
$.get(url, function(response) {
vm.someData = response;
});
}
Now, you do not need anything else to pass the data to the child. It is already in the child as theProp!
What you really need to do is to have, in the child, something which reacts to data changes on its own theProp property.
Either in the interface:
<div v-if="theProp.id > 0">
Loaded!
</div>
Or in JavaScript code:
// at the child component
computed: {
// using a computed property based on theProp's value
awesomeDate: function() {
if (!this.theProp || (this.theProp.length === 0)) {
return false;
}
if (!this.initialized) {
this.initCalendar();
}
return this.theProp.someThing;
}
}
Update 1
You can also, in the parent, render the child conditionally:
<child v-if="dataLoaded" :theProp="someData"></child>
Only set dataLoaded to true when the data is available.
Update 2
Or maybe your issue is related to a change detection caveat
Maybe you're creating a new property in an object...
vm.someObject.someProperty = someValue
...when you should do...
vm.$set('someObject.someProperty', someValue)
...among other "caveats".
Update 3
In VueJS 2 you are not restricted to templates. You can use a render function and code the most complex rendering logic you want.
Update 4 (regarding OP's edit 2)
Maybe you can drop ready and use immediate option, so your initialization is in a single place:
watch: {
someData: {
handler: function (someData) {
// check someData and eventually call
this.initCalendar();
},
immediate: true
}
}
It's because tricky behavior in Vue Parent and Child lifecycle hooks.
Usually parent component fire created() hook and then mount() hook, but when there are child components it's not exactly that way: Parent fires created() and then his childs fire created(), then mount() and only after child's mount() hooks are loaded, parent loads his mount() as explained here. And that's why the prop in child component isn't loaded.
Use mounted() hook instead created()
like that https://jsfiddle.net/stanimirsp5/xnwcvL59/1/
Vue 3
Ok so I've spent like 1.5h trying to find out how to pass prop from parent to child:
Child
<!-- Template -->
<template>
<input type="hidden" name="_csrf_token" :value="csrfToken">
<span>
{{ csrfToken }}
</span>
</template>
<!-- Script -->
<script>
export default {
props: [
"csrfToken"
]
}
</script>
Parent
<!-- Template -->
<template>
<form #submit.prevent="submitTestMailForm" v-bind:action="formActionUrl" ref="form" method="POST">
...
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
...
</form>
</template>
<!-- Script -->
<script>
...
export default {
data(){
return {
...
csrfToken : "",
}
},
methods: {
/**
* #description will handle submission of the form
*/
submitTestMailForm(){
let csrfRequestPromise = this.getCsrfToken();
let ajaxFormData = {
receiver : this.emailInput,
messageTitle : this.titleInput,
messageBody : this.bodyTextArea,
_csrf_token : this.csrfToken,
};
csrfRequestPromise.then( (response) => {
let csrfTokenResponseDto = CsrfTokenResponseDto.fromAxiosResponse(response);
this.csrfToken = csrfTokenResponseDto.csrToken;
this.axios({
method : "POST",
url : SymfonyRoutes.SEND_TEST_MAIL,
data : ajaxFormData,
}).then( (response) => {
// handle with some popover
})
});
},
/**
* #description will return the csrf token which is required upon submitting the form (Internal Symfony Validation Logic)
*/
getCsrfToken(){
...
return promise;
}
},
components: {
CsrfTokenInputComponent
}
}
</script>
Long story short
This is how You need to pass down the prop to child
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
NOT like this
<CsrfTokenInputComponent csrf-token="csrfToken"/>
Even if my IDE keep me telling me yeap i can navigate with that prop to child - vue could not bind it.
solution (testing ok)
In child component just using the props data, no need to re-assignment props's values to data, it will be cause update bug!
vue child component props update bug & solution
https://forum.vuejs.org/t/child-component-is-not-updated-when-parent-component-model-changes/18283?u=xgqfrms
The problem is not how to pass data with props, but rather how to do two things at almost the same time.
I have an user account component that can edit users (with an user id) and add users (without id).
A child component shows checkboxes for user<->company assignments, and needs the user id to prepare API calls when the user account is saved.
It is important that the child component shows before saving the user account, so that things can be selected before the user is saved and gets an id.
So it has no user id at first: the id is passed to the child component as 'null'.
It updates when the user is stored and gets an id.
But at this point, it takes a very short time for the child to get the new id into its model.
If you call a function in the child component that relies on data that was just changing, it might happen that the function executes before the data is updated.
For cases like this, nextTick() is your friend.
import { nextTick } from 'vue';
...
saveAccount() {
axios.post(URL, this.userModel).then((result)) {
// our model gets an id when persisted
this.userModel.id=result.data.id;
nextTick( () => {
this.$refs.childComponent.doSomething();
});
}
}

Initializing react component from asp.net

Hopefully this is a slam-dunk for someone out there...my essential problem is this: I've built up a very nice set of react components which i can render in my asp.net 4.5 mvc 6 application using react.js, flux, gulp, and browserify.
as long as i have it structured so that the react components have all the data they need everything is perfect. My issue now is that I would like to have an MVC view include the react stuff, and inject run-time properties into the top-level component as it is created. Since I'm brpowserify-ing all of my react code into a bundle, i just include the one script tag in my view:
<script src="/js/modules/AuthContainer.jsx"></script>
But whereas I would normally use JSX syntax to instantiate my component with props like this:
...the view in ASP.NET never gets translated to pure JS, so that fails.
I've also tried:
ReactDOM.render
(
React.createElement(AuthContainer, { successPath: '/home' }),
document.getElementById('reactRoot')
);
...from inside a script block in my view but i get:
Uncaught ReferenceError: AuthContainer is not defined
But i'm sure i'm exposing 'AuthContainer' via the browserify-ed bundle, so i don't understand why it's unable to resolve that component.
I know there's a React.NET way to do this, but i can't get that server-side rendering to work with my components because I'm using jQuery to fetch data in componentDidMount and the server-side rendering is choking looking for $() jQuery stuff.
I'd love to get the server side rendering going but right now i just need it to do work, one way of the other. Can someone provide a simple code snippet or gist of how to instantiate a React component from inside a cshtml file with run-time props?
One easy solution is this, just put your server side properties with Javascript in a global:
index.cshtml
<script>
var __config__ = {
base: "#MyBackEdnVariable",
initialCount: "#Count",
user: {
id: #user.id,
name: #user.name,
}
};
</script>
<script src="/js/modules/AuthContainer.jsx"></script>
And with React use that global variable:
AuthContainer.js
class AuthContainer extends React.Component {
render() {
return (
<div>{this.props.user.name}</div>
);
}
}
AuthContainer.defaultProps = {
initialCount: __config__.initialCount,
user: __config__.user
};
For posterity:
ReactDOM.render
(
React.createElement
(
MyComponent,
{
prop1: #numericValue,
prop2: '#textValue',
}
),
document.getElementById('reactRoot')
);
the magic was the jsx-alternative syntax, which i was aware of couldn't get a handle on that day. This allows you to instantiate react using pure JS and therefor just embed inside a simple script tag in your cshtml.
hth.

Resources