.map() the data from API in useEffect react - reactjs

I am kinda new at react and trying to make two axios get call according to results of first call as you can see in my code. Eventually I am trying to .map the response data in swiper component. However my code is not working properly. The problem is, the data from the first call is displayed on the component properly while the images from the second call are not.
const [picks, setPicks] = useState([]);
const getAllPicks = async () => {
try {
axios.get(".../shoppicks").then(async (response) => {
const responseData = await response.data.result;
for (let i = 0; i < responseData.length; i += 1) {
axios.post(".../getimage", { shopID: response.data.result[i]._id }).then((res) => {
if (res.data.header.arg === "No image found") {
// dummy photo for corrupted responses
responseData[i].imgPath = "#"
} else {
responseData[i].imgPath = res.data
}
})
}
return responseData
}).then((responseData) => {
setPicks(responseData)
console.log(responseData) row 38 logs the data properly
console.log(picks) row 39 logs empty array
})
.catch((error) => console.log(JSON.stringify(error)));
}
catch (error) {
console.log(error)
};
}
useEffect(() => {
getAllPicks();
}, []);
Here where I try .map the data
{picks?.map((pick: IHomePickType) => (
<><SwiperSlide key={pick._id}>
<CardMedia image={pick.imgPath} component="img" />
<div>
<h2>{pick._id}</h2>
</div>
</SwiperSlide></>
))}
Additionally it throws "Each child in a list should have a unique "key" prop." console error even though the picks data has unique keys

I've updated my code according the comments. It is working now.
here is the final code
const [picks, setPicks] = useState([]);
const getAllPicks = async () => {
try {
const response = await axios.get("../shoppicks");
const data = await response.data.result;
for (let i = 0; i < data.length; i += 1) {
const imgResponse = await axios.post("../getimage", { shopID: data[i]._id });
if (imgResponse.data.header.arg === "No image found") {
// dummy photo for corrupted responses
data[i].imgPath = "#"
} else {
data[i].imgPath = imgResponse.data.result.imgPath
}
}
setPicks(data)
}
catch (error) {
console.log(error)
};
}
component =>
{picks?.map((pick: IHomePickType) => (
<SwiperSlide key={pick._id}>
<CardMedia image={pick.imgPath} component="img" />
<div>
<h2>{pick._id}</h2>
</div>
</SwiperSlide>
))}
Thanks

Related

I need to listen update phase (in life cycle) without constantly sending get requests in react JS

