React get and show attached files in Rails - javascript

seems a simple problem but, i don't know why this is causing a little stress, anyway.
here is situation:
after get the attachments on the Post.show in rails
async getAttachments() {
// this.setState({showProgress: true})
let auth_token = window.localStorage.getItem("auth_token");
let post_id = this.props.match.params.id;
fetch(`/posts/${post_id}/attachments`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Access: auth_token
}
})
.then(response => response.json())
.then(json => {
this.setState({
attachments: json.attachments,
image: json.attachments.image,
thumb: json.attachments.image.thumb,
url: json.attachments.thumb.url
});
})
.catch(error => {
console.error(error);
});
}
i decide to render it as usual
with {this.state.attachments}
but did not rendered.
so i tried to map and then i tried
var attachments = this.state.attachments.map(a => {
return (
<div key={a.id}>
<img source={a.thumb} />
<img source={a.url}>{a.url}</img>
</div>
);
});
the thing is ever rails carrierwave attachment/files. create a object inside the array and many people still have doubt how to grab and render these files.

The HTML image tag uses an attribute named src, not source. That's your problem.
Aside: Consider this snippet of your code:
this.setState({
attachments: json.attachments, // OK, keep
image: json.attachments.image, // bad path into the object
thumb: json.attachments.image.thumb, // bad path into the object
url: json.attachments.thumb.url // bad path into the object
});
The three lines starting with image, thumb, and attachment should be deleted.
The result of using the code without deleting those lines will be that your state looks like:
{
attachments: <an array of attachments>, // correct, wanted, should keep
image: undefined,
thumb: undefined,
url: undefined
}
This is because json.attachments is an array so it does not have any data at the paths you are calling. By "path into the object" I merely mean a sequence of keys to call in sequence of dot notation.

Related

Axios get call in Vue3 not working, although curl and javascript work as expected

I'm trying to make an API call from my Vue3 app. The prepared API has an endpoint like http://localhost:8888/api/dtconfigsearch, where one needs to pass a json payload like { "Modelname": "MyFancyModel"} to get the full dataset with the given modelname. Pure get functions without a payload / a body do work from my Vue3 project to the golang backend, but I'm having problems with passing a payload to the backend.
Test with curl -> ok
$ curl -XGET localhost:8888/api/dtconfigsearch -d '{"Modelname" : "MyFancyModel" }'
{"ID":4,"Modelname":"MyFancyModel","ModelId":"96ee6e80-8d4a-b59a-3524-ced3187ce7144000","OutputTopic":"json/fancyoutput"}
$
This is the expected output.
Test with javascript ok
Source file index.js:
const axios = require('axios');
function makeGetRequest() {
axios.get(
'http://localhost:8888/api/dtconfigsearch',
{
data: { Modelname : "MyFancyModel" },
headers: {
'Content-type' : 'application/json'
}
}
)
.then(resp => {
console.log(resp.data)
})
.catch(err => {
console.log(err)
})
}
makeGetRequest()
Output
$ node index.js
{
ID: 4,
Modelname: 'MyFancyModel',
ModelId: '96ee6e80-8d4a-b59a-3524-ced3187ce7144000',
OutputTopic: 'json/fancyoutput'
}
$
Here, I also get the desired output.
Test within Vue fails :-(
Source in the Vue one file component:
onSelection(event) {
let searchPattern = { Modelname : event.target.value }
console.log(event.target.value)
console.log("searchPattern = " + searchPattern)
axios.get("http://localhost:8888/api/dtconfigsearch",
{
data : { Modelname : "Windshield"},
headers: {
'Content-type' : 'application/json',
'Access-Control-Allow-Origin': '*'
}
})
.then(response => {
console.log(response.data)
})
.catch(err => {
console.log(err)
alert("Model with name " + event.target.value + " not found in database")
})
},
Output in browser:
In the image you can see in the terminal log on the right side that the backend is not receiving the body of the API call. However, in the browser information of the call there is content in the config.data part of the object tree, which is the payload / the body. The only thing that bothers me that it is not a json object, but stringified json, although it was entered as json object. According to the documentation, the parameter name (data) in the call should be correct to hold the body content of the api call.
I've tried different header information, looked if it could be a CORS issue, what it isn't to my opinion, exchanged key data with body, used axios instead of axios.get and adapted parameter, all without success. The version of the axios library is 0.27, identical for Vue and vanilla javascript. After checking successfully in javascript, I was sure that it would work the same way in Vue, but it didn't.
Now I'm lost and have no further ideas how to make it work. Maybe someone of you had similar issues and could give me a hint? I'd be very grateful for some tipps!!

Changing JSON values with fetch

Hey guys I'm writing a small email app and I am trying to create an archive button for each email. I'm sure that the function is being called but, for some reason, it's not changing archived to true.
The odd thing is, if I replaced "archived: true" with "read: true" it does change 'read' to true. However, for some reason it won't change the archived attribute.
I'm sure that 'archived' is a valid attribute in the JSON API.
Any reason this would happen? Here is the code in question:
function archive_email(id) {
fetch('/emails/' + id, {
method: 'PUT',
body: JSON.stringify({
archived: true
})
})
load_mailbox("inbox");
}
I think is a promise problem, did you try the following code?
function archive_email(id) {
fetch('/emails/' + id, {
method: 'PUT',
body: JSON.stringify({
archived: true
})
}).then(() => {
load_mailbox("inbox");
})
}

Syntax for correct Routes path

So I have a component for displaying players which uses my displayPlayerObject function. The problem is it is I do not have the correct route for fetching in the function and for POST in my routes.rb. The model relationships have the players belonging to the teams and the route for getting a player is "http://localhost:3000/api/teams/1/players/1" 1 being the team id for the former and player id in the latter. But how do I make the displayPlayerObject work the same way syntax wise? And how should it look like for the POST in routes.rb? Also I suspect my players controller's 'show' is wrong as well.
displayPlayerObject function (edited):
export const displayPlayerObject = (id, teamId, type) => {
return dispatch => {
const data = { id };
fetch(`/api/teams/:team_id/players/show`, {
method: 'post',
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data)
})
.then(res => res.json())
.then(responseJSON => { dispatch({ type , player_object: responseJSON})
})
}
};
My routes.rb (edited):
Rails.application.routes.draw do
scope '/api' do
post "teams/show", to: 'teams#show'
post "teams/:team_id/players/show", to: 'players#show'
resources :teams do
resources :players
resources :star_players
end
end
end
Players Controller Show (edited):
def show
Player.find(params[:id])
render json: #player, status: 200
end
Let's make it look better.
First of all Player.find_by(id: params[:id]) is nonsense, since find_by(id: params[:id]) is equal to where(id: params[:id]).take. Better to replace it with classic find(params[:id]) which looks much better.
What about your main question, what is the point to give name for POST with displayObject. For me display something means GET it to me. If you want to get some player you need to call api/teams/:team_id/players/:id. But if you want to create new player you need to make POST request to /api/teams/:team_id/players route.
P.S.1 Just change request method from POST to GET. And then add to your promise this:
.then(res => {
console.log(res)
})
And observe what server returns
P.S.2 Change request to
fetch(`/api/teams/:team_id/players/:id`)
And in controller find player by :id

