How to chain actions in ReactJS? - reactjs

How to chain actions to get the url of image property when I fetch the post list.
I've made a request that fetch all posts, and it gives me a link for the property "image".
mywebsite/api/recipes?_page=1 :
{
"#context": "/api/contexts/Recipes",
"#id": "/api/recipes",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/recipes/524",
"#type": "Recipes",
"id": 524,
"category": "/api/categories/11",
"title": "NewPost",
"content": "This is a new post",
"time": "50 minutes",
"image": [
"/api/images/37"
],
"slug": "new-post",
"createdAt": "2020-06-30T10:26:00+00:00",
"comments": [
"/api/comments/1359",
"/api/comments/1360"
]
},
........
and the result for mywebsite/api/images/37 is :
{
"url": "/images/5efbe9a4a1404818118677.jpg"
}
now in my actions i have
export const recipesListError = (error) => ({
type: RECIPES_LIST_ERROR,
error
});
export const recipesListReceived = (data) => ({
type: RECIPES_LIST_RECEIVED,
data
});
export const recipesListFetch = (page = 1) => {
return (dispatch) => {
dispatch(recipesListRequest());
return requests.get(`/recipes?_page=${page}`)
.then(response => dispatch(recipesListReceived(response)))
.catch(error => dispatch(recipesListError(error)));
}
};
so the first request is recipesListFetch, now what is missing is the second request to get the image and then return the url so i can directly have access to the image for each post
the easy solution would have been to use normalization_context groups has
i'm working with symfony api platform but it still gives me a link for the image property, I think because it's a ManyToMany relation

There don't seem to have the need for normalisation. The images and comments are specific to the recipe.
Make the then block callback as async fun and inside then block loop thru the recipes array first and then loop thru the image array and make api call for the image and await for it.
export const recipesListFetch = (page = 1) => {
return (dispatch) => {
dispatch(recipesListRequest());
return requests
.get(`/recipes?_page=${page}`)
.then(async (response) => {
//make then callback as async fun
const recipes = response["hydra:member"];
const imagesForTheRecipie = [];
for (let i = 0; i < recipes.length; i++) {//loop thru recipies
for (let j = 0; j < recipes[i].image.length; j++) {//loop thru images for each recipie
const imageUrl = recipes[i].image[j];//grab the image url
const image = await requests.get(`/${imageUrl}}`);
imagesForTheRecipie.push(image);
}
recipes[i].image = imagesForTheRecipie; //mutate the object which will directly update the response
}
dispatch(recipesListReceived(response));
})
.catch((error) => dispatch(recipesListError(error)));
};
};
Note - If you want to normalise then you can choose to nomalise data for the categories as the same category will be used by many recipes. In that case you will have to re-structure your reducers.

Related

Can't use the data from API when app just starts