I am trying to write a program that communicates with the client frequently and I need to quickly notice the changes and read the result from the server. But this request is constantly being sent and loaded, even when the user is not interacting. And this causes the user's system to be occupied.
this is mu code:
const AdminDashboard = () => {
const [filterShow, setFilterShow] = useState({ sort: "", read: "", flag: "", skip: 0, limit: 15 });
const [adminItemList, setAdminItemList] = useState([]);
const [searchParams, setSearchParams] = useSearchParams();
async function changeItem(updateItem) {
// update admin item state
await axios.put("{...}/api/admin", { ... });
}
useEffect(() => {
async function resultItem() {
// get result(admin items)
await axios.get(`{...}/api/admin?${searchParams.toString()}`)
.then((res) => {
setAdminItemList(res.data.data);
}).catch((res) => {
console.log(res)
});
}
resultItem();
})
return (<>
{adminItemList.map((ai, i) => {
return (<div key={i}>
<AdminItem item={ai} count={i} skip={filterShow.skip} res={changeItem} />
</div>)
})}
</>);
}
I know that I can avoid resending the request by using "useEffect" and passing an empty array to its second input. But I need to listen the changes so I can't do that.
How can i listening the changes and prevent repeated get requests???
The only solution I could find:
I moved the function "resultItem" outside of useEffect. And I wrote useEffect with an empty array in the second entry. then call "resultItem" in useEffect.
I gave this function "resultItem" an input to receive a query so that it can be flexible
I called it wherever I needed it
Note: In async functions, I first put that function in the Promise and call the "resultItem" in then().
I will put all the written codes here:
const AdminDashboard = () => {
const usePuCtx = useContext(PublicContext);
const { lang, dir } = usePuCtx.language;
// base state
const [allAdminItem, setAllAdminItem] = useState(0);
const [filterShow, setFilterShow] = useState({ sort: "", read: "", flag: "", skip: 0, limit: 3 });
const [adminItemList, setAdminItemList] = useState([]);
// validate for show this page
const [isAdmin, setIsAdmin] = useState(false);
const [searchParams, setSearchParams] = useSearchParams();
async function setResalt(radioName, radioState) {
// set setting on query for get result(admin items)
let newFilter = { ...filterShow };
newFilter[radioName] = radioState;
const queryStr = queryString.stringify(newFilter);
setSearchParams(queryStr);
new Promise((res, rej) => { res(setFilterShow(newFilter)) })
.then(() => resultItem(queryStr));
}
async function changeItem(updateItem) {
// update admin item state (read & flag)
await axios.put(usePuCtx.ip_address + "/api/admin",
{ _id: updateItem._id, read: updateItem.read, flag: updateItem.flag })
.then(() => resultItem(searchParams));
}
function showSkipPage(numberPage) {
const nextResult = numberPage * filterShow.limit
setResalt("skip", nextResult)
}
async function resultItem(query) {
// get result(admin items)
await axios.get(usePuCtx.ip_address + `/api/admin?${query.toString()}`)
.then((res) => {
setIsAdmin(true);
setAdminItemList(res.data.data);
}).catch(() => {
// if auth token is wrong
window.location = "/not-found";
});
// get all admin item number
await axios.get(usePuCtx.ip_address + "/api/admin?limit=0&skip=0&flag=&read=&sort=close")
.then((res) => {
setIsAdmin(true);
setAllAdminItem(res.data.data.length);
}).catch(() => {
// if auth token is wrong
window.location = "/not-found";
});
}
useEffect(() => {
resultItem(searchParams);
}, []);
return (!isAdmin ? "" : (<>
<div className="container-fluid" dir={dir}>
<div className="row m-4" dir="ltr">
{/* radio buttons */}
<RadioFilterButton name="read" id1="read-" id2="read-false" id3="read-true"
inner1={txt.radio_filter_button_all[lang]} inner2={txt.radio_filter_button_read_no[lang]}
inner3={txt.radio_filter_button_read[lang]} res={setResalt} />
<RadioFilterButton name="flag" id1="flag-" id2="flag-false" id3="flag-true"
inner1={txt.radio_filter_button_all[lang]} inner2={txt.radio_filter_button_flag_no[lang]}
inner3={txt.radio_filter_button_flag[lang]} res={setResalt} />
<RadioFilterButton name="sort" id1="sort-close" id2="sort-far"
inner1={txt.radio_filter_button_close[lang]} inner2={txt.radio_filter_button_far[lang]}
res={setResalt} />
</div><hr />
<div className="m-4" style={{ minHeight: "100vh" }}>
{
// show result(admin items)
adminItemList.map((ai, i) => {
return (<div key={i}>
<AdminItem item={ai} count={i} skip={filterShow.skip} res={changeItem} />
</div>)
})
}
</div>
<div className="row justify-content-center mt-auto" dir="ltr">
<PageinationTool countAll={allAdminItem} limitCard={filterShow.limit} skipNum={filterShow.skip} res={showSkipPage} />
</div>
<Footer />
</div>
</>));
}

Trying to get data from api and map to another component in React

