Filtering fetched array in render React - arrays

New to React here so please bear with me. I fetched a list of data from a server and that I put in the State though the componentDidMount function. In it, I push the data to my State array named solo. It works great until I try to render the array. I need to filter my array to map the data based on one property (categ) matching a property from another array, and create buttons in the right category. When I run my code, I only get one button rendered, whichever is first in my solo array, in which all my data appears. I'm sure it has someting to do with the asynchronous nature of fetch, but I don't understand what.
Here's my code :
componentDidMount(){
// Get URLS from config
const data = this.props.config.layerUrls
// Init state values
let categ = [...this.state.categ]
let solo = [...this.state.solo]
// Loop through categories
for (let i = 0; i < data.length; i++) {
let donneesCateg = data[i]
let listURLS = donneesCateg["urls"]
categ.push({['categ']: donneesCateg["name"], ['type']: "categ", ['urls']: listURLS })
this.setState({ categ : categ })
// Loop through individual URL data
for (let a = 0; a < listURLS.length; a++) {
fetch(listURLS[a] + "?f=pjson")
.then(res => res.json())
.then(data => {
// Push to state array
solo.push({['categ']: donneesCateg["name"], ["type"]: "solo", ["couche"]: data.name, ["url"]: listURLS[a] })
this.setState({ solo : solo })
})
}
}
}
render() {
{
return (
<p className="shadow-lg m-3 p-1 bg-white rounded">
{this.state.categ.length!=0 ? this.state.categ.map(item => {
return (
<div>
<button id={item.categ} className="categ" value={item.urls} onClick={this.selectChangeMultiple}>{item.categ}</button>
{this.state.solo.length!=0 ? this.state.solo.filter(solo => solo.categ == item.categ).map(data =>
<button id={data.categ} className="btn" value={data.url} onClick={this.selectChangeSingle}>{data.couche}</button>
) : <p id="loadingMsg">Solo data Loading...</p>}
</div>
)
}) : <p id="loadingMsg">Categ data Loading...</p>}
</p>
</div>
);
}
}
Note : I have to go through this loop system because the URLs I use to fetch the data are in a JSON in which they are stored by categories.
Many thanks.

In your code, any subsequent call to setState({solo: solo}) isn't recognized as an update by React. This happens because solo is always the same "object" (although mutated with push, but React comparison algorithm doesn't go so far as to compare the new and previous. It just compares the old and new solos with something like ===.
An obvious fix would be to call this.setState({ solo : [...solo] }) instead of this.setState({ solo : solo }), although it would still may cause too many extra rerenders. But it'd be a good start.

Related

How can I create a parent html element by appending sub element from an object?

In my react app I need to return a line which will be created based on a list.
Here is the object,
searchCriteria: {
op_company: "039",
doc_type: "ALL"
}
and in my UI, i need to show it as a paragraph with bold values. So the hard coded code would be like below
<p>Download request for op_company: <b>{searchCriteria.op_company}</b>, doc_type: <b>{searchCriteria.doc_type}</b></p>
But the object(searchCriteria) will be changed based on the user request. So I tried like below.
const getSearchCriteria = (criteria) => {
let searchCriteria = []
searchCriteria.push('Download request for')
Object.keys(criteria).forEach((key) => {
if(criteria[key] !== '') {
searchCriteria.push(` ${key}: ${criteria[key]},`)
}
});
return searchCriteria;
}
return (
<p>
{getSearchCriteria(searchCriteria).map((item) => <span key = {item}>{item}</span>)}
</p>
);
here i'm getting the expected output. But I can't get the value as bold (highlighted). Is there another way to directly deal with html elements?

compare an object inside a map function React