My data is undefined when the app is started but after the refresh, the data comes perfectly.
For startup
It gives me [Unhandled promise rejection: TypeError: Object.entries requires that input parameter not be null or undefined]
But after the refresh, the data comes perfectly and everything working.
This is part of my data
Object {
"attributes": Object {
"htmlName": null,
"id": 0,
"items": Array [
Object {
"htmlName": "r_1",
"name": "m2 (Brüt)",
"numeric": true,
"options": Object {},
"order": 0,
"required": true,
},
Object {
"htmlName": "r_2",
"name": "m2 (Net)",
"numeric": true,
"options": Object {},
"order": 0,
"required": true,
},
Object {
"htmlName": "r_164",
"name": "Arsa Alanı (m2)",
"numeric": true,
"options": Object {},
"order": 0,
"required": true,
},
Object {
"htmlName": "a_137",
"name": "Oda Sayısı",
"numeric": false,
"options": Object {
"12": "1+0",
"13": "1+1",
"14": "1.5+1",
"15": "2+0",
"16": "2+1",
"17": "2.5+1",
"18": "2+2",
"19": "3+1",
"20": "3.5+1",
"21": "3+2",
"22": "4+1",
"226": "0+1",
"23": "4.5+1",
"24": "4+2",
"25": "4+3",
"26": "4+4",
"27": "5+1",
"28": "5+2",
"29": "5+3",
"30": "5+4",
"31": "6+1",
"32": "6+2",
"33": "6+3",
"34": "7+1",
"35": "7+2",
"36": "7+3",
"37": "8+1",
"38": "8+2",
"39": "8+3",
"40": "8+4",
"41": "9+1",
"42": "9+2",
"43": "9+3",
"44": "9+4",
"45": "9+5",
"46": "9+6",
"47": "10+1",
"48": "10+2",
"49": "10 Üzeri",
},
"order": 0,
"required": true,
},
api.js
export const getData = function () {
return axios
.get(
"blabla",
{
headers: {
Authorization: `blabla`,
},
}
)
.then((json) => {
if (json && json.status === 200) {
//console.log(json);
return json.data;
}
})
.catch((e) => {
console.log(e);
});
};
App.js
const [data, setData] = useState({});
const [roomValue, setRoomValue] = useState(null);
const [roomCount, setRoomCount] = useState([]);
const [isFocus, setIsFocus] = useState(false);
useEffect(() => {
getDataFunc();
//setDropdown(data.attributes.items[3].options);
}, []);
const getDataFunc = async () => {
const res = await getData();
//console.log(res);
setData(res);
console.log(data);
};
function setDropdown(query) {
const response = query;
try {
const entries = Object.entries(response);
const tempArray = [];
for (let i = 0; i < entries.length; i++) {
var key;
var value;
(key = entries[i][0]), (value = entries[i][1]);
tempArray.push({ key: value, value: key });
}
setRoomCount(tempArray);
//console.log(roomCount);
} catch (error) {
//console.log(error);
}
}
How can I fix that ?
Add a seperate useEffect to check wheather the data has been set and then only set the dropdown values
useEffect(() => {
getDataFunc();
}, []);
useEffect(() => {
if(data && data.attributes?.items[3]){
setDropdown(data.attributes.items[3].options);
}
}, [data]);
const getDataFunc = async () => {
const res = await getData();
//console.log(res);
setData(res);
console.log(data);
};
It seems like the error is caused by the attributes property being empty when you try to access it. But when you assign them one by one then it loads because the data is loaded per nested property before assigning it to the variable. Means it hasn't fully loaded yet
const response = data.attributes.items[3].options;
It outputs an error because attributes is undefined. So it's not an object, therefore, attributes.items is considered invalid
// sample
const data = {
/* attributes: {
items: {
1: {
options: 'option1'
},
2: {
options: 'option2'
},
3: {
options: 'option3'
}
}
} */
}
const specificData = data.attributes.items[3].options
console.log(specificData) //
So one solution would be using the optional chaining operator to avoid the error, it's just basically a question mark (?) after the object you are trying to access.
The response would be then 'undefined'. That way even if the attributes is empty or not, data will be assigned to the response constant then you can just add some more checking outside of that.
// sample
const data = {
/* attributes: {
items: {
1: {
options: 'option1'
},
2: {
options: 'option2'
},
3: {
options: 'option3'
}
}
} */
}
const specificData = data.attributes?.items[3].options
console.log(specificData) // outputs undefined instead of an error
Let me know if this works btw. maybe you could provide the actual api or maybe a sample api endpoint so we could test it directly. Or maybe the full code?
I've encoutered this before though I'm not 100% sure this is all I've done. But for the error I'm sure the optional chaining operator will prevent it
Try calling getData inside an async function and wait for the process to complete like this in your App.js
const [data, setData] = useState([]);
const [roomCount, setRoomCount] = useState([]);
useEffect(() => {
getDataFunc()
}, []);
const getDataFunc = async() => {
await getData(setData);
const response = data;
console.log(response);
const entries = Object.entries(response);
const tempArray = [];
for (let i = 0; i < entries.length; i++) {
var key;
var value;
(key = entries[i][0]), (value = entries[i][1]);
tempArray.push({ key: value, value: key });
}
setRoomCount(tempArray);
console.log(roomCount);
}
note: The best practice is not to directly pass the setData function to getData api call instead return the response from api and assign the response in main code like below
const response = await getData();
setData(response)
From what I see, your data.attributes has undefined value.
Please double-check everything, it is technically impossible to get data directly if data.attributes is undefined

React with easy-peasy

My route:
const id = req.params.id
const event = await events.findByPk(id, {
include: [clubs]
})
return res.json(event)
})
Result in Postman:
{
"id": 12,
"title": "Title",
"description": "Short description",
"clubId": 1,
"club": {
"id": 1,
"clubname": "My Club"
}
}
On my page I'm getting data using useEffect calling my action and updating "currentEvent" in state:
const currentEvent = useStoreState(state => state.currentEvent)
const fetchEventById = useStoreActions(actions => actions.fetchEventById)
const { id } = useParams()
useEffect(() => {
fetchEventById(id)
}, [])
Destructuring data:
const { title, description, club } = currentEvent
This is working well, and state is updated:
<h1>{title}</h1>
This is not working at all. State will not be updated and the Console says "Cannot read properties of undefined (reading 'clubname')":
<h1>{title}</h1>
<h2>{club.clubname}</h2>
Any ideas?
I think you should wait data fetched correctly and then you can see your updated.
Please refactore your code by using ? like this club?.clubname

Pushing data to an array in already existing object with axios

i have a object which looks like this:
{
"title": "675756",
"release_date": "2022-01-16",
"series": "Better Call Saul",
"img": "https://upload.wikimedia.org/wikipedia/en/0/03/Walter_White_S5B.png",
"characters": [],
"id": 1
}
to an characters array i want to add the id of characters.
I do it by form and then i handle submit like this:
const handleSubmit = (values) => {
console.log("dodano aktora do filmu!");
console.log(values);
addActorToMovie(values);
history.goBack();
};
the addActorToMovie action:
export const addActorToMovie = (resp) => ({
type: types.ADD_CHAR_TO_MOVIE,
payload: resp,
});
and the reducer:
case types.ADD_CHAR_TO_MOVIE:
console.log(action.payload);
return {
...state,
...state.episodes.map(function (item) {
return item.id === action.payload.episodeId
? {
id: item.id,
title: item.title,
release_date: item.release_date,
series: item.series,
img: item.img,
characters: [...item.characters, action.payload.actor],
}
: { ...item };
}),
};
It all works, but the problem is that i dont want to do it loccaly. Im using an database with json-server, and I want to do an Axios Request so that it would add a data to the database.
And i don't know how to do this, when i use axios.post it adds an object to my episodes array, if im using axios.put it changes an object. Is there any possibility to push the data to an array as i do it with the code above, but with axios so that it would be added to database?
My approach looked like this:
export const addActorToMovieAxios = (value) => {
console.log(value);
return async (dispatch) => {
try {
const response = await axios.post(
`http://localhost:3000/episodes/`,
value
);
console.log(response);
dispatch(addActorToMovie(response.data));
} catch (ex) {
console.log(ex);
}
};
};
but as I said this does add a new object to an array.....
"episodes": [
{
"title": "675756",
"release_date": "2022-01-16",
"series": "Better Call Saul",
"img": "https://upload.wikimedia.org/wikipedia/en/0/03/Walter_White_S5B.png",
"characters": [],
"id": 1
},
{
"episodeId": 1,
"actor": "1",
"id": 2
}
]
So just to be clear I understand your question, you have an object that already exists in your DB, and you want to push something onto the 'characters' array in that existing object, without creating a new object, correct?
To do this, I would use Mongo for your DB and define two Mongoose Schemas, one for the existing object (let's call it TVShow) and one for the Characters within that object. Your two Schemas will look like this:
TVShowModel.js:
const mongoose = require('mongoose');
const CharacterModel = require('./CharacterModel')
const TVShowScheme = new mongoose.Schema({
title: {
type: String,
},
release_date: {
type: Date,
},
series: {
type: String,
},
img: {
type: String,
},
characters:[
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Student'
},
],
examQuestions: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'CharacterModel'
}
]
})
module.exports = mongoose.model('TVShowModel', TVShowScheme )
CharacterModel.js:
const mongoose = require('mongoose');
const CharacterModel= new mongoose.Schema({
characterName: {
type: String,
},
actorName: {
type: String,
},
}) // add any other fields you want here
module.exports = mongoose.model('CharacterModel', CharactModelScheme )
Then, create your Axios post request. Make sure you send when you send the 'value' variable to your server, it contains the id (or perhaps the unique title) of the object you'll be 'pushing' to. Push won't work in axios/react, so we'll use the 'spread' opperator instead.
Your router will look like this:
const CharacterModel= require ('../models/CharacterModel');
const TVShowModel= require ('../models/TVShowModel');
const router = express.Router();
router.post('/episodes', async function(req,res){
try{
const tvshow = await TVShowModel.find({title: req.body.title})
// edit as needed
console.log("FOUND TV Show: "+tvshow )
const characterName= req.body.characterName
const actorName = req.body.actorName
const newCharacter = new CharacterModel({
characterName,
actorName,
})
console.log("new character created: "+newCharacter)
tvshow[0].CharacterModel = [...tvshow[0].CharacterModel,newCharacter];
await tvshow[0].save()
.then(()=>res.json('New Character Added to DB'))
.catch(err=>res.status(400).json('Error: ' + err))
} catch(e){
console.log(e)
}
})
Hope this was clear!