I'm trying to map an array of movies which I get from an API.
The data is fetched successfully but when I try to map the values and display, it becomes undefined and does not show anything.
I'm new to React so any help and advice would be helpful.
const [items, setItems] = useState([]);
const getMovieData = () => {
axios
.get(api_url)
.then((response) => {
const allMovies = response.data;
console.log(allMovies);
setItems(allMovies);
})
.catch((error) => console.error(`Error: ${error}`));
};
useEffect(() => {
getMovieData();
}, []);
return (
<div>
{items.map((item) => {
<p>{item.title}</p>;
})}
</div>
);
The data is stored like this:
0: {
adult: false,
backdrop_path: '/9eAn20y26wtB3aet7w9lHjuSgZ3.jpg',
id: 507086,
title: 'Jurassic World Dominion',
original_language: 'en',
...
}
You're not returning anything from your map
{
items.map((item) => {
// Add a return
return <p>{item.title}</p>
})
}
First, your items value is an empty array[] as you have initialized with setState([]) and your useEffect() runs only after your component is rendered which means even before you could do your data fetching, your HTML is being displayed inside which you are trying to get {item.title} where your items is an empty array currently and hence undefined. You will face this issue often as you learn along. So if you want to populate paragraph tag with item.title you should fast check if your items is an empty array or not and only after that you can do the mapping as follow and also you need to return the element from the map callback. If it takes some time to fetch the data, you can choose to display a loading indicator as well.
const [items, setItems] = useState([]);
const getMovieData = () => {
axios.get(api_url)
.then((response) => {
const allMovies = response.data;
console.log(allMovies);
setItems(allMovies);
}).catch(error => console.error(`Error: ${error}`));
};
useEffect(() => {
getMovieData();
}, []);
return ( < div > {
items.length !== 0 ? items.map((item) => {
return <p > {
item.title
} < /p>
}) : < LoadingComponent / >
}
<
/div>
);
Good catch by Ryan Zeelie, I did not see it.
Another thing, since you're using promises and waiting for data to retrieve, a good practice is to check if data is present before mapping.
Something like :
return (
<div>
{ (items.length === 0) ? <p>Loading...</p> : items.map( (item)=>{
<p>{item.title}</p>
})}
</div>
);
Basically, if the array is empty (data is not retrieved or data is empty), display a loading instead of mapping the empty array.

How can I persist active cameras as it is, and update only added/removed user's camera on each new connection?

I'm developing a Video chat app with React, Redux and Zoom Web SDK.
What I'm trying to do is create camera (canvas API) for each user on room enter, then removing on room leave.
I implemented the feature above, but every time new participants enter or current participants leave, all cameras reset and turn off.
I think this has something to do with states, and rerendering.
Here's my core code snippet of the feature.
const Session = () => {
... otherStates
const stream = useRef({})
const [ sessionUsers, setSessionUsers ] = useState([]) // This is the state for currentParticipants.
useEffect(() => {
... otherListeners.
client.on('peer-video-state-change',async(payload) => {
const { action, userId } = payload
try {
await participantsRender(action,userId)
} catch (error) {
console.log(error)
}
})
client.on('user-added', (user) => {
const participants = client.getAllUser();
setSessionUsers(participants)
})
client.on('user-removed', (user) => {
const participants = client.getAllUser();
setSessionUsers(participants)
})
},[client])
const participantsRender = async(action,userId) => {
if (!action || !userId) return console.log('Empty Param')
const canvas = document.querySelector(`.canvas[data-canvas_id="${userId}"]`)
if (action === 'Start'){
await stream.current.renderVideo(canvas, userId, 640, 360, 0, 0, 2);
} else if (action === 'Stop') {
await stream.current.stopRenderVideo(canvas, userId);
} else {
console.log('error')
}
}
const toggleVideo = async() => { // This is a feature to start/stop video for the user.
if (!stream) return
const uid = client.getCurrentUserInfo().userId
const canvas = document.querySelector(`.canvas[data-canvas_id="${uid}"]`)
if(!stream.current?.isCapturingVideo()) {
try {
await stream.current?.startVideo();
stream.current?.renderVideo(canvas, uid, 640, 360, 0, 0, 2);
} catch (error) {
console.log(error)
}
} else if (stream.current.isCapturingVideo()){
try {
await stream.current.stopVideo()
stream.current?.stopRenderVideo(canvas, uid)
} catch (error) {
console.log(error)
}
}
}
const CanvasRenderer = React.memo(( { state } ) => {
if (!state) return <></>
return state.map((item,index) => (
<div className = "canvas_container" key = {item.userId}>
<span className = "canvas_name_container">
<p className = "canvas_name_self">{item.userId || ''}</p>
</span>
<canvas className = "canvas" data-canvas_id = {item.userId}></canvas>
</div>
))
})
return (
<div>
<div className = "canvas_list_container">
<CanvasRenderer state = {sessionUsers} /> // This is the renderer for cameras.
</div>
</div>
)
}
It seems that you are using multiple canvases for video rendering. This is not recommended. If you need to manage multiple participants in a session, we suggest that you can maintain a participant list, use a single canvas, and programmatically decide which should draw on the canvas. If you are familiar with React, you can refer to the react-sample-app.