I try to render a mapped list of activity based on props. Layout of those props are:
completedActivity:
message:
status:
time:
type:
progressId:
time:
userId:
I need to compare the completedActivity.progressId to another set of props.
logData:
timestamp:
taskRunId:
userName:
I need to see where completedActivity.progressId = logData.taskRunId. If they are the same I need to grab the logData.userName.
Here is the code to render out the activities. It is working just, but I need to add additional info to them. As of right now it will render activity.userId, which is a bind user and not the actual user. If they are not the same, it needs to render the bind user.
const ActivityList = props => {
const activityList = props.data.completedActivity.map(activity => (
<li
class="collection-item"
onClick={() =>
activity.messageDetails
? props.toggleMessageDetails(true, activity.messageDetails)
: false
}
>
<p>{activity.type}</p>
<p className="message">{activity.message}</p>
{/*
displays who last ran that activity and when they did. Need to come up with a better naming solution.
*/}
<div class="whodiditandwhen">
<span>{activity.userId}
</span>
{/*<span>{activity.userId}</span>*/}
<span>{new Date(activity.time).toLocaleString()}</span>
</div>
{/*
this will allow the user to click on the icon and see more detailed info on the activity. NOT ALL ACTIVITES HAVE THIS
*/}
{activity.messageDetails}
</li>
));
return (
<ul className="activity-list">{activityList}
</ul>
);};
Here is a screenshot of how it is rendered now.
Thank you
This is what I tried:
const userNameLog = props.data.completedActivity.map(activity => {
let result = props.logData.find(log => log.taskRunID === activity.progressId)
let userName = ""
if(result === undefined){
userName = activity.userId
} else {
userName = result
}
console.log(userName)
}
)
This works to some degree except it gets rendered multiple times.
I was able to solve this issue inside of the axios call that is getting the data before the page renders it.
export function getActivityUpdates(options, updateProgress) {
axios
.post(URL, qs.stringify(options))
.then(response => {
// Null out saved currentActivityID if no live activity.
handleCurrentActivityStorage(response.data.liveActivity);
let changedIDs = [];
let dataResponse = response.data;
dataResponse.userId = ". . ."
/*
iterate over the completed activity and the log data.
compare progress id to taskRunId.
logData is from highest to lowest id.
completedActivity is also from high to low but puts parents above children.
check and make sure that it has a bind user written to it.
Then if taskrunid is = to or less than the progressId. change it and add it to the changedIDs array.
the ChangedIds array makes sure that ids are not iterated over multiple times.
set the userID to the actual username found in the logs.
*/
dataResponse.completedActivity.forEach(data => {
options.logData.forEach(log => {
//
if (
data.userId === options.bindUsername &&
!changedIDs.includes(data.progressId) &&
(log.taskRunID === data.progressId ||
log.taskRunID < data.progressId)) {
changedIDs.push(data.progressId)
data.userId = log.magUserName;
}
})
});
updateProgress(dataResponse);
// Exit function if a task is not running
if (!response.data.liveActivity.length) {
return;
}
// Get updated data every second until task is complete
setTimeout(
getActivityUpdates.bind(null, options, updateProgress),
1000
);
})
.catch(() => showErrorMessage(options.command));
}

react address lookup issue with changing final input value

I am building an address lookup functionality for an app, and my previous 2 questions have thus far, been unable to garner an answer. I am on the last step, and need to figure this out, so I turn to you stack overflow.
I start with an input, a button and a target input:
<input type='text' name='postcode-lookup' onChange={this.postcodeChange} />
<button onClick={this.searchPostcode}>Search</button>
<input type='text' name='target-for-data' />
Easy enough. Now for the functions attached to both of those elements:
postcodeChange = {e} => {
this.setState({'postcode': e.target.value});
}
searchPostcode = () => {
this.setState({'visible': true});
if(this.state.postcode.length . 0){
Axios.get('postcode look up api here')
.then(response => {
this.setState({'response': response.data});
})
}
}
Ok here, we have 3 state items: postcode, which we will set to an empty string '', visible, which is initially set to true, and response, which is an empty array, that we then populate with the response data of address objects.
My next step, was to display those addresses, so inside the render, I set a const that maps over the response array like so:
const result = this.state.response.map((item) => {
if(this.state.visible === true){
return(
<p key={item.id} onClick={addressClick}>{item.address1Field}</p>
)
}else {
}
})
Ok, so when we click the button, it will return a p tag filled with address data from the array. This also has a function, which is where my problem lies.
Inside of this function I set the visible state item to false, so that the addresses disappear. Easy enough.
But how do I then take the address that I clicked on, and populate it into the original input we started with?
I have tried many things, from setting the state in addressClick, targeting the innerHTML, e.target.value, e.target.innerHTML, and so on and so on for hours.
Any takers? Ideas?

Buggy movement of Object in Array with Redux and React