Update data from array with multiple objects using Axios PUT request

I need to update data in an array that has multiple objects where a user will input a new balance that will update the old balance state. The array consists of a company name with an array called details, and that array holds objects containing employee information(name, balance, notes), for this question I am just using notes to simplify things. I am using Axios PUT to access the id of the nested object, I get the id from a Link that is passed via useParams hook.
My issue is in the Axios PUT request. Before I had a schema that was just a data object (no arrays were in it) and the PUT req was working fine. Then I needed to change the schema to an array with multiple objects and now I cannot seem to update the data. I am able to target the data through the console log but when I take that code from the console and apply it, the state still doesn't change. Even in Postman, the only way for me to successfully update is to get the Shema from a GET request and paste that schema in the PUT request and change some data in it, then I hit send and it updates, but to get it to update again I need to hit send twice (this shouldn't be, no? ).
I am able to access the data and render it in other components by mapping it twice as shown below:
setBalance(res.data.data.details.map((r) => r.balance));
My question: How can I edit the below code to update the state correctly?
setNotes([...details, res.data.data.details.map((r) => r.notes )]);
However, I am really struggling with how to do this in the Axios PUT request.
Here is my code:
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { useParams } from "react-router-dom";
import axios from "axios";
const AddForm = () => {
const [newBalance, setNewBalance] = useState("");
const [details, setDetails] = useState([]);
const [notes, setNotes] = useState("");
const [total, setTotal] = useState("");
const { id } = useParams();
const history = useHistory();
//update balance
const updateBal = () => {
// function to calculate balance
};
const updateBalHandler = (e) => {
e.preventDefault();
axios({
method: "PUT",
url: `http://localhost:5000/update-snapshot-test/${id}`,
data: {
balance: total
notes: notes
},
}).then((res) => {
history.push(`/success/` + id);
setNotes([...details, res.data.data.details.map((r) => r.notes )]); //this code isolates the notes state but does not update it
});
};
return (
<form
action="/update-snapshot/:id"
method="post"
onSubmit={updateBalHandler}
>
<Input
setInputValue={setNewBalance}
inputValue={newBalance}
inputType={"number"}
/>
<Input
setInputValue={setTotal}
inputValue={total}
inputType={"number"}
/>
<TextArea
setInputValue={setNotes}
inputValue={notes}
inputType={"text"}
/>
<Button onClick={() => { updateBal(); }} >
Update
</Button>
<Button type="submit">
Save
</Button>
</form>
);
};
export default AddForm;
Here is my data structure from Mongo DB
{
"message": "Post found",
"data": {
"company": "Riteaid",
"_id": "1",
"details": [
{
"employee": "jane doe",
"balance": "3",
"notes": "some notes",
"_id": "2"
},
{
"employee": "john doe",
"balance": "1",
"notes": "some more notes",
"_id": "3"
}
],
}
}
You have the id, so you have to search for the relevant object, update it and pass it to the setNotes() setter.
let localNotes = res.data.data.details.map((responseDetail) => {
if (detail._id === id){
let newNotes = [...responseDetail.notes, ...details];
return {
...responseDetail,
notes: newNotes
};
}
return responseDetail;
});
if (localNotes.length){
setNotes(localNotes);
}
Does this solve your problem?
The answer was in the backend, the front end was fine, the code did not need any of the additions, it should just be:
const addBalHandler = (e) => {
e.preventDefault();
axios({
method: "PUT",
url: `http://localhost:5000/endpoint${id}`,
data: {
balance: total,
notes: notes,
date: date,
},
}).then((res) => {
history.push(`/success/` + id);
console.log(res.data);
});
};

