I have a react page that looks like this:
and right now when creating a new category the post request goes through to the database but the categories is not rendered again to display the new category unless you refresh the page (GET request for all categories on page start up).
SideBar.js
createNewCategory = async (input) => {
console.log("CREATING NEW: ", input);
var response = await fetch("http://localhost:8081/api/categories", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Token": 1234,
Accept: "application/json"
},
body: JSON.stringify({
title: input
})
})
let resp = await response.json();
this.setState({
categories: [...this.state.categories, resp]
})
}
CreateCategory.js
handleNewCategory = (event) => {
event.preventDefault()
this.props.createNewCategory(this.state.input)
this.setState({
input: ''
})
}
render(){
return (
<form onSubmit={this.handleNewCategory} className="new-category-form">
<h4>Create Category</h4>
<input onChange={this.handleInput} className="new-category-input" type="text" value={this.state.input} />
<input className="new-category-input" type="submit" value="Create" />
</form>
)
}
CategoriesContainer.js
function CategoriesContainer(props) {
function renderCategories(){
console.log("PROPS: ", props)
return props.categories.map(category => {
console.log("CATEACH: ", category)
return <Category key={category.id} category={category} />
})
}
return(
<div>
{renderCategories()}
</div>
)
}
At the moment if I create a new category with a name of letters I get the err
Uncaught (in promise) SyntaxError: Unexpected token a in JSON at position 0 sidebar.js:46
and if I create it with numbers I get
Warning: Each child in a list should have a unique "key" prop.
Im still new to react so hopefully Im not completely off the mark here, any ideas?
Fixed it. First off I was using response instead of resp to update the state and I was returning just the name rather than the whole object to the POST request.
Related
Using Symfony Forms, HTML is generated that looks like this:
<input type="text" id="form_name" name="form[name]">
<input type="email" id="form_email" name="form[email]">
<textarea id="form_message" name="form[message]"></textarea>
With a bit of JS the entries are transformed to JSON and submitted:
const contactForm = document.getElementById('contact-form');
contactForm.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const jsonData = JSON.stringify(Object.fromEntries(formData));
// handle submission...
})
JSON that is sent to the backend:
"{"form[name]":"John Doe","form[email]":"example#domain.com","form[message]":"Some message"}"
In my controller (in PHP) I serialize the data into an array: $data = json_decode($request->getContent()); The issue is this data is formatted (as expected) like so:
["form[name]" => "John Doe", "form[email]" => "example#domain.com", "form[message]" => "Some message"];
Is there a built-in way to get the following result (either in PHP or JS)?
[ "name" => "John Doe", "email" => "example#domain.com", "message" => "Some message" ];
I looked into using the Serializer Component without success, and now wonder if I missed something or if the data should be fixed in JS before submission. Might there be a built-in solution?
If I'm not wrong you are submitting from using AJAX. And in that you can directly specify FormData object as body in AJAX API request. At backend you will receive data in $_POST or $_GET array as per your request method.
Here is the example code.
const contactForm = document.getElementById('contact-form');
contactForm.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(event.target);
fetch('<AJAX API URL>', {
method: 'POST',
body: formData
}).then(function (response) {
if (response.ok) {
return response.json();
}
return Promise.reject(response);
}).then(function (data) {
console.log(data);
}).catch(function (error) {
console.warn(error);
});
})
<form id="contact-form">
<input type="text" name="form['name']" />
<input type="text" name="form['job']" />
<input type="submit" value="submit" />
</form>
Here is how you will get data in POST array.
Array
(
[form] => Array
(
['name'] => 123
['job'] => 123123
)
)
I tried different ways to upload images to SQL, but the problem is that I cannot get the file's data at backend. Some answers say that it's because the form does not have an enctype but I tried that too. I'm not sure if it's applicable to Vue since I am using axios. Also, I tried using uploadFile as a parameter for the axios so $_FILES would also read it as $_GET, because it worked on some of my codes as well. By the way, the submitTestData is in another file in another folder, which is inside a store (VueX). I used dispatch to send the data towards the store so that it would finally send a post method to backend.
store.js
submitTestData2 ({ commit }, payload) {
console.log(payload.uploadFile)
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('uploadFile', payload.uploadFile)
const config = {
headers: { 'Content-Type': 'multipart/form-data' }
}
axios
.post(
'http://localhost/MyComposer/',
{
token: payload.token,
subject: payload.subject,
timer: payload.timer,
question: payload.question,
answer: payload.answer,
formData
},
{
params: {
submitId: 7,
uploadFile: formData
},
config
}
)
.then(response => {
commit('SAVE_TEST_DATA', response.data)
console.log(response)
resolve(response)
})
.catch(error => {
reject(error)
})
})
},
AddTest.vue
<q-form class="q-pa-md" align="center">
<h5>Test Creation Form</h5>
<!-- <q-btn label="Add Subject" color="primary" to="/addsub" /> -->
<q-btn label="Return to Main" to="/dashboard" color="primary" />
<q-btn label="View Student Answers" color="primary" to="/subjectntestlist" />
<q-btn label="View Student Profile" color="primary" to="/studentprofile" />
<q-card>
<q-separator />
<q-card-section class="q-gutter-md" align="center">
<q-select
filled
v-model="testItems.subject"
:options="option"
map-options
emit-value
option-value="subjectId"
option-label="subjectName"
label="Choose a Subject"
style="width: 250px"
stack-label
input-debounce="0"
/>
<q-file
filled
v-model="testItems.uploadFile"
label="Upload File Here"
style="width: 500px"
/>
<h5>Timer</h5>
<q-input label="Minute(s)" name="timer" v-model="testItems.timer" style="width: 500px" />
<h5>Question</h5>
<q-input name="question" v-model="testItems.question" style="width: 500px" />
<h5>Answer</h5>
<q-input name="answer" v-model="testItems.answer" style="width: 500px" />
<br />
<q-btn label="Save Test Item" #click="submitTestData" />
</q-card-section>
</q-card>
</q-form>
submitTestData1() {
this.$store
.dispatch("submitTestData2", {
token: this.token,
subject: this.testItems.subject,
question: this.testItems.question,
answer: this.testItems.answer,
uploadFile: this.testItems.uploadFile,
timer: this.testItems.timer
})
.then(response => {
alert("Test was added to the database!");
});
},
<?php
namespace Classes;
use Classes\ConnectDb;
class TestClass
{
public function addTest()
{
$datab = new ConnectDb;
$db = $datab->Connect();
if (isset($_GET['submitId']) && $_GET['submitId'] == 7) {
$testdata = file_get_contents('php://input');
$testdecodedData = json_decode($testdata);
$subject = $testdecodedData->{'subject'};
$access_id = $testdecodedData->{'token'};
$question = $testdecodedData->{'question'};
$answer = $testdecodedData->{'answer'};
// $testImage = $testdecodedData->{'uploadFile'};
$testTimer = $testdecodedData->{'timer'};
$name = $_FILES['uploadFile'];
echo $name;
$testdataDb = array(
'SubjectId' => $subject,
'AccessId' => $access_id,
'Question' => $question,
'Answer' => $answer,
// 'TestImage' => $testImage,
'Timer' => $testTimer * 60
);
$testId = $db->insert('testdetails', $testdataDb);
if ($testId) {
echo 'Test details were added!';
}
}
}
You need to pass header as well like below
submitTestData ({ commit }, payload) {
console.log(payload.uploadFile)
return new Promise((resolve, reject) => {
const formData = new FormData()
formData.append('uploadFile', payload.uploadFile)
const config = {
headers:{'Content-Type' : 'multipart/form-data'}
};
axios
.post('http://localhost/MyComposer/',formData,config)
.then(response => {
commit('SAVE_TEST_DATA', response.data)
console.log(response)
resolve(response)
})
.catch(error => {
reject(error)
})
})
},
It looks like you're not getting the file's name properly from the $_FILES superglobal. You have $_FILES['uploadFile'] in your code however the $_FILES array is structured like this for uploads (uploadFile represents the name of the file upload input field from your form so this varies by input field name):
Array
(
[uploadFile] => Array
(
[name] => users_file_name.png
[type] => image/png
[tmp_name] => /path/to/temporary/files/abc123
[error] => 0
[size] => 12345
)
)
So to access the file's name, you need to change the code to this: $_FILES['uploadFile']['name'].
The actual file is stored in a temporary file location on the server so you'll need to grab that temporary file and move it somewhere else on your server. Something like this is what most people do:
$temp_file = $_FILES['uploadFile']['tmp_name'];
$target_upload_destination = 'path/to/desired/directory/' . basename($_FILES['uploadFile']['name']);
// Check to see that the file was moved to desired destination successfully
if (move_uploaded_file($temp_file, $target_upload_destination)) {
// do something here
} else {
// Fallback logic here
}
Obviously there should be some logic checks before moving the temp file on your server but, I hope you get the basic idea behind this. You should use the file path once it's moved for the DB insert. I hope this helps.
There are several problems here both in your client-side and server-side code.
Client side
If you want to send a file, you must use a multipart/form-data request with a FormData payload. You appear to be trying to combine a JSON payload with an embedded FormData which simply won't work.
You need something like this
const formData = new FormData()
Object.entries(payload).forEach(([key, val]) => {
// adds all the properties in "payload" to "formData"
formData.append(key, val)
})
axios.post('http://localhost/MyComposer', formData, {
params: { submitId: 7 }
})
❗ Note that there is no Content-type header added. Passing a FormData instance sets this automatically with the required mime boundaries.
Server-side
On the PHP side, you would get the token, subject, timer, etc values from $_POST
$subject = $_POST['subject'];
$access_id = $_POST['token'];
$question = $_POST['question'];
// etc
The upload file will be available in $_FILES (see https://www.php.net/manual/features.file-upload.post-method.php)
$uploadFile = $_FILES['uploadFile'];
if (!$uploadFile['error']) {
echo $uploadFile['name'];
}
I have a checkbox
<div class="checkbox">
<label>
<input type="checkbox" value="add user" v-model="user.permissions">Add User
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" value="edit user" v-model="user.permissions">
Edit User
</label>
</div>
the checkbox is v-model on user.permission array
user:{
permissions: []
},
in which when i check a checkbox it will produce a result like this
user:Object
permissions:Array[2]
0:"edit user"
1:"add user"
now when i fetch a data from my backend using axios and put the data on user
editUser: function(id){
let vm = this;
axios.get('api/users/' + id)
.then( response => {
vm.user = response.data.data; //PUT RESPONSE DATA TO USER OBJECT
vm.usersModal = true;
})
.catch( error => {
console.log(error);
});
},
it will produced an output like this
user:Object
created_at:"2018-08-28 03:17:33"
deleted_at:null
email:"aa#gmail.com"
id:3
name:"aa"
permissions:Array[2]
0:Object
created_at:"2018-08-28 03:03:41"
guard_name:"web"
id:2
name:"delete user"
pivot:Object
updated_at:"2018-08-28 03:03:41"
1:Object
created_at:"2018-08-28 03:03:41"
guard_name:"web"
id:3
name:"add user"
pivot:Object
updated_at:"2018-08-28 03:03:41"
updated_at:"2018-08-28 03:17:33"
Now how can I check the checkbox using only v-model user.permission. I used the v-model user.permission because I'm using it on posting a request. However when I fetch it using id the data structure changes.
You will have to modify the fetched response:
editUser: function(id){
let vm = this;
axios.get('api/users/' + id)
.then( response => {
response.data.data.user.permissions = response.data.data.user.permissions.map((item) =>
{
return item.name; // <--- convert the array of objects into array of strings
});
vm.user = response.data.data; //PUT RESPONSE DATA TO USER OBJECT
vm.usersModal = true;
})
.catch( error => {
console.log(error);
});
},
I'm new to Vue and I'm stuck at the moment. For the practice I'm making an app for episode checklist for series. The first part of the app searches series and add one of them to a database. Result for the search gives me a result like this: https://i.stack.imgur.com/QuOfc.png
Heres my code with template and script:
<template>
<div class="series">
<ul>
<li v-for="item in series" :key="item.id">
<img :src="image_url+item.poster_path"/>
<div class="info">
{{item.name}}
<br/>
<h5>{{item.id}}</h5>
Start Date: {{item.first_air_date}}
<br/>
{{getEpisodeNumber(item.id)}}
<br/>
{{getSeasonNumber(item.id)}}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "series",
props: ["series"],
data() {
return {
image_url: "https://image.tmdb.org/t/p/w500",
api_key: {-api key-},
episode_url: "https://api.themoviedb.org/3/tv/",
}
},
methods: {
async getEpisodeNumber(showID) {
const json = await fetch(this.episode_url + showID + this.api_key)
.then((res) => { return res.json() })
.then((res) => { return res.number_of_episodes })
return await json
},
async getSeasonNumber(showID) {
const json = await fetch(this.episode_url + showID + this.api_key)
.then((res) => { return res.json() })
.then((res) => { return res.number_of_seasons })
return await json;
}
},
}
</script>
Methods should return to me a number but they return an object, probably promise object. But when I try to console.log the data in the methods they print a value(int). I need reach this value but I'm stuck. I tried to sort of thinks but it fails every time.
I just create a new component called show and pass item.id to this component. In show component, I use another fetch() to get show data again and now it works like I want.
I have this state defined:
constructor(props){
super(props);
this.state = {
posts:[],
post:{},
openNew:false,
openModify:false
};
}
With the following function which contains a fetch, I recieve an array of objects with responseData:
getPosts(){
fetch(
DOMAIN+'/api/posts/', {
method: 'get',
dataType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization':'Bearer '+this.props.token
}
})
.then((response) =>
{
return response.json();
})
.then((responseData) => {
this.setState({posts:responseData});
console.log("Log del responseData de posts");
console.log(responseData);
})
.catch(function() {
console.log("error");
});
}
This function is called in componentDidMount:
componentDidMount(){
this.getPosts()
}
The JSON object obtained from the fetch and kept within this.state.products looks like this:
As shown previously in the fetch, with this line this.setState({posts:responseData}); I can pass posts to the table where I want title, date and hour to be displayed:
<DataTables
height={'auto'}
selectable={false}
showRowHover={true}
columns={CAMPAIGN_TABLE_COLUMNS}
data={this.state.posts}
showCheckboxes={false}
rowSizeLabel="Filas por página"
onCellClick={this.handleOpenModify.bind(this)}
/>
The table called is:
const CAMPAIGN_TABLE_COLUMNS = [
{
key: 'title',
label: 'Título',
style:{width: '40%'}
}, {
key: 'created',
label: 'Fecha',
style:{width: '30%'},
render: (DateToFormat) => {
return moment(DateToFormat).format("DD/MM/YYYY");
}
}, {
key: 'created',
label: 'Hora',
style:{width: '30%'},
render: (DateToFormat) => {
return moment(DateToFormat).format("hh:mm:ss");
}
}
];
With all of this I am able to print the data that I want on the table, looking like this:
What I am not able to do is: When I click on a row of the table to pass the values that were previously printed, such as the title.
This dialog is constructed using the following lines:
<Dialog
title="Modificar Post"
actions={actions}
modal={false}
open={this.state.openModify}
onRequestClose={this.handleClose}
titleClassName="dialog-title"
contentStyle={{width:660}}
autoScrollBodyContent={true}
>
<TextField
fullWidth={true}
floatingLabelText="Título"
errorText="¡Ups! No deberías ver este mensaje."
defaultValue={this.state.posts.title}
/>
</Dialog>
I thought that binding this to handleOpenModify (the function that is called when you click on a row of the table):
handleOpenModify = () => {
this.getPosts();
this.setState({openModify: true});
};
Would allow me to print the title within the TextField as simple as giving to the defaultValue this.state.posts.title, but is not working as you can see on the last picture that I added.
P.D.: I call getPosts() in handleOpenModify in case it had to be called again when a row is clicked, but it hasn't worked either.
Any suggestions?
DataTables provides you the rowNumber and columnIndex as arguments.
For more information, check their docs:
https://github.com/hyojin/material-ui-datatables/blob/master/src/DataTables/DataTablesRow.js#L142
<DataTables
...
onCellClick={(event, rowNumber) => console.log('selectedPost', this.state.posts[rowNumber]) }
/>
Thanks to #EdwardChopuryan and #Semi-Friends I've been able to retrieve the data that I wanted.
First of all I had to change the name of my function handleOpenModify to handleCellClick, since I could pass through the row parameter all I wanted and keep it within post {}, declared before in the sate.
handleCellClick = (y,x,row) => {
this.getPosts();
this.setState({
openModify: true,
newForm:false,
post:{...row, _id:row._id,title:row.title}})
};
Then, on DataTable, bind it on the onCellClick parameter:
<DataTables
height={'auto'}
selectable={false}
showRowHover={true}
columns={CAMPAIGN_TABLE_COLUMNS}
data={this.state.posts}
showCheckboxes={false}
rowSizeLabel="Filas por página"
onCellClick={this.handleCellClick.bind(this)}
/>
And call the value that I wanted on the TextField through the defaultValue:
<TextField
fullWidth={true}
floatingLabelText="Título"
errorText="¡Ups! No deberías ver este mensaje."
defaultValue={this.state.post.title}
/>
And this is the result!
this is a sample on how to bind and retrieve specific data on click of cell
list item creation
var list = CAMPAIGN_TABLE_COLUMNS.map((data, key) =>
<td onClick={this.handleClick.bind(this, key)}>{data.title}</td>
)
onClick handler
handleClick(id) {
let item = CAMPAIGN_TABLE_COLUMNS[id]; // item data
}
as for your current code, you need to modify this part
onCellClick={this.handleOpenModify.bind(this)} // key or the array index
handleOpenModify(e, row, key) { // receive the column number as 3rd param
let item = CAMPAIGN_TABLE_COLUMNS[key]; // now get the respective object
}