Basically I'm trying to move a single object within an Array of object, but when I move the same object once or twice it starts moving the other object in said array.
So I have tried making a new Array with .slice() then .shift(item) by it's index and then add it back in at the right index with .splice(newIndex, 0, item), once the array has been update I push it to the Redux store which updates my Megadraft(ie Draft.js) application.
I have also tried directly manipulating the original array, ie this.props.array (like your meant too with Redux) and using the keys inside of the objects instead of the indexes.
import React from 'react';
import { MegadraftPlugin, DraftJS, CommonBlock } from "megadraft"
export default class ImageGalleryBlock extends React.Component {
_moveImgOneBack = (e, images, index) =>{
e.preventDefault()
let newPlace = index - 1
if(newPlace == -1){
newPlace = images.length
}
const image = images.shift(index)
images.splice(newPlace, 0, image)
return this.props.container.updateData({ images: images })
}
_moveImgOneForward = (e, images, index) =>{
e.preventDefault()
let newPlace = index +1
if(newPlace > images.length){
newPlace = 0
}
const image = images.shift(index)
images.splice(newPlace, 0, image)
return this.props.container.updateData({ images: images })
}
render(){
return (
<CommonBlock {...this.props} actions={this.actions} title="Image
Gallery">
<BlockContent>
<div className='gallery-cms-block'>
{ this.props.images.map((obj, index)=> {
return(
<div key={obj.key} className="image-box">
<button title="Move image back one" className="move-button"
onClick={(e)=> this._moveImgOneBack(e,
this.props.data.images, index)}>◀ {index}</button>
<img className="image" src={`${obj.image.uri}?
id=${obj.image.id}`} />
<div>
<button key={obj.key} title="Move image forward one"
className="move-button" onClick={(e)=>
this._moveImgOneForward(e, this.props.data.images,
index)}>▶</button>
</div>
</div>
)
}) }
</div>
</BlockContent>
</CommonBlockMKII>
);
}
}
I expect the the button(ether forward or backward) to move said item and only the said item.
The results are that it will move the item once... maybe twice then get suck moving all the other items in the array.
... your using shift wrong:
array = ['foo', 'bar', 'not', 'feeling', 'welcome', 'by jack', 'ass users']
array.shift(whatEverIndex)
the output will always be the first index, i.e 'foo', and
because your index is correct and your using
array.splice(newIndex, 0, item)
properly your array is changing in a strange fashion.
Try copying the desired item then delete it with .splice(), like this:
const item = array[index] //copy item
array.splice(index, 1) //remove old item
array.splice(newIndex, 0, item) //place item
funny that none of you guys NaN, laruiss, Antoine Grandchamp, J-Alex took the time to actually do what you should on stackoverflow... you know help someone out. damn vete a cascarla, Good luck Reece hope this solved it for you.
Thanks #Whitepaw,
I've updated my code with:
_moveOneImgBack = (newArray, index) =>{
const arrayLength = newArray.length - 1
const newBackPlace = index == 0 ? arrayLength : index - 1
const image = newArray[index]
newArray.splice(index, 1)
// const image = images.shift(index)
newArray.splice(newBackPlace, 0, image)
this.props.container.updateData({ images: newArray })
}
and it now works perfectly, I got stuck on the fact it might have something to do with redux immutables. So thats for pointing out the misuse of .shift()

React, dynamically add text to ref span

I'm trying to render a message to a span tag specific to an item in a list. I've read a lot about React 'refs', but can't figure out how to populate the span with the message after it's been referenced.
So there's a list of items and each item row has their own button which triggers an API with the id associated with that item. Depending on the API response, i want to update the span tag with the response message, but only for that item
When the list is created the items are looped thru and each item includes this
<span ref={'msg' + data.id}></span><Button onClick={() => this.handleResend(data.id)}>Resend Email</Button>
After the API call, I want to reference the specific span and render the correct message inside of it. But I can't figure out how to render to the span at this point of the code. I know this doesn't work, but it's essentially what I am trying to do. Any ideas?
if (response.status === 200) {
this.refs['msg' + id] = "Email sent";
I recommand using state. because string refs legacy (https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs)
const msgs = [
{ id:1, send:false },
{ id:2, send:false },
{ id:3, send:false },
];
this.state = {
msgs
};
return this.state.msgs.map((msg, index) => {
const status = msg.send ? "Email Sent" : "";
<span>{ status }</span><Button onClick={() => this.handleResend(index)}>Resend Email</Button>
});
async handleResend (index) {
const response = await callAPI(...);
if(reponse.status !== 200) return;
const newMsgs = _.cloneDeep(this.state.msgs);
newMsgs[index].send = true;
this.setState({
msgs: newMsgs
})
}
The workaround is set innerText
this.refs['msg' + id].innerText = "Email sent";
But rather than using ref try to use state to update elements inside render.
i was facing with this issue right now and i figured it out this way:
// currentQuestion is a dynamic Object that comes from somewhere and type is a value
const _target = `${currentQuestion.type}_01`
const _val = this[`${_target}`].current.clientHeight // here is the magic
please note that we don't use . after this to call the ref and not using refs to achieve what we want.
i just guessed that this should be an Object that would hold inner variables of the current object. then since ref is inside of that object then we should be able to call it using dynamic values like above...
i can say that it worked automagically!

Resources