How to pass JavaScript variables to React component - javascript

I'm somewhat new to React and I'm having some trouble passing some variables from my Django server to my React components. Here's what I have:
The server is Django, and I have a url mydomain.com/testview/ that gets mapped to a views.py function testview:
def testview(request):
now = datetime.datetime.now()
return render(request, 'testview.html', {
'foo': '%s' % str(now),
'myVar': 'someString'
})
In other words, running testview will render the template file testview.html and will use the variables foo and myVar.
The file testview.html inherits from base.html which looks like this:
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
</head>
<body>
{% block main %}{% endblock %}
</body>
</html>
The file test.html basically inserts the needed code into block main:
testview.html:
{% extends "base.html" %}
{% load render_bundle from webpack_loader %}
{% block main %}
<script type="text/javascript">
var foo = {{ foo }};
var myVar = {{ myVar }};
</script>
<div id="App"></div>
{% render_bundle 'vendors' %}
{% render_bundle 'App' %}
{% endblock %}
Note that just before the div id="App", I created a couple of javascript variables foo and myVar and set them to the values from Django.
Now to REACT: my file App.jsx looks like this:
import React from "react"
import { render } from "react-dom"
import AppContainer from "./containers/AppContainer"
class App extends React.Component {
render() {
return (
<AppContainer foo={props.foo} myVar={props.myVar}/>
)
}
}
render(<App foo={window.foo} myVar={window.myVar}/>, document.getElementById('App'))
In other words, my App.jsx file renders the App component, passing in foo and myVar. Inside class App, I assumed these were props so I pass these to AppContainer using props.foo and props.myVar. My class AppContainer is inside a components folder and looks like this:
import React from "react"
import Headline from "../components/Headline"
export default class AppContainer extends React.Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<Headline>Running App! foo is {props.foo}, Here is a string: {props.myVar}</Headline>
</div>
</div>
</div>
)
}
}
However, none of this seems to work. I just get a blank page. What am I doing wrong?

if foo and myVar is string you should declare
var foo = "{{ foo }}";
var myVar = "{{ myVar }}";

So this is what I needed to do to get it to work. First, I used Giang Le's answer above and in my testview.html file (a Django template file), I put quotes around the variables as they were indeed strings. Next, I changed the render statement in my App.jsx file to be this:
render(<App foo={foo} myVar={myVar}/>, document.getElementById('App'))
This used Bruno Vodola Martins' answer to access foo and myVar as javascript globals. I also had to use this.props.foo instead of props.foo in my App class:
class App extends React.Component {
render() {
return (
<AppContainer foo={this.props.foo} myVar={this.props.myVar}/>
)
}
}
And I did the same thing in containers/AppContainer.jsx:
export default class AppContainer extends React.Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<Headline>App! foo is {this.props.foo}, Here is a string: {this.props.myVar}</Headline>
</div>
</div>
</div>
)
}
}
Bottom line: put quotes around string variables from Django, and use this.props.foo instead of just props.foo.

Related

How to create and render a React Component after babelify/transpiling?