Why does React state return undefined but page still loads from state OK?

I am attempting to develop a React app which makes a call to a database to load a set of pages to a board to build a drag and drop decision tree.
I am only just starting out with React, so keen to hear about anything I'm doing wrong here.
Using 'useEffect' the pageTree function will load the pages up on the first load and on every refresh, however the pages state returns with an empty array instead of the current pages.
Strangely enough the pages all show up on the board with the pages.map function which works on the pages state... (which returns as empty on console.log...)
If I add a page to the array it saves the change to the database, but then will only show the new page on the board. You will then have to refresh to see the new set of pages (including the added page).
Calls to add or delete a page are called by the layout menu buttons in the parent component.
Console after refresh
Additionally, if I move a page, the state will console OK:
Page state in console after moving a page. DB call and state update works OK
function PageTree({AddNewPageFunc}) {
const [pages, setPages] = useState([]);
const movePage = useCallback((droppedPage) => {
const updatedPages = pages.map(page => droppedPage._id == page._id ? droppedPage : page);
setPages(updatedPages);
}, [pages]);
const [{isOver}, drop] = useDrop(() => ({
accept: ItemTypes.PAGECARD,
drop(page, monitor) {
const delta = monitor.getDifferenceFromInitialOffset();
let x = Math.round(page.x + delta.x);
let y = Math.round(page.y + delta.y);
page.x = x;
page.y = y;
movePage(page);
setNewPagePosition(page);
return undefined;
},
}), [movePage]);
const setNewPagePosition = async (pageDetails) => {
console.log("function called to update page position");
Api.withToken().post('/pageupdate/'+pageDetails._id,
pageDetails
).then(function (response) {
console.log("moved page: ",response.data)
}).catch(function (error) {
//console.log(error);
});
}
React.useEffect(() => {
AddNewPageFunc.current = AddNewPage
}, [])
const AddNewPage = useCallback(() => {
console.log("calling add new page function")
console.log("the pages before the API call are ",pages)
Api.withToken().post('/addblankpage/'
).then(function (response) {
console.log("produced: ",response.data);
setPages(pages.concat(response.data))
console.log("the pages after updating state are: ",pages)
}).catch(function (error) {
//console.log(error);
});
}, [pages]);
const handleDelete = async (id) => {
Api.withToken().post('/deletepages/'+id
).then(function (response) {
let index = pages.findIndex(function(item){
return item.id === response.data._id;
});
const PageRemoved = pages.splice(index, 1);
setPages(PageRemoved);
}).catch(function (error) {
//console.log(error);
});
}
useEffect(() => {
Api.withToken().get('/pages/')
.then(res => {
setPages(res.data);
console.log('res data ',res.data);
console.log('pages ',pages);
})
}, []);
return (
<div ref={drop} style={styles}>
{pages.map((page) => (<PageCard page={page} id={page._id} key={page._id} handleDelete={() => handleDelete(page._id)} handleMaximise={() => handleMaximise(page)} handleCopy={() => handleCopy(page)}/>))}
</div>
)
}
export default PageTree;
As Danielprabhakaran pointed out, the issue was the callback in React.useEffect. On adding a new page it needed to send the updated page state back to the parent component.
Using console.log on a state after an API call seems to be fraught, even if using .then(console.log(state)
function PageTree({AddNewPageFunc}) {
const [pages, setPages] = useState([]);
const movePage = useCallback((droppedPage) => {
const updatedPages = pages.map(page => droppedPage._id == page._id ? droppedPage : page);
console.log("updated pages ",updatedPages);
setPages(updatedPages);
console.log("set pages ",pages);
}, [pages]);
const [{isOver}, drop] = useDrop(() => ({
accept: ItemTypes.PAGECARD,
drop(page, monitor) {
const delta = monitor.getDifferenceFromInitialOffset();
let x = Math.round(page.x + delta.x);
let y = Math.round(page.y + delta.y);
page.x = x;
page.y = y;
movePage(page);
setNewPagePosition(page);
return undefined;
},
}), [movePage]);
const setNewPagePosition = async (pageDetails) => {
console.log("function called to update page position");
Api.withToken().post('/pageupdate/'+pageDetails._id,
pageDetails
).then(function (response) {
console.log("?worked ",response)
}).catch(function (error) {
//console.log(error);
});
}
React.useEffect(() => {
AddNewPageFunc.current = AddNewPage
}, [pages])
const AddNewPage = useCallback(() => {
console.log("calling add new page function")
console.log("the pages before the API call are ",pages)
Api.withToken().post('/addblankpage/'
).then(function (response) {
console.log("produced: ",response.data);
setPages(pages.concat(response.data))
console.log("the pages after updating state are: ",pages)
}).catch(function (error) {
//console.log(error);
});
}, [pages]);
const handleDeletedCallback = (deletedIndex) => {
console.log("delete callback fired")
setPages(pages.splice(deletedIndex, 1));
}
useEffect(() => {
Api.withToken().get('/pages/') //can add in a prop to return only a given tree once the app gets bigger
.then(res => {
setPages(res.data);
console.log('res data ',res.data);
console.log('pages ',pages);
})
}, []);
return (
<div ref={drop} style={styles}>
{pages.map((page, index) => (<PageCard page={page} id={page._id} key={page._id} index={index} deleteCallback={handleDeletedCallback} handleMaximise={() => handleMaximise(page)} handleCopy={() => handleCopy(page)}/>))}
</div>
)
}
export default PageTree;

Cannot map through AsyncStorage data

I try to build react-native app with AsyncStorage with multiGet property. I can sucessfully add stuff to storage, and even display it in debugger console.log but when I want to map through array of data, it constantly shows no result. Does anyone knows where is the problem?
componentDidMount() {
this._getAllData();
}
_getAllData = async () => {
try {
const data = [];
const keys = await AsyncStorage.getAllKeys();
const items = await AsyncStorage.multiGet(keys, (err, stores) => {
stores.map((result, i, store) => {
// let key = store[i][0];
let value = store[i][1];
let parsedValue = JSON.parse(value);
data.push(parsedValue);
});
this.setState({ data });
});
} catch (error) {
this.setState({ error });
}
};
_displayAllData = () => {
// console.log(this.state.data.length);
// console.log(this.state.data);
this.state.data.length &&
this.state.data.map(el => {
return (
<View>
<Text>{el.name}</Text>
<Text>{el.street}</Text>
<Text>{el.postalCode}</Text>
<Text>{el.city}</Text>
<Text>{el.phone}</Text>
<Text>{el.email}</Text>
<Text>{el.nip}</Text>
</View>
);
});
};
and then while rendering my content i try
{this._displayAllData()}
Currently your _displayAllData method is not returning anything. You need to return the map result in order to have some JSX returned by your method.
_displayAllData = () => {
// console.log(this.state.data.length);
// console.log(this.state.data);
return this.state.data.length &&
this.state.data.map(el => {
return (
<View>
<Text>{el.name}</Text>
<Text>{el.street}</Text>
<Text>{el.postalCode}</Text>
<Text>{el.city}</Text>
<Text>{el.phone}</Text>
<Text>{el.email}</Text>
<Text>{el.nip}</Text>
</View>
);
});
};

Resources