Storing images' URLs from firebase storage into firebase database - URLs are getting lost in cyberspace

It's a React app with Redux. A form collects a new recipe data from user. The data may include an array of file objects (images) selected by user for upload. I call it imagesToAdd. An action is called startAddRecipe. The following needs to happen:
Write new recipe to firebase database - this returns a ref.key needed for storing images in storage. - this works ok
If there are imagesToAdd a function uploadImages is called that uploads images to firebase storage and returns an array with uploaded images URLs. this works ok
Now the recipe created in (1.) above needs to be updated with the URLs obtained in (2.) - this does NOT work. console.logs are showing the URLs alright but they are not appearing in firebase database:
images
imageNames
0: "voteUpSmiley.png"
(no imageUrls)
...nor in the Redux store:
images: {
imageNames: [
'voteUpSmiley.png'
],
imageUrls: []
},
Oddly the redux-logger tool shows the data ok in console:
images:
imageNames: ["voteUpSmiley.png"]
imageUrls: ["https://firebasestorage.googleapis.com/v0/b/mniam-…=media&token=0e9b7991-0314-4f24-a94a-6b24f93baed7"]
The function uploadImages contains asynchronous tasks - firebase storage upload and a call for URLs so I await for the result and am getting correct one but it's not passed in time to subsequent statements because as said before the firebase database and the redux store are not getting the URLs. I've been looking at this for 2 days and seem to be going in circles.
I include the relevant code below for good people caring to have a look at it. Thank you.
export const startAddRecipe = (recipeData = {}) => {
return (dispatch, getState) => {
const {
authorEmail = '',
brief= '',
createdAt = 0,
ingredients = { general: [] },
keyWords = [],
preparation = { general: [] },
publishedAt = 0,
shared = false,
tips = '',
title = '',
votes = {
downs: [],
ups: [],
},
imagesToAdd = [],
} = recipeData
let imageNames = []
imagesToAdd.map(image => {
imageNames.push(image.name)
})
let recipe = {
authorEmail,
brief,
createdAt,
images: {
imageNames,
imageUrls: [],
},
ingredients,
keyWords,
preparation,
publishedAt,
shared,
tips,
title,
votes,
}
console.log('recipeB4', recipe); //this is showing URLs even before image upload???
database.ref(`recipes/`).push(recipe).then((ref) => {
console.log('ref.key:', ref.key);
if (imagesToAdd.length > 0) {
(async () => {
recipe.images.imageUrls = await uploadImages(ref.key, imagesToAdd)
console.log('recipeAfterImageUpload', recipe); // URLS are shown here but not reflected in the next line
database.ref(`recipes/${ref.key}`).update(recipe).then(() => {
console.log('RECIPE ADDED & UPDATED');
})
})()
}
dispatch(addRecipe({
id: ref.key,
...recipe,
}))
dispatch(startSetRecipeKeyWords())
})
}
}
const uploadImages = (id, imagesToAdd) => {
let imageUrls = []
imagesToAdd.map(image => {
const uploadTask = storage.ref(`/recipePics/${id}/${image.name}`).put(image)
uploadTask.on('state_changed',
(snapShot) => {
// console.log(snapShot)
},
(err) => {
// console.log(err)
},
() => {
storage.ref(`recipePics/${id}`).child(image.name).getDownloadURL()
.then(fireBaseUrl => {
console.log('fireBaseUrl', fireBaseUrl)
imageUrls.push(fireBaseUrl)
})
})
})
return imageUrls
}

Resources