I have a hello world react component that is written in JSX, transpiled with babel, and then included in the hello.html template of a Flask app. What I have working is creating and rendering the component before transpiling as such:
const hello = <Hello name="world" />;
ReactDOM.render(hello, document.getElementById('hello'));
How can I do those two steps in a <script> tag in my hello.html template? My goal is to be able to pass that name variable from the template to the component and then render it.
A little more context:
The JSX hello.js looks like this:
import React from 'react';
import ReactDOM from 'react-dom'
import { render } from 'react-dom'
class Hello extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>Hello {this.props.name}!!!</div>
)
}
}
//The following works:
//const hello = <Hello name="world" />;
//ReactDOM.render(hello, document.getElementById('hello'));
hello.html looks like this:
<html>
<head>
</head>
<body>
<div>ASDF</div>
<div id="hello"></div>
</body>
{# The following line is a post babelify (transpiled) hello.js #}
<script type="text/javascript" src="{{ url_for('static', filename='js/hello.js') }}"></script>
<script type="text/javascript">
{#
What goes here? The code in the above section does not work.
The transpiled code defines a "var Hello = /*#__PURE__*/ function (_React$Component) { ...".
const hello = Hello(); does not throw an error, but also does not render or pass an argument.
hello.render(); is also something that I have tried, along with arguments for div/id to render in and name.
#}
</script>
</html>
Correction: Calling Hello() does not throw an error if the script is text/babel, in which case the script probably isn't doing anything.
The Flask route looks like this:
#app.route(u'/')
def index():
return render_template(u'hello.html', name="universe")
Two ways you can pass variables from your server application to react component:
Use the html data-variable prop.
Create a global variable. Something like window.variable
Then you should be able to access variable as a props like props.variable in your react-component.
My recommended approach I would take is to use a bundler such as SystemJS (version 2), and you will have something like the following:
<!DOCTYPE html>
<html>
<head>
<script src="node_modules/core-js-bundle/minified.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script type="systemjs-importmap" src="systemjs.imports.json"></script>
<script src="node_modules/systemjs/dist/system.min.js"></script>
<script src="node_modules/systemjs/dist/extras/named-exports.min.js"></script>
<script>
System.import('../.playground/index.js').catch(function (err) { console.error(err); });
</script>
</head>
<body>
<div>ASDF</div>
<div id="hello"></div>
</body>
</html>
And index.js will look something like this
ReactDOM.render(
(< Hello/>),
document.getElementById('app')
);
Then your systemjs-importmap will look like this
{
"imports": {
"react": "../node_modules/react/umd/react.production.min.js",
"react-dom": "../node_modules/react-dom/umd/react-dom.production.min.js",
// ... other named exports you want to add like the Hello component here
}
}

NoReverseMatch from url tag inside include tag

I am trying to render a link inside an include html template with the url tag.
I have done this before and usually it works, but for some reason this time I can't make it.
I get a NoReverseMatch Error and suspect its because Django tries to load the url tag first but my object isn't ready, so the pk is empty. I believe that because it takes a moment until the dynamic data loads, while the static is already loaded.
The url works if I set pk to a fixed number, but I would like it to change dynamically.
Error:
Reverse for 'transaction' with keyword arguments '{'pk': ''}' not found. 1 pattern(s) tried: ['en/budget/account\\/(?P<pk>[0-9]+)\\/$']
Relevant urls:
from django.urls import path
from django.contrib import admin
from django.contrib.auth import views as auth_views
from . import views
app_name='budgetapp'
urlpatterns = [
path('', views.index, name='index'),
path('account/<int:pk>/', views.transaction, name='transaction'),
path('account/', views.account, name='account'),
]
Relevant views:
from django.shortcuts import get_object_or_404, render, redirect
from django.contrib.auth.models import Group
from django.contrib.auth.decorators import login_required, user_passes_test
from .models import *
from .forms import *
def index(request):
context = {}
context['accounts'] = Account.objects.filter(author=request.user)
return render(request, 'budgetapp/index.html', context)
def account(request):
context = {}
context['account'] = get_object_or_404(Account, pk = request.POST['accountPk'])
return render(request, 'budgetapp/account.html', context)
def transaction(request, pk):
context = {}
context['account'] = get_object_or_404(Account, pk = pk)
return render(request, 'budgetapp/addTransaction.html', context)
index.html:
{% csrf_token %}
<h1>Personal Budget</h1>
<br />
<p>
<label for="accountSelector">Account:</label>
<select required = "" id="accountSelector">
{% for account in accounts %}
<option value="{{account.pk}}">{{account}}</option>
{% endfor %}
</select>
</p>
<hr />
{% include 'budgetapp/account.html' %}
<script>
$(document).ready(function () {
reload();
});
$("#accountSelector").change(function () {
reload();
});
function reload() {
var dictionary = {}
dictionary['csrfmiddlewaretoken'] = $('input[name="csrfmiddlewaretoken"]').val();
dictionary['accountPk'] = $('#accountSelector').val();
$('#accountDiv').load("account/", dictionary);
console.log('Changed account');
}
</script>
account.html:
<div id="accountDiv">
<p>
Name: {{account.name}} Account balance: {{account.balance}} Add a transaction
</p>
</div>
If I change {% url 'budgetapp:transaction' pk=account.pk %} to /budget/account/{{account.pk}} it works, but that feels wrong.
I tried to provide all necessary code, but please let me know if it is to much or something is missing.
If you want to use {% url 'budgetapp:transaction' pk=account.pk %} then account must be in the template context. This has nothing to do with your browser dynamically loading data. The entire template is rendered by the server before the response is sent to the browser.
Using /budget/account/{{account.pk}} won't give an error, but if you look at the rendered HTML you'll see /budget/account/ since {{ account.pk }} will evaluate as ''.

How to pass a variable from twig to vueJS (symfony 2.8 and VueJS 2)

I have a symfony 2.8 application and I recently integrated VueJs 2 as my front-end framework, because it gives a lot of flexibility.
My application is not single page and I use the symfony controllers to render views. All the views are wrapped in a base twig layout:
<!DOCTYPE html>
<html lang="{{ app.request.locale|split('_')[0] }}">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
<div id="app">
{% block body %} {% endblock %}
</div>
<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
<script src="/js/fos_js_routes.js"></script>
<script type="text/javascript" src="{{ asset('build/vendor-bundle.js') }}"></script>
<script type="text/javascript" src="{{ asset('build/vue-bundle.js') }}"></script>
</body>
</html>
I load most of the JS with webpack, all my vue components and JS dependencies are compiled in vendor-bundle.js and vue-bundle.js. My VueJs instance looks like this:
import './components-dir/component.vue'
import './components-dir/component2.vue'
Vue.component('component', Component);
Vue.component('component2', Component2);
window.onload = function () {
new Vue({
el: '#app',
components: {}
});
};
I want to pass some php variables from the controller to the vuejs componets, but I can't manage to make it work.
A very simple example of a controller looks like this:
/**
* #Route("/contract", name="contract")
* #Method("GET")
*/
public function indexAction()
{
$paymentMethods = PaymentMethod::getChoices();
return $this->render('contracts/index.html.twig', [
'paymentMethods' => $serializer->normalize($paymentMethods, 'json'),
]);
}
All the html, css and js are handled by vueJs. The twig view looks like this:
{% extends 'vue-base.html.twig' %}
{% block body %}
<contracts :paymentMethods="{{paymentMethods | raw}}"></contracts>
{% endblock %}
The contracts.vue component looks like this:
<template>
<div>
<p>Hi from component</p>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: ['paymentMethods'],
mounted: function () {
console.log(this.paymentMethods)
}
}
</script>
<style>
</style>
How can I pass the php variables as props to vueJs ?
In the example above, I don't get any errors, but the property is not passed to vuejs. The console log prints undefined.
I want to be able to do this, because I don't want to have a SPA, but I also want to pass some variables from symfony to vue, because I won't have to make additional requests.
Instead of passing Twig variable as value of Vue attr:
<contracts :payment-methods="{{ twigVar}}"></contracts>
you can render whole using twig:
<contracts {{ ':payment-methods="' ~ twigVar ~ '"' }}></contracts>
Thanks to this you will avoid delimeters conflict between vue and twig.
Also as the value comes directly from twig, it probably wont change upon a time, as it is generated in backend - not in some vue-source - so you don't need to bind it, just pass it like:
<contracts payment-methods="{{ twigVar}}"></contracts>
The simplest way to pass variables from twig to Vue application is:
Twig:
<div id="app" data-foo="{{ foo }}" data-bar="{{ bar }}"></div>
JS:
import Vue from 'vue'
new Vue({
el: '#app',
data: {
foo: '',
bar: ''
},
template: '<div>foo = {{ foo }}</div><div>bar = {{ bar }}</div>',
beforeMount: function() {
this.foo = this.$el.attributes['data-foo'].value
this.bar = this.$el.attributes['data-bar'].value
}
})
If you would like to use a Vue component you can do it the following way:
Twig:
<div id="app" data-foo="{{ foo }}" data-bar="{{ bar }}"></div>
JS:
import Vue from 'vue'
import App from 'App'
new Vue({
el: '#app',
render(h) {
return h(App, {
props: {
foo: this.$el.attributes['data-foo'].value,
bar: this.$el.attributes['data-bar'].value,
}
})
}
})
App.vue:
<template>
<div>foo = {{ foo }}</div>
<div>bar = {{ bar }}</div>
</template>
<script>
export default {
props: ['foo', 'bar'],
}
</script>
Please note if you would like to pass arrays you should convert them to json format before:
Twig:
<div id="app" data-foo="{{ foo|json_encode }}"></div>
and then you should decode json:
JS:
this.foo = JSON.parse(this.$el.attributes['data-foo'].value)
you need to add the following to your .vue file props: ['paymentMethods'] please refer to the following url for complete documentation https://v2.vuejs.org/v2/guide/components.html#Passing-Data-with-Props
Probably late to the party, but if anyone is having the same issue, the problem here was the casing.
CamelCased props like paymentMethods are converted to hyphen-case in html, and can be used like this:
<contracts :payment-methods="{{ paymentMethods | raw }}"></contracts>

vue.js passing data from parent single file component to child

Using single file architecture I'm trying to pass data (an object) from a parent component to a child:
App.vue
<template>
<div id="app">
<app-header app-content={{app_content}}></app-header>
</div>
</template>
<script>
import appHeader from './components/appHeader'
import {content} from './content/content.js'
export default {
components: {
appHeader
},
data: () => {
return {
app_content: content
}
}
}
</script>
appHeader.vue
<template>
<header id="header">
<h1>{{ app_content }}</h1>
</header>
</template>
<script>
export default {
data: () => {
return {
// nothing
}
},
props: ['app_content'],
created: () => {
console.log(app_content) // undefined
}
}
</script>
Seems to be such a trivial task and probably the solution is quite simple. Thanks for any advice :)
You're almost there.
In order to send the app_content variable from App.vue to the child component you have to pass it as an attribute in the template like so:
<app-header :app-content="app_content"></app-header>
Now, in order to get the :app-component property inside appHeader.vue you will have to rename your prop from app_component to appComponent (this is Vue's convention of passing properties).
Finally, to print it inside child's template just change to: {{ appContent }}

Using globally defined script inside the component

I am using global script declaration inside index.html
<!DOCTYPE html>
<html>
<head>
<script src='https://js.espago.com/espago-1.1.js' type='text/javascript'></script>
...
</head>
<body>
...
</body>
Now I want to use it inside the component.
import * as React from "react";
import * as $ from "jquery";
//how to import Espago?
export default class EspagoPayment extends React.Component<any, any> {
componentDidMount() {
$("#espago_form").submit(function(event){
var espago = new Espago({public_key: 'xxx', custom: true, live: false, api_version: '3'});
espago.create_token({
...
});
});
}
render() {
return (
...
);
}
}
Webpack gives an error on build.
error TS2304: Cannot find name 'Espago'
How to get Espago visible inside the component?
Maybe there is other way to link to online js resource?
You have to tell TypeScript that it's defined somewhere else.
declare var Espago: any;
See https://stackoverflow.com/a/13252853/227299

Categories