How to upload a file along with text using fetch in react native?

I'm trying to upload a file to the server using react-native-document-picker. The problem I'm facing is I don't know how to upload the file along with a text.In my app there is a portion for file upload also there is an area for writing some text.Then it will get uploaded to the server.So I've done the following.But I'm getting this error after submitting to server
unhandled promise rejection unsupported BodyInit type
updated portion of code
filepick = () => {
DocumentPicker.show({
filetype: [DocumentPickerUtil.images()],
}, (error, res) => {
if (error == null) {
console.log(
res.uri,
res.type, // mime type
res.fileName,
res.fileSize
);
this.setState({
img_uri: res.uri,
img_type: res.type,
img_name: res.fileName
})
} else {
Alert.alert('Message', 'File uploaded failed');
}
});
};
onPressSubmit() {
const data = new FormData();
data.append('file', { uri: this.state.img_uri, type:
this.state.img_type, name: this.state.img_name })
data.append('comment', { text: this.state.text });
AsyncStorage.getItem("userdetail").then(value => {
fetch(GLOBAL.ASSN_URL +`${this.props.id}`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
'Authorization': value
},
body: data
}).then((response) => {
return response.text()
}).then((responseJson) => {
var result = responseJson;
console.log(result);
});
})
}
The function filepick() is called after choosing a file from your device.Please help me to find a solution.How do I upload this to server also how to send text without stringifying it?
body: ({
file: this.state.file,
comment: this.state.text
})
Why are you wrapping body in brackets? Removing them might fix it.
Also see this, https://github.com/facebook/react-native/issues/6025 you might want to stringify the body object, since your content type is not application/json
body: JSON.stringify({
file: this.state.file,
comment: this.state.text
})
Edit
From comments we now know the following
1) You are uploading a file separately.
2) The upload response contains information about the file
3) You are saving the entity in separate server call
4) You need to save file with that entity
The solution below assumes that you have full control over server and you are also handling the file uploading endpoint. Here is the solution
You basically do not need to upload the whole file again with your entity since it is already uploaded on server, all you need to do is to save the reference of the file with entity. Their are two ways to save the reference
1) Just save either the fileName or fileUrl in your entity table and then store the name or url with entity so it will look like this
{
id: 1,
name: 'Cat',
picture: // url or name of picture
}
2) Save the uploaded file in different table, then save the id of the file with your entity, and when you fetch entities get the related file. However if the relationship between entity and file is one to many as in one entity can have many files then you will first need to save the entity and then upload the files with reference of entity. This way your entity will look like this
{
id: 1,
name: 'Cat',
pictures: [{fileName: 'cat1'}, {fileName: 'cat2'}]
}

Using JavaScript to submit an array and a file object to a Rails backend

