Hey sorry I'm completely new to React and Javascript and I'm really confused about this. I'm trying to create a section where I will have a dropdown list in React. I have already fetched the data from Flask and now when I click my chosen option, I want to send the POST request to Flask (without clicking any submit button).
I was reading different answers and trying to figure it out so I used "fetch" while getting the data and "axios" when trying to send the request.
App.js
import React, { useEffect, useState } from "react"
import { DropdownItem } from "../Components/DropdownItem"
import Select from "react-select"
import axios from "axios"
export const DropdownPage = () => {
const [years, setYears] = useState([])
const [selectedYear, setSelectedYear] = useState("")
useEffect(() => {
fetch("http://127.0.0.1:5000/api", {
method: "GET",
headers: {
"Content-type": "application/json",
},
})
.then((res) => res.json())
.then((data) => {
setYears(data)
})
}, [])
const handleDropdownSelectYear = (e) => {
setSelectedYear(e.target.value)
}
const handleDropdownSubmitYear = (e) => {
e.preventDefault()
console.log(e)
axios
.post("http://127.0.0.1:5000/api/yearselect", selectedYear)
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
}
return (
<div>
<form method="post" onSubmit={handleDropdownSubmitYear}>
<select onChange={handleDropdownSelectYear}>
<option value="">Select Year</option>
{years.map((year) => (
<DropdownItem key={year.id} year={year}></DropdownItem>
))}
</select>
</form>
</div>
)
}
api.py
class Year(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
def __str__(self):
return f'{self.id} {self.content}'
def year_serializer(year):
return {'id': year.id,
'content': year.content}
#app.route('/api', methods=['GET'])
def index():
return jsonify([*map(year_serializer, Year.query.all())])
#app.route('/api/yearselect', methods=['POST'])
def yearselect():
if request.method == 'POST':
request_data = request.get_json(force=True)
year = Year(content=request_data['content'])
db.session.add(year)
db.session.commit()
return {"201": request_data['content']}
If there's any other information/code you need, please let me know. Also, I'm planning to recreate this image below. So, when I make my selection on the first dropdown, it should send a request to flask and narrow down the options for the next dropdown and so on. Let me know if you need any clarification. Thanks!!
If you want to send the POST request when a selection is made, you don't need a <form> or a submit handler.
Instead, add an effect hook that listens for changes to selectedYear
// if selectedYear is going to be an object, don't initialise it as a string
const [selectedYear, setSelectedYear] = useState();
useEffect(() => {
if (selectedYear) {
fetch("http://127.0.0.1:5000/api/yearselect", {
method: "POST",
body: JSON.stringify(selectedYear),
headers: { "content-type": "application/json" },
})
.then((res) => {
if (!res.ok) return Promise.reject(res);
return res.json();
})
.then((data) => {
// do something with data ¯\_(ツ)_/¯
})
.catch(console.error);
}
}, [selectedYear]); // effect hook dependencies
Effect hooks also run when the value is initialised so checking the value of selectedYear before making the request is advisable.
The Axios equivalent would be
axios.post("http://127.0.0.1:5000/api/yearselect", selectedYear)
.then(({ data }) => {
// do something with data
})
.catch(err => {
console.error(err.toJSON());
});
try it please .. You can send data to backend onchange functıon directly called handleDropdownSelectYear function
const handleDropdownSelectYear = (e) => {
setSelectedYear(e.target.value)
console.log(e)
axios
.post("http://127.0.0.1:5000/api/yearselect", e.target.value)
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
}
or you can use useeffect .
const handleDropdownSelectYear = (e) => {
setSelectedYear(e.target.value)
}
useEffect(() => {
if (selectedYear) {
axios
.post("http://127.0.0.1:5000/api/yearselect", selectedYear)
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
}
}, [selectedYear]);
Related
I have external .js file created exactly for fetching data from backend based on website locale. Here is the code:
import { ref } from "vue";
export function fetchData(section, key) {
// GET request using fetch with error handling and headers
const headers = {
method: "GET",
headers: { "Content-Type": "application/json" },
};
const fetchedData = ref(null);
fetch(
"http://localhost:4000/api/" + section + "/?api-key=" + key,
headers
)
.then(async (response) => {
const data = await response.json();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
fetchedData.value = data;
})
.catch((error) => {
console.error("There was an error!", error);
return error;
});
return fetchedData;
}
And this is code from .vue file where I calling fetchData function:
<script setup>
import { fetchData } from "../../utils/universal-fetch";
import { ref, watch } from "vue";
import { useStore } from "../../stores/language.js";
import { useI18n } from "vue-i18n";
import AOS from "aos";
const store = useStore();
const { locale } = useI18n({ useScope: "global" });
const fetchedData = ref(fetchData("homeFirstSection", store.getLanguage));
AOS.init();
watch(
() => locale.value,
() => {
fetchedData.value = fetchData("homeFirstSection", store.getLanguage);
}
);
</script>
When page is created/refreshed, fetchData function fetch data from backend correctly. The problem which I'm trying to solve is that, when I change a locale, watcher automatically detects that, locale was changed and variable fetchedData should be updated based on choosen locale.
Problem
Thanks!
I found a problem. Here is code:
export function async fetchData(section, key) { // Added async
// GET request using fetch with error handling and headers
const headers = {
method: "GET",
headers: { "Content-Type": "application/json" },
};
let fetchedData = null;
await fetch( // Added await
"http://localhost:4000/api/" + section + "/?api-key=" + key,
headers
)
.then(async (response) => {
const data = await response.json();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
fetchedData = data;
})
.catch((error) => {
console.error("There was an error!", error);
return error;
});
return fetchedData;
}
In my external .js file I was missing one more async await.
<script setup>
import { fetchData } from "../../utils/universal-fetch";
import { ref, watch } from "vue";
import { useStore } from "../../stores/language.js";
import { useI18n } from "vue-i18n";
import AOS from "aos";
const store = useStore();
const { locale } = useI18n({ useScope: "global" });
const fetchedData = ref(null);
fetchData("agreementsFirstSection", store.getLanguage).then(
(data) => (fetchedData.value = data)
); // Added .then
AOS.init();
watch(
() => locale.value,
() => {
fetchData("agreementsFirstSection", store.getLanguage).then(
(data) => (fetchedData.value = data)
); // Added .then instead of directly assign
}
);
</script>
And in .vue file I was missing .then insted of directly assigning value to variable. I added comments to code to compare changes before and after.
Problem solved
I have multiple API calls with fairly lengthy, yet similar, response/error handling for each call.
What is the best non-repetitive ways to make multiple independent api calls that update state using fetch?
Copying and pasting 40+ instances of fetch doesn't seem right.
I want to avoid doing this ....
fetch(url,options)
.then((response) => {
// ...
return response.json
})
.then((data) => {
setState(data)
//...
})
.catch((err) => {
//Error logic here
})
Here's what I've done so far:
I made (found and modified) a useFetch hook...
useFetch.ts
//Only calls fetch() when .load() is called.
const useFetch = (path : string, HttpMethod : string, dependencies : any = [] , body : {} | undefined = undefined) => {
const history = useHistory()
const [response, setResponse] = useState<any>({});
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [controller, setController] = useState(2)
const [isReady, setIsReady] = useState<any>(false)
const load = ():void => {
setError("")
//This prevents useEffect from triggering on declaration.
if (isReady) {
//Math.random() is just to get useEffect to trigger.
setController(Math.random())
}
}
const token = localStorage.getItem("token");
let requestOptions:any = {
method: HttpMethod,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "* always",
Authorization: "Token " + token,
},
};
if (body !== undefined) {
requestOptions["body"] = {
body: JSON.stringify(body)
}
}
const URI = BASE_URI + path
useEffect(() => {
const fetchData = async () => {
if (controller !== 2) {
setIsLoading(true);
try {
const res = await fetch(URI, requestOptions);
const json = await res.json();
if (json?.action == "ENFORCE_BILLING" ) {
history.push(BILLING_CREDENTIALS_PATH, { enforceBillingPopUp: true });
}
if (json?.action == "ENFORCE_SMS_CONFIRMATION") {
// Should we log user out, as well?
history.push(CONFIRMATION_CODE_PATH)
}
if (res.ok) {
setResponse(json);
setIsLoading(false)
} else {
setError(json)
setIsLoading(false)
}
} catch (err) {
setError(err);
// Error logic here...
}
}
}
};
fetchData()
setIsReady(true)
}, [controller, ...dependencies]);
return { response, setResponse ,error, isLoading, load, isReady };
};
Component.tsx
//Inside react functional component...
// Prepares to fetch data from back-end
const data1 = useFetch(PATH1, "GET");
const data2 = useFetch(PATH2, "GET");
const data3 = useFetch(PATH3, "GET");
useEffect(() => {
// Initial on load data fetch
// .load() fetches data
data1.load();
data2.load();
data3.load();
}, [activeReservations.isReady]);
// Sort data depending on sort selection
...
Is useFetch considered bad practice? What are the advantages of using Redux, instead?
Any help would be greatly appreciated. Thanks.
I've got components in my react app,
First:
const App = () => {
const [tasks, setTasks] = useState([])
useEffect(() =>{
getTasks(data => setTasks(data))
}, [])
const onNewTask = task => {
setTasks(prev => ([
...prev,
task
]))
}
const onRemoveTask = () => {
setTasks(prev => ([
...prev,
]))
}
return (
<>
<NewTask add={onNewTask}/>
<Task tasks={tasks} remove={onRemoveTask}/>,
</>
)
}
This component renders two childrens:
In comp newTask there is a method where user can add a new task, the method:
const addNewTask = e => {
e.preventDefault();
fetch(`${API_URL}/tasks`, {
method: 'POST',
headers: {
Authorization: API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify(inputs)
})
.then(response => response.json())
.then(data => add(data.data))
.catch(e => console.warn(e))
}
It works fine, user adds a tasks, and it automatically appears in HTML thanks to onNewTask method in App component.
My problem is something similar, but in case of removing the task, I've got a method in Task component:
const handleDeleteTask = e => {
e.preventDefault();
const taskToRemove = e.target.id;
console.log(taskToRemove)
taskToRemove ?
fetch(`${API_URL}/tasks/${taskToRemove}`, {
method: 'DELETE',
headers: {
Authorization: API_KEY,
"Content-Type": "application/json",
},
})
.then(remove())
.catch(err => console.warn(err))
:
console.log('Error')
}
It removes a task, but user needs to refresh page to make the task disappear from HTML.
I know that my method isn't correct. Can someone please tell me how do it?
Thanks in advance.
you need to update the task array and set the same in setTasks.
const onRemoveTask = (id) => {
const newTaskList = tasks.filter(item.id != id)
setTasks(newTaskList)
}
I have a like function on the backend (Node, MongoDB) that returns the given post with updated likes counter. This works, tested it with Postman. This is just an object with a bunch of properties like likes, _id., by, createdAt and so on...
let p = await Post.findById(req.params.id).populate("by");
return res.json(p);
Then I have a like action in React:
export const like = (id) => (dispatch) => {
const token = localStorage.getItem("token");
if (token) {
axios
.put(`http://localhost:5000/likePost/${id.id}`, id, {
headers: { "X-Auth-Token": token },
})
.then((res) => {
dispatch({
type: LIKE,
payload: res.data,
});
});
}
};
And I have a LIKE reducer:
case LIKE:
return {
...state,
posts: state.posts.map((p) => {
return { ...p };
}),
};
The LIKE reducer triggers when I click on the button and on the backend I can see the update but on the client side it doesn't update. I use redux-logger and the posts state is not updated.
What did I do wrong? I thought that spreading all the posts (...p) will update it, since it is updated on the backend.
This one works:
case LIKE:
return {
...state,
posts: state.posts.map((p) => {
if (p._id === action.payload._id) {
p.likes = action.payload.likes;
}
return p;
}),
};
I'm trying to make a "edit" feature for my project, and I'm stuck at this part..
I have a put request :
export const updateEvent = (event, id) => (dispatch, getState) => {
request
.put(`${baseUrl}/event/${id}`)
.send(event)
.then(response => {
dispatch(updatedEvent(response.body))
})
.catch(error => console.log(error))
}
This is the route for the said put, with Sequelize as ORM:
router.put('/event/:id', async (req, res, next) => {
const { id } = req.params
try {
const event = await Event.findByPk(id)
const updatedEvent = await event.update(req.body)
res.send(updatedEvent)
} catch (error) {
next(error)
}
})
When I test it with postman, everything works as expected. Where I ran into my problem is when I'm sending the put data from React in the frontend.
I have a form, and I save my data in the local state, and then dispatch it to actions like this:
handleSubmit = e => {
e.preventDefault()
const id = this.props.event.id
const updatedEvent = {
name: this.state.name,
description: this.state.description,
picture: this.state.picture,
startDate: this.state.startDate,
endDate: this.state.endDate,
userId: this.props.userId
}
this.props.updateEvent(updatedEvent, id)
}
Any value that is left empty in the form is overwriting my fields with nothing (an empty string). How do I properly handle this?
A solution is to filter your object, such that you remove any properties which have empty values and therefore won't be included in the database update.
In your router.put():
router.put('/event/:id', async (req, res, next) => {
const { id } = req.params
try {
const event = await Event.findByPk(id);
// filter req.body to remove empty values
const { body } = req;
const filteredBody = Object.keys(body).reduce((resultObj, key) => {
if(body[key] != ''){
resultObj[key] = body[key];
}
return resultObj;
}, {});
const updatedEvent = await event.update(filteredBody);
res.send(updatedEvent)
} catch (error) {
next(error)
}
})