Change number of servings on click (React Hooks - API) - json

I'm working on a recipe site using API from a third party and want to change the number of servings (which is output from the API data) when clicking the + & - button. I tried assigning the output serving amount <Servings>{recipe.servings}</Servings> in a variable and useState to update it but it kept showing errors. I would appreciate any help (preferably using react Hooks). Thanks :)
Here is my code:
const id = 716429;
const apiURL = `https://api.spoonacular.com/recipes/${id}/information`;
const apiKey = "34ac49879bd04719b7a984caaa4006b4";
const imgURL = `https://spoonacular.com/cdn/ingredients_100x100/`;
const {
data: recipe,
error,
isLoading,
} = useFetch(apiURL + "?apiKey=" + apiKey);
const [isChecked, setIsChecked] = useState(true);
const handleChange = () => {
setIsChecked(!isChecked);
};
return (
<Section>
<h2>Ingredients</h2>
<ServingsandUnits>
{recipe && (
<ServingsIncrementer>
<p>Servings: </p>
<Minus />
<Servings>{recipe.servings}</Servings>
<Plus />
</ServingsIncrementer>
)}
<ButtonGroup>
<input
type="checkbox"
id="metric"
name="unit"
checked={isChecked}
onChange={handleChange}
/>
<label htmlFor="male">Metric</label>
</ButtonGroup>
</ServingsandUnits>
</Section>
};
My custom hook is called useFetch:
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch(url, { signal: abortCont.signal })
.then((res) => {
if (!res.ok) {
// error coming back from server
throw Error("Could not fetch the data for that resource");
}
return res.json();
})
.then((data) => {
setIsLoading(false);
setData(data);
setError(null);
})
.catch((err) => {
if (err.name === "AbortError") {
console.log("Fetch aborted");
} else {
// auto catches network / connection error
setIsLoading(false);
setError(err.message);
}
});
return () => {
abortCont.abort();
};
}, [url]);
return { data, isLoading, error };
};
export default useFetch;

Related

Confused about changing state in a certain way

I have a page in NextJS for editing an sql row and sending it back. I have fetched all the rows from the table and then have set the state to be the single row which matches the query parameter in the useRouter hook. Now, after I have edited the data in the row, what is a good way to POST it back to the backend?
Below is my React code:
import { React, useEffect, useState } from "react";
import { useRouter } from "next/dist/client/router";
const axios = require("axios");
export default function Edit() {
const [data, setData] = useState([]);
const router = useRouter();
const onSubmitHandler = (e) => {
e.preventDefault();
axios.post("/api/cards", data);
};
useEffect(() => {
const fetchData = async () => {
await axios
.get("/api/cards")
.then((res) => {
if (res.data) {
res.data.map((element) => {
if (element.ID == router.query.card) {
setData(element);
return;
}
return;
});
}
})
.catch((err) => {
console.log(err);
});
};
if (router.isReady) {
fetchData();
}
}, [router.isReady, router.query.card]);
return (
<form onSubmit={onSubmitHandler}>
<label htmlFor="front">Front</label>
<input
defaultValue={data.Front}
id="front"
onChange={(e) => setData({ ...data, Front: e.target.value })}
></input>
<label htmlFor="back">Back</label>
<input
defaultValue={data.Back}
id="back"
onChange={(e) => setData({ ...data, Back: e.target.value })}
></input>
<button type="submit">Add Word</button>
</form>
);
}
Below is my backend code
if (req.method === "POST") {
const { front, back, type } = req.body.data;
const id = uuidv4();
db.query(
`INSERT INTO deck VALUES('${front}', '${back}', '${type}', '${id}')`,
(err, rows, fields) => {
if (!err) {
res.json(rows);
} else {
console.log(err);
}
}
);
}
Its good to post the edited data after submiting the form..
const onSubmitHandler = async (e) => {
e.preventDefault();
try {
await axios.post("/api/cards", data);
// react-toast or something like that to indicate the ui the form is updated
// then the control flow of the application
} catch (error){
console.error(error)
}
};
One thing I notice over here is you're using POST for the update. Try HTTP PUT instead of POST.
And regarding your answer: You can send modified data in your API call like you're already maintaining the state of the updated data. Then you can just send that row to the API call and handled that in your backend code.
const onSubmitHandler = (e) => {
e.preventDefault();
axios.put("/api/cards/:id", data); // modify the API URL and append dynamic ID of the record.
};