I haven't been able to figure out how to get my JavaScript to send a request in a format that Rails will accept when I try to edit a Game with a File parameter and an array parameter in the same payload.
The Rails controller looks like this (simplified, obviously):
class GamesController < ApplicationController
def update
#game = Game.find(params[:id])
authorize #game
respond_to do |format|
if #game.update(game_params)
format.html { render html: #game, success: "#{#game.name} was successfully updated." }
format.json { render json: #game, status: :success, location: #game }
else
format.html do
flash.now[:error] = "Unable to update game."
render :edit
end
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
private
def game_params
params.require(:game).permit(
:name,
:cover,
genre_ids: [],
engine_ids: []
)
end
end
So I have JavaScript like so:
// this.game.genres and this.game.engines come from
// elsewhere, they're both arrays of objects. These two
// lines turn them into an array of integers representing
// their IDs.
let genre_ids = Array.from(this.game.genres, genre => genre.id);
let engine_ids = Array.from(this.game.engines, engine => engine.id);
let submittableData = new FormData();
submittableData.append('game[name]', this.game.name);
submittableData.append('game[genre_ids]', genre_ids);
submittableData.append('game[engine_ids]', engine_ids);
if (this.game.cover) {
// this.game.cover is a File object
submittableData.append('game[cover]', this.game.cover, this.game.cover.name);
}
fetch("/games/4", {
method: 'PUT',
body: submittableData,
headers: {
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
}).then(
// success/error handling here
)
The JavaScript runs when I hit the submit button in a form, and is supposed to convert the data into a format Rails' backend will accept. Unfortunately, I'm having trouble getting it to work.
I'm able to use JSON.stringify() instead of FormData for submitting the data in the case where there's no image file to submit, like so:
fetch("/games/4", {
method: 'PUT',
body: JSON.stringify({ game: {
name: this.game.name,
genre_ids: genre_ids,
engine_ids: engine_ids
}}),
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
})
This works fine. But I haven't been able to figure out how to use JSON.stringify when submitting a File object. Alternatively, I can use a FormData object, which works for simple values, e.g. name, as well as File objects, but not for array values like an array of IDs.
A successful form submit with just the ID arrays (using JSON.stringify) looks like this in the Rails console:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "engine_ids"=>[], "genre_ids"=>[13]}, "id"=>"4"}
However, my current code ends up with something more like this:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"18,2,15", "engine_ids"=>"4,2,10"}, "id"=>"4"}
Unpermitted parameters: :genre_ids, :engine_ids
Or, if you also upload a file in the process:
Parameters: {"game"=>{"name"=>"Pokémon Ruby", "genre_ids"=>"13,3", "engine_ids"=>"5", "cover"=>#<ActionDispatch::Http::UploadedFile:0x00007f9a45d11f78 #tempfile=#<Tempfile:/var/folders/2n/6l8d3x457wq9m5fpry0dltb40000gn/T/RackMultipart20190217-31684-1qmtpx2.png>, #original_filename="Screen Shot 2019-01-27 at 5.26.23 PM.png", #content_type="image/png", #headers="Content-Disposition: form-data; name=\"game[cover]\"; filename=\"Screen Shot 2019-01-27 at 5.26.23 PM.png\"\r\nContent-Type: image/png\r\n">}, "id"=>"4"}
Unpermitted parameters: :genre_ids, :engine_ids
TL;DR: My question is, how can I send this payload (a name string, an array of IDs, as well as a game cover image) to Rails using JavaScript? What format will actually be accepted and how do I make that happen?
The Rails app is open source if that'd help at all, you can see the repo here. The specific files mentioned are app/controllers/games_controller.rb and app/javascript/src/components/game-form.vue, though I've simplified both significantly for this question.
I figured out that I can do this using ActiveStorage's Direct Upload feature.
In my JavaScript:
// Import DirectUpload from ActiveStorage somewhere above here.
onChange(file) {
this.uploadFile(file);
},
uploadFile(file) {
const url = "/rails/active_storage/direct_uploads";
const upload = new DirectUpload(file, url);
upload.create((error, blob) => {
if (error) {
// TODO: Handle this error.
console.log(error);
} else {
this.game.coverBlob = blob.signed_id;
}
})
},
onSubmit() {
let genre_ids = Array.from(this.game.genres, genre => genre.id);
let engine_ids = Array.from(this.game.engines, engine => engine.id);
let submittableData = { game: {
name: this.game.name,
genre_ids: genre_ids,
engine_ids: engine_ids
}};
if (this.game.coverBlob) {
submittableData['game']['cover'] = this.game.coverBlob;
}
fetch(this.submitPath, {
method: this.create ? 'POST' : 'PUT',
body: JSON.stringify(submittableData),
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': Rails.csrfToken()
},
credentials: 'same-origin'
})
}
I then figured out that, with the way DirectUpload works, I can just send the coverBlob variable to the Rails application, so it'll just be a string. Super easy.
You can convert the File object to a data URL and include that string in JSON, see processFiles function at Upload multiple image using AJAX, PHP and jQuery or use JSON.stringify() on the JavaScript Array and set that as value of FormData object, instead of passing the Array as value to FormData.
submittableData.append('game[name]', JSON.stringify(this.game.name));
submittableData.append('game[genre_ids]', JSON.stringify(genre_ids));
submittableData.append('game[engine_ids]', JSON.stringify(engine_ids));

Categories

Resources