I am using the React-Rails gem and accessing a json object items from a Rails controller.
Rails controller:
class ItemsController < ApplicationController
def index
#items = Item.all
render json: #items
end
end
My React App component accesses these items and attempts to pass it as a prop to a child component:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: {},
activeTab: 'items'
};
}
componentDidMount() {
$.getJSON('/items.json', (response) => {
this.setState({ items: response })
});
}
render () {
return (
<div>
<ItemsContent items={this.state.items}>
</div>
);
}
}
And this child component looks like this:
class ItemsContent extends React.Component {
render () {
return (
<div>
<div>Items: {this.props.items}</div>
</div>
);
}
}
ItemsContent.propTypes = {
items: React.PropTypes.object
};
And I get this error:
react.js?body=1:1324 Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `ItemsContent`.
How do I get around this? Is there a way to easily use JSON objects in my React components?
Right now I tried wrapping the JSON object in an array:
tabbedContent = <ItemsContent items={[this.state.items]}></ItemsContent>;
Since this.state.items is an array, you are unable to dump out all the items in an array like that. You can use the javascript array API and iterate over the items and display them like so :
class ItemsContent extends React.Component {
render () {
return (
<div>
{
this.props.items.map(function(item) {
return <div> Item : {item} </div>
}
</div>
);
}
}
ItemsContent.propTypes = {
items: React.PropTypes.object
};
If you are only getting back a single object every time then map will not work and you need to break out the object by property in order to display everything :
render () {
return (
<div>
<div> Item : {this.props.items.a} , {this.props.items.b}, {this.props.items.c} </div>
</div>
);
}
You can iterate over the list in render() of App Component. And create a React.Component Item for each of the items.
App.js
render () {
return (
<div>
this.state.items.map( function(item){
<Item value={item} key={}>
});
</div>
);
}
In Item.js
class Item extends React.Component {
render () {
return (
<div>
return <div> Item : {this.props.value} </div>
</div>
);
}
}
Item.propTypes = {
value: React.PropTypes.object
};
Related
I'm trying to make a basic CRUD store app with Rails and React, but I'm stuck on displaying the author (user) association of the post. The post itself shows just fine. I'm trying to avoid using jbuilder so I can understand the problem I'm having.
The current show method in the controller, which works:
controllers/post_controller.rb
def show
if post
render json: post
else
render json: post.errors
end
end
The current React view, which works:
app/javascript/components/Post.js
import React from "react";
import { Link } from "react-router-dom";
class Post extends React.Component {
constructor(props) {
super(props);
this.state = { post: { description : '' } };
}
componentDidMount() {
const {
match: {
params: { id }
}
} = this.props;
const url = `/api/v1/show/${id}`;
fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.then(response => this.setState({ post: response }))
.catch(() => this.props.history.push("/posts"));
}
render() {
const { post } = this.state;
let descriptionList = "No descriptions present";
if (post.description.length > 0) {
descriptionList = post.description
.split(",")
.map((description, index) => (
<li key={index} className="list-group-item">
{description}
</li>
));
}
return (
<div className="">
<div className="hero position-relative d-flex align-items-center justify-content-center">
<img
src={post.image}
alt={`${post.description} image`}
className="img-fluid position-absolute"
/>
<div className="overlay bg-dark position-absolute" />
</div>
<div className="container py-5">
<div className="row">
<div className="col-sm-12 col-lg-3">
<ul className="list-group">
<h5 className="mb-2">Description</h5>
{descriptionList}
<div>{post.title}</div>
<div>${(post.price * .01).toLocaleString()}</div>
</ul>
</div>
</div>
<Link to="/posts" className="btn btn-link">
Back to all posts
</Link>
</div>
</div>
);
}
}
export default Post;
When I add render json: post, include: :user to the controller and {post.user.email} and render() { const { post, user } = this.state;
to the view, the error message in the console is cannot read property 'email' of undefined. When I try to define the user in the controller method user = post.user.email and in the view {user}, the terminal error is:
NoMethodError (undefined method 'oswaldo#daugherty.info' for #<Post id: 5, title: "Post 5", description: "You can't synthesize the bandwidth without compres...", image: "https://loremflickr.com/300/300/cats 5", price: 883105, rating: nil, review: nil, created_at: "2021-01-31 23:26:03", updated_at: "2021-01-31 23:26:03", user_id: 5>):
I've checked my database and all the associations display correct there. In short, I don't know how to send the post's user association correctly to the view. What am I missing? Any help appreciated because I'm really spinning my wheels on this one.
you might be facing the bug reported bug.
If you are only looking email from related user record, you can use following
# in Post model
delegate :email, to: :user, prefix: true, allow_nil: true
# and while rendering
use Post.as_json(methods: [:user_email])
Even though rails 6 is still in beta I thought to test it out building a rails + vue app but when trying to parse the json data im getting a error in the console "VM353:1 Uncaught SyntaxError: Unexpected token u in JSON at position 0" Not sure why my data is not being parsed. Undefined but cant figure out why
Here is my hello_vue.js file
import Vue from 'vue/dist/vue.esm'
import VueResource from 'vue-resource'
Vue.use(VueResource)
document.addEventListener('DOMContentLoaded', () => {
Vue.http.headers.common['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content')
var element = document.getElementById("gameapp-form")
if(element != null) {
var game_application = JSON.parse(element.dataset.game_application)
var app = new Vue({
el: element,
data: function () {
return { game_application: game_application}
},
methods: {
saveApplication: function() {
this.$http.post('/game_applications', {game_application: this.game_application }).then(response => {
console.log(response)
}, response => {
console.log(response)
})
}
}
})
}
}
)
Here is my _form.html.erb file
<%= content_tag :div,
id: "gameapp-form",
data: {
game_application: game_application.to_json(except: [:created_at, :updated_at]),
} do %>
<label>Game Name</label>
<input type="text" v-model="game_application.name" />
<label>Game Name</label>
<input type="text" v-model="game_application.video_link" />
<button v-on:click="saveApplication">Send Application</button>
<% end %>
Use response.data with your axios request
this.$http.post('/game_applications', {game_application: this.game_application})
.then(response => response.data)
.then(response => {
console.log(response)
})
Axios returns your JSON output in the data field. So, in the first then we are basically returning response.data so that you can use it in the next then. Hope that makes sense.
I will fill the customerNumberContainers which looks like this:
this.form = new FormGroup({
customerNumberContainers: new FormArray([
new FormGroup({
contactTenant: new FormControl('', [Validators.required, Validators.minLength(2)]),
customerNumber: new FormControl('', [Validators.required, Validators.minLength(2)])
}),
]),
Therefore I do this after I get the values over
this.contactService.findContactById(this.id).subscribe(response => { ...
Set values into form:
let customerNumberContainersFormArray: FormArray = this.form.controls.customerNumberContainers as FormArray;
customerNumberContainersFormArray.controls["0"].controls.contactTenant.value = 'TestValue';
but it is not shown with:
in Controller:
get customerNumberContainers(): FormArray {
return this.form.get("customerNumberContainers") as FormArray;
}
in Template:
<div formArrayName="customerNumberContainers">
<div *ngFor="let customerNumberContainer of customerNumberContainers.controls; index as i" [formGroupName]="i">
<mat-input-container class="full-width-input">
<input matInput formControlName="contactTenant">
</mat-input-container>
</div>
Does anyone known what I am doing wrong. It seems for me that values with *ngFor arn't refreshed.
why dont You just patch whole form with model ? like this:
set up your model, for example:
export class Tenants {
id: number;
customerNumberContainers: TenantContact[];
}
export class TenantContact {
contactTenant: string;
customerNumber: string;
}
fetch it from service like u always do but it should match above models and patch whole form (or setValue)
this.contactService.findContactById(this.id).subscribe((tenats: Tenants) => {
this.form.patchValue(tenats);
});
I am working on an AngularJS app with a remote Rails backend for registrations for a sports event. Besides registering as a single runner, users should also be able so submit registrations for relay teams with 4 runners.
Rails backend structure:
class Relay < ActiveRecord::Base
has_many :registrations
accepts_nested_attributes_for :registrations
end
class Registration < ActiveRecord::Base
belongs_to :relay
end
class API::RelaysController < API::BaseController
def create
#relay = Relay.new(relay_params)
if #relay.save
render json: #relay, status: :created#, location: #relay
else
render json: #relay.errors, status: :unprocessable_entity
end
end
end
Nested form in Rails frontend:
My Angular relay create controller:
appControllers.controller('RelayCreateCtrl', ['$scope', '$routeParams', 'Relay', 'Registration', '$location', function($scope, $routeParams, Relay, Registration, $location) {
$scope.errors = {}
$scope.relay = new Relay();
$scope.registrations_attributes = [];
for (var i=0; i<4; ++i ){
registration = new Registration();
registration.run_id = $routeParams.run_id;
$scope.registrations_attributes.push(registration);
}
$scope.relay.registrations_attributes = $scope.registrations_attributes;
$scope.submit = function() {
function success(response) {
console.log("success", response)
$location.path("/registrations");
}
function failure(response) {
console.log("failure", response);
$scope.errors = response.data;
console.log($scope.errors);
}
Relay.create($scope.relay, success, failure)
};
}]);
ngResource Services:
rilaServices.factory('Registration', ['$resource',
function($resource){
return $resource('http://localhost:3000/api/registrations/:id', { id: "#id" }, {
'create': { method: 'POST' }
});
}
]);
rilaServices.factory('Relay', ['$resource',
function($resource){
return $resource('http://localhost:3000/api/relays/:id', { id: "#id" }, {
'create': { method: 'POST' }
});
}
]);
relay-create.html:
<form name="form" ng-submit="submit()" class="form-horizontal" novalidate>
<div ng-repeat="registration in relay.registrations_attributes" ng-form="registration_form" class="well">
...
<div class="form-group">
<label class="control-label col-sm-4">
Firstname
</label>
<div class="input-group col-sm-7">
<input class="form-control" ng-model="registrations_attributes.runner_firstname" type="text">
</div>
</div>
...
</div>
</form>
What steps do I need to follow to get a form with nested fields running in AngularJS like the one I created in Rails?
I have datatable and a div, where I want to load information about the order with ajax after clicking on the row of the datatable.
assets/javascript/orders.js:
$(document).ready(function() {
$('#orders').dataTable({
"sPaginationType": "full_numbers",
"bJQueryUI": true
});
$('#orders tbody tr').each( function() {
this.onclick = function() {
$('#order_info').load(
???
)
};
})
})
orders/list.html.erb:
<table id="orders">
<tbody>
<% #orders.each do |order| %>
<tr data-orderrow=<%= order[:id] %>>
</tr>
<% end %>
</tbody>
</table>
<div id="order_info" style="float: right;">
</div>
If my approach is good, what should I put in the place of ??? in my javascript.
You can make an ajax call to some controller and action that returns json data. Then consume that data, and display the json that comes back.
$(document).ready(function() {
// best to 'cache' the jquery queries you use more than once.
var $orders = $("#orders"),
$orderInfo = $("#orderInfo");
$orders.dataTable({
"sPaginationType": "full_numbers",
"bJQueryUI": true
});
// it is best not to attach one click even to each 'tr', so instead
// I've added a single click event to the entire table, and then
// determine the closest 'tr' to get the orderId from it.
$orders.click(function(e){
var $target = $(e.target), $order, orderId; // get the element that was clicked
$order = $target.closest("tr");
orderId = $order.attr("data-orderrow");
$.getJSON("/orders/"+orderId+".json",function(orderData){
// do something with orderData, which is an object with the order data
});
});
})
Then in your controller, you could do something like this. You'll need to fill this out in the best way for your application:
class SomeController < ApplicationController
def show
order = Order.find(params[:id])
respond_to do |format|
format.html { render "show" }
format.json { render :json => order }
end
end
end