Component responsible to display all my transactions are not updating after submit

I'm using redux toolkit with react and have a basic setup because I'm building a simple expense tracker, so I have two operations: get all transactions and add a new transaction. That's it.
My problem: When I create a new transaction the component responsible for displaying my data does not update and I can only see the changes after refreshing the page.
Below you can see my transactionSlice file:
const initialState = {
transactions: [],
loading: false,
error: null,
}
export const getTransactions = createAsyncThunk(
"transactions/getTransactions",
async () => {
const res = await axios.get('http://localhost:8000/transactions')
return res.data
}
)
export const addTransaction = createAsyncThunk(
"transaction/addTransaction",
async(data) => {
const res = await axios.post('http://localhost:8000/transactions', data);
return res.data
}
)
const transactionsSlice = createSlice({
name: 'transactions',
initialState,
reducers: {},
extraReducers: {
[getTransactions.pending]: (state) => {
state.loading = true;
},
[getTransactions.fulfilled]: (state, {payload}) => {
console.log(payload);
state.loading = false;
state.transactions = payload;
state.error = ''
},
[getTransactions.rejected]: (state) => {
state.loading = false;
state.error = state.error.message;
},
[addTransaction.pending]: (state) => {
state.loading = true;
},
[addTransaction.fulfilled]: (state) => {
state.loading = false;
},
[addTransaction.rejected]: (state) => {
state.loading = false;
state.error = state.error.message;
}
}
});
and here is the code from the component where I'm displaying all transactions
const { transactions, loading } = useSelector(selectAllTransactions);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getTransactions());
}, [dispatch]);
but when I make a post request my state with all transactions doesn't update immediately. I can only see the changes if I update the page and I'm doing it manually. I'm wondering why is this happening if I have useEffect watching for changes?
AddTransaction.js file :
const [transactionName, setTransactionName] = useState('');
const [amount, setAmount] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
const data = {
transactionName,
amount
}
if(transactionName && amount){
dispatch(addTransaction(data));
dispatch(getTransactions());
setTransactionName('')
setAmount('');
}
}
I've tried to google it but it seems my doubt is so silly that I can't even find an answer for that.
Here is my server file:
app.post('/transactions',(req, res) => {
const {transactionName, amount} = req.body;
const query = `INSERT INTO transactions (title, amount)
VALUES ("${transactionName}", "${amount}")`
db.query(query, (err, result) => {
if(err){
console.log(err)
}
res.send(result)
})
});
Am I missing something? Could someone explain to me why the component responsible to display all transactions are not updating after submit, please?
Try executing getTransactions once addTransaction(data) is finished, not at the same time:
const handleSubmit = (e) => {
e.preventDefault();
const data = {
transactionName,
amount
}
if(transactionName && amount){
dispatch(addTransaction(data))
.then(() => {
dispatch(getTransactions())
setTransactionName('')
setAmount('')
}
}
}

How to implement edit form with file upload

Trying to update a form without uploading a new image. I'm
using multer for the image upload. it works very well when i
create a form.
I'm using reactJs for frontend and node for the server side.
This is the front end code with react
import React, { useState, useEffect } from 'react';
import { useParams } from "react-router-dom";
import useFetch from "./fetch";
function EditForm() {
const { id } = useParams();
const { contestant: form, error, isPending } =
useFetch("http://localhost:5000/dashboard/form_single/" +
id);
const [name, setName] = useState('');
const [address, setAddress] = useState('');
const [fileName, setFileName] = useState('');
useEffect(() => {
setName(form.full_name);
setAddress(form.home_address);
}, [form])
const editForm = async (id) => {
try {
const formData = new FormData();
formData.append("name", name);
formData.append( "home_address", address);
formData.append("image", fileName);
const myHeaders = new Headers();
myHeaders.append("jwtToken", localStorage.jwtToken);
await
fetch(`http://localhost:5000/dashboard/form_update/${id}`, {
method: "PUT",
headers: myHeaders,
body: formData,
});
} catch (err) {
console.error(err.message);
}
};
const onChangeFile = e => {
setFileName(e.target.files[0]);
}
return (
<div>
{ isPending && <div>Loading...</div> }
{ error && <div>{ error }</div> }
<form encType="multipart/form-data"
onSubmit={() => editForm(form.form_id)}>
<input
type="text"
className="form-control"
value={name || ''}
onChange={e => setName(e.target.value)}
/>
<input
type="text"
className="form-control"
value={address || ''}
onChange={e => setAddress(e.target.value)}
/>
<input
type="file"
id="update"
name="image"
onChange={onChangeFile}
/>
<button type ="submit" >Save</button>
</form>
<div>
<img
alt="contestant"
src= {`http://localhost:5000/upload/${form.passport}`}
className="rounded-circle" style={{width: "100px",
height: "100px"}}/>
</div>
</div>
);
}
export default EditForm;
UNFORTUNATELY I GET Cannot read properties of undefined (reading 'filename'). I've tried to make multer image upload optional but it did'nt work. The code bellow is the api.
This is the server side code. Nodejs
router.put("/form_update/:id", upload.single('image'), async(req,
res) => {
try {
const { id } = req.params;
const image = req.file.filename;
const { name, home_address } = req.body;
const updateForm = await pool.query("UPDATE form SET
full_name = $1, home_address = $2, passport = $3 WHERE
form_id = $4
RETURNING *", [name, home_address, image, id]);
res.json("Form was updated");
} catch (err) {
console.error(err.message);
}
});
how do i not always have to change image everytime i need to edit a form.
on the server, check if fields are provided, and then store values for each, and construct query dynamically, and when done checking, execute query
you could list fields in an array and then iterate them and construct query and values against req.body
you could also add some validation (check if there is id etc., you could add that on the front-end as well)
try this:
router.put("/form_update/:id", upload.single('image'), async(req, res) => {
try {
// fields. same as in req.body
const fields = ['full_name', 'home_address', 'something', 'else'];
// store values
const values = [];
// dynamic query string
let stmt = [];
const {id } = req.params;
// add some validation
if(!id) {
console.error('no id..');
return res.json({msg:"err: no id"});
}
const image = req.file ? req.file.filename : '';
// build query
fields.map((field)=>{
if(req.body[field]) {
values.push(req.body[field]);
stmt.push(`${field} = $${values.length}`);
}
});
// check image, as it's not in req.body
if(image) {
values.push(image);
stmt.push(`passport = $${values.length}`);
}
// no data..end
if(!values.length) {
console.log('no data..');
return res.json({msg:'no data..'});
}
// finish
stmt = "UPDATE form SET " + stmt.join(', ');
values.push(id);
stmt += ` WHERE form_id = $${values.length}`;
stmt += ' RETURNING *';
const updateForm = await pool.query(stmt, values);
res.json({msg:"Form was updated"});
} catch (err) {
console.error(err.message);
}
});

How to print json api data in reactjs

I'm fetching json api details through GET request and trying to print it. Getting an error:
Error in the console is Uncaught ReferenceError: allUsers is not defined
const Dashboard = ({status, juser}) => {
const [allUsers, setAllUsers] = React.useState([]);
const id = juser.actable_id;
console.log(id); //getting id here as 1
const getAllusers = () => {
axios
.get(`http://localhost:3001/user/${id}`, { withCredentials: true })
.then((response) => {
console.log(response.data);
setAllUsers(response.data);
})
.catch((error) => {
console.log(" error", error);
});
};
React.useEffect(() => {
getAllusers();
}, []);
{allUsers.map((job_seeker, index) => {
return (
<div>
<p>{job_seeker.name}</p>
</div>
);
})}
}
export default Dashboard;
I'm new to react. Any help is appreciatable.
const [state, setState] = React.useState([]);
the state is where your data is located and setState is function to reset the state from anywhere,
so on your code,
const [jobseekers, allUsers] = React.useState([]); // change string to array
jobseekers is the variable where your data is located and allUsers is the function to store data into state.
set data to state using allUsers function,
const getAllusers = () => {
axios
.get(`http://localhost:3001/user/${id}`, { withCredentials: true })
.then((response) => {
allUsers(response.data);
})
.catch((error) => {
console.log(" error", error);
});
};
and map from jobseekers
{jobseekers.map((job_seeker, index) => {
return (
<div>
<p>{job_seeker.name}</p>
</div>
);
})}
Also I would suggest to rename your state and setState as,
const [allUsers, setAllUsers] = React.useState([]);
You didn't pass the value of response to allUsers, instead, you just created a new variable. So change
const allUsers = response.data;
to:
allUsers(response.data)
Besides, you can also improve the way that you have used useState. You have initialized it as an empty string while you'll probably store an array from response in jobseekers. So, initialize it as an empty array.
const [jobseekers, allUsers] = React.useState([]);

How can I make a search box in react-hooks?

I'm making a cocktail recipe web. If I search for the name of the cocktail, I want the cocktail to appear. The error message shown to me is as follows.
"TypeError: Cannot read property 'filter' of undefined"
Please tell me how to solve this problem. I'm a beginner. Is there a problem with my code?
This is Search.jsx
import React, { useState, useEffect } from "react";
import useFetch from "../Components/useFetch";
const Searchs = () => {
const url =
"https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita";
const [data] = useFetch(url);
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const handleChange = (event) => {
setSearchTerm(event.target.value);
};
useEffect(() => {
const results = data.drinks.filter(({ strDrink }) =>
data.strDrink.toLowerCase().includes(searchTerm)
);
setSearchResults(results);
}, [searchTerm]);
return (
<Wrapper>
<Search
type="text"
placeholder="재료 또는 이름을 검색하세요"
value={searchTerm}
onChange={handleChange}
/>
<ul>
{searchResults.map((item) => (
<li>{item}</li>
))}
</ul>
</Wrapper>
);
};
export default Searchs;
This is useFetch.jsx
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
}
useEffect(() => {
fetchUrl();
}, []);
return [data, loading];
}
export default useFetch;
This is JSON
{
"drinks": [
{
"idDrink": "12784",
"strDrink": "Thai Iced Coffee",
"strCategory": "Coffee / Tea",
"strIBA": null,
"strAlcoholic": "Non alcoholic",
"strGlass": "Highball glass",
"strDrinkThumb": "https://www.thecocktaildb.com/images/media/drink/rqpypv1441245650.jpg",
"strIngredient1": "Coffee",
"strIngredient2": "Sugar",
"strIngredient3": "Cream",
"strIngredient4": "Cardamom",
"strMeasure1": "black",
"strMeasure3": " pods\n",
"strImageAttribution": null,
"strCreativeCommonsConfirmed": "No",
"dateModified": "2015-09-03 03:00:50"
}
]
}
Do null check before filter(), Your API might return null/undefined you should handle such cases.
Bonus: onChange={handleChange} don't directly call API on change, add some denounce check, to improve performance.
useEffect(() => {
const results = data?.drinks?.filter(({ strDrink }) =>
data.strDrink.toLowerCase().includes(searchTerm)
) ?? [];
setSearchResults(results);
}, [searchTerm]);
you did many mistakes in this code look below how I did it
here you can find sandbox URL where you can see live working code
https://codesandbox.io/s/boring-tesla-hoc11?file=/src/App.js:75-1141
I have changed your wrapper to input element for testing you can revert it back
const Searchs = () => {
const url =
"https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita";
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const handleChange = (event) => {
setSearchTerm(event.target.value);
};
useEffect(() => {
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
const results = data.drinks.filter(({ strDrink }) =>
strDrink.toLowerCase().includes(searchTerm)
);
setSearchResults(results);
}
fetchUrl();
}, [searchTerm]);
return (
<>
<input
type="text"
placeholder="재료 또는 이름을 검색하세요"
value={searchTerm}
onChange={handleChange}
/>
<ul>
{searchResults.map((item,index) => (
<li key={index}>{item.strDrink}</li>
))}
</ul>
</>
);
};
It seems like your API is returning nothing. You should add a check to see if anything is returned from API:
ALSO: you have to include data which you get from useFetch to the useEffect dependencies, otherwise it's value won't be changed in each useEffect call:
useEffect(() => {
const results = data?.drinks?.filter(({ strDrink }) =>
data.strDrink.toLowerCase().includes(searchTerm)
) ?? [];
setSearchResults(results);
}, [searchTerm, data]);