Ok, newbie alert, but I have a react component like below. I want to load user object and then show user.name and possible other nested properties, like user.address.street.
const ProfileDisplay =()=>
{
const [user, setUser] = useState(null);
useEffect(()=>{
async function populate(){
const user = await fetch('server/getUserProfile/'...);
setUser(user)
}
populate();
},[whatevs])
return (<FancyLookingForm>...<span>Your name is {user.name}</span>...</FancyLookingForm>);
}
...
During the time the user-object is loaded I want the form to be displayed, but empty. The alternatives I see is either create a dummy object that looks like the one that will come from the server and use that as init object to useState(). The other alternative is to use an if null-check on every place I use the user-object.
I will do quite alot of forms, so I want to check if there is a better way to do this?
I have written a utility function that will help you.
/**
* object is the forms data container in your example user
* property is field name e.g. name, age
*/
function getPropertryValue(object, property) {
if(!object) return "";
const keys = Object.keys(object);
const keysLength = keys.length;
for(let i= 0; i < keysLength; i++) {
const key = keys[i];
if(key == property) {
return object[key];
}
if(typeof object[key] === 'object'){
return utilFindHas(object[key], property);
}
}
return "";
};
So instead of using {user.name} use getPropertyValue(user, "name")
It will return empty string if it doesn't exist yet.
Related
I'm trying to get the temperature of each hour from this website: https://www.smhi.se/vader/prognoser/ortsprognoser/q/Stockholm/2673730
I'm getting the data from https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/16/lat/58/data.json. The "t" object is the temperature.
The problem I have is displaying the data for each hour in the repeater.
Here is my backend-code:
import { getJSON } from 'wix-fetch';
export async function getWeather() {
try {
const response = await getJSON('https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/16/lat/58/data.json');
console.log(response) // all data
const tempData = response.timeSeries[0].parameters[10].values[0];
return tempData // Only returns "t" - temperature
} catch (e) {
return e;
}
}
The backend part works, however the frontend doesn't.
import { getWeather } from 'backend/getSMHI.jsw'
$w.onReady(function () {
(
getWeather().then(weatherInfo => {
$w('#weatherRepeater').onItemReady(($item, itemData, index) => {
if (index > 6) {
$item('#tempText').text = itemData.timeSeries[index].parameters[1].values[0];
} else if (index === 6) {
$item('#tempText').text = itemData.timeSeries[index].parameters[0].values[0];
} else {
$item('#tempText').text = itemData.timeSeries[index].parameters[10].values[0];
} // The parameters number for "t" changes depending on the index
})
$w('#weatherRepeater').data = weatherInfo;
})
)
})
Seems like there are at least a couple of issues here.
First, you are retrieving a single number from the API and trying to put that in a repeater. From the description of what you're trying to do, it would seem that you mean to be retrieving a list of numbers, probably as an array. You probably want to do some filtering and/or mapping on the response data instead of directly accessing a single value.
Second, the data you send to a repeater must be in the proper format. Namely, it must be an array of objects, where each object has a unique _id property value (as a string). You are not doing that here. You are simply assigning it a number.
Third, and this is just an efficiency thing, you don't need to define the onItemReady inside the then(). Not that it will really make much of a difference here.
I am having trouble figuring out how to get or filter a bunch of childNodes by their style class name inside my useEffect. Using ReactJs v18.
Straight after the line with: const circleElements = launcherCircle!.childNodes; I would like to get/filter the div's with the class name 'launcherPos' so I can position them in a circle formation.
const LauncherComponent = () => {
const launcherCircleRef = useRef<HTMLDivElement>(null);
let modules: Module[] | null = GetModules();
const enableLauncher = (module: Module) => {
return !module.IsEnabled ? styles['not-active'] : null;
};
useEffect(() => {
const launcherCircle = launcherCircleRef.current;
const circleElements = launcherCircle!.childNodes;
let angle = 360 - 190;
let dangle = 360 / circleElements.length;
for (let i = 0; i < circleElements.length; i++) {
let circle = circleElements[i] as HTMLElement;
angle += dangle;
circle.style.transform = `rotate(${angle}deg) translate(${launcherCircle!.clientWidth / 2}px) rotate(-${angle}deg)`;
}
}, []);
if (modules == null){
return <Navigate replace to={'/noaccess'} />
} else {
return (
<div data-testid="Launcher" className={styles['launcherContainer']} >
<div className={styles['launcherCircle']} ref={launcherCircleRef}>
{modules.map(function (module: Module, idx) {
return (
<div key={idx} className={styles['launcherPos']} ><div className={`${styles['launcherButton']} ${enableLauncher(module)}`}><img src={module.ImagePath} alt={module.Prefix} /></div></div>
)
})}
<div className={styles['launcherTextDiv']}>
<span>TEST</span>
</div>
</div>
</div>
)
}
};
export default LauncherComponent;
From what I've read getElementsByClassName() is not advisable practise because of the nature of ReactJs and it's virtual DOM.
I tried the following filter but I think with React garburling the class name I didn't get anything back.
const launcherChildren = launcherCircle!.children;
const circleElements = [...launcherChildren].filter(element => element.classList.contains('launcherPos'));
Maybe there's a way to ref an array of the just the children with the launcherPos class???
There must be a couple of different ways, but, they are eluding me.
When you filter/map an array of HTMLElements, the results are in the form of objects, which contains properties like, props, ref etc.
Since className is a prop on the element, you should try looking for the class name by digging into the props key.
Simply put, all the props that you pass to the element, like onClick, onChange, value, className are stored under the props property.
You can filter the results by converting the class name into an array and further checking if it contains the target string (launcherPos in this case).
Your code should look something like this:
const circleElements = [...launcherChildren].filter(element=>element.props.className.split(' ').includes('launcherPos'))
The above method could be used when an array directly holds elements. E.g: [<div></div>,<div></div>...].
The approach that you've followed is correct, except for the way you are selecting the elements by their class names. I can see that you are using CSS modules in this component, meaning all the class names exist as properties on the imported object(styles in this case), so when you use contains('launcherPos') you are essentially checking for the presence of a string, but when using CSS modules, class names are available only as object properties, that's the reason you are getting an empty array. Simply update launcherPos to styles.launcherPos and that shall fix the issue.
All-in-all your useEffect function should look something like this:
useEffect(() => {
const launcherCircle = launcherCircleRef.current;
const launcherChildren = launcherCircle!.children;
const circleElements = [...launcherChildren].filter(element => element.classList.contains(styles.launcherPos)); //change launcherPos to styles.launcherPos
let angle = 360 - 190;
let dangle = 360 / circleElements.length;
for (let i = 0; i < circleElements.length; i++) {
let circle = circleElements[i] as HTMLElement;
angle += dangle;
circle.style.transform = `rotate(${angle}deg) translate(${launcherCircle!.clientWidth / 2}px) rotate(-${angle}deg)`;
}
}, []);
I have this Firebase structure:
Firebase Structure.
Then I have a function in my Code, which adds a map called "set".
My Structure is looking like this after: New structure.
Now i want an onUpdate Firebase function, which is called after the map "set" is added in any document.
This function should shuffle my "question" array.
I tried something like this:
exports.shuffleSet = functions.firestore
.document('duell/{duell_id}/set/questions')
.onUpdate((change, context) => {
const data = change.after.data();
const previousData = change.before.data();
if (data.name == previousData.name) {
return null;
}
//shuffle code here
});
But Im not sure if .document('duell/{duell_id}/set/questions') is the correct way to navigate to the question array. And at the beginning the "set" is not even existing as explained at the top.
How do I navigate to the question array correctly, that I can pull it & update it shuffled?
You should pass a document path to functions.firestore.document(). You cannot pass a field name, since Firestore Cloud Functions are triggered by documents events.
So you should do as follows:
exports.shuffleSet = functions.firestore
.document('duell/{duell_id}')
.onUpdate((change, context) => {
if (!change.after.data().shuffledSet) {
const data = change.after.data();
const question = data.set.question; // get the value of the question field
const shuffledSet = shuffle(question); // generate the new, suffled set. It’s up to you to write this function
return change.after.ref.update({shuffledSet});
} else {
return null; // Nothing to do, the shuffled field is already calculated
}
});
createNewList = (id, input) => {
const foundCard = {...this.state.cards.find(card => id === card.id)};
this.setState(foundCard.list = [...foundCard.list, input]);
};
Hello everyone
There is an array of data (list), which is stored in the state for each object (card).
Problem: I can’t seem to add a new element to this array.
The way it is set up creates only one element and change it every time. But I need to create a new one every time. I tried to create a separate array, add to it using newArray.push (input) and then do this.setState (foundCard.list = [... foundCard.list, ... newArray])`, but have the same result.
I don’t use Redux, because I just started to learn React and I store everything in state yet.
Thanks in advance for your reply.
createNewList = (id, input) => {
const newCards = this.state.cards.map(card => {
if (card.id === id) card.list = [...card.list, input];
return card;
});
this.setState({
cards: newCards
});
};
I want to send filter object using ReferenceField, so that I can get that object on dataprovider in params so that I can put filter when doing GET_MANY call to the API.
Similar thing exist in ReferenceManyField and that works just fine but, there you send multiple calls to the API unlike in ReferenceField where you send just group of ids and that's it.
For example simple GET_MANY is something like this:
https:\somewebsite.com\api\v1\users?filter[id_in]='123adb'
Now I would like to send some filter and do something like this:
https:\somewebsite.com\api\v1\cars?filter[user_id_in]='123adb'
This part with filter I would like to dynamically get it from params in dataprovider and then create https request according to that.
I can do something like checking what resource it is and then based of that create the right URL for API request but that doesn't seems to me like a good solution, because that would make dataprovider look dirty with all the IF's.
<ReferenceField
label="Car name"
source="id"
reference="cars"
filter:{{user_id_in: "user_id_in"}}
>
<TextField source="name"/>
</ReferenceField>
This is what I would like to do, but I know that ReferenceField doesn't have filter prop.
Instead of modifying the Data Provider, you can do the required operations in the decorator: https://marmelab.com/react-admin/DataProviders.html#decorating-your-data-provider-example-of-file-upload.
Here is an example decorator for modifying the date format in queries:
addUploadCapabilities.js
const addUploadCapabilities = (requestHandler, apiUrl) => (type, resource, params) => {
if ((type === 'GET_LIST') && (Object.keys(params.filter).length)) {
const { filter, ...otherParams } = params
const newFilter = Object.entries(filter).reduce((result, [name, value]) => {
if (value && value.hasOwnProperty('lte')) {
let maxValue = value.lte
const regexp = /(\d{4})-(\d{2})-(\d{2})/
let match = regexp.exec(value.lte) // Only for dates
if (match) {
maxValue = `${match[1]}-${match[2]}-${match[3]}T23:59:59.999Z`
}
if (value.hasOwnProperty('gte')) {
result[name] = { between: [value.gte, maxValue] } // Replace the conditions lte + gte with between
} else {
result[name] = { lte: maxValue } // Shift the end date to the end of the day
}
} else {
result[name] = value // Other values are copied to the new filter
}
return result
}, {})
return requestHandler(type, resource, { ...otherParams, filter: newFilter })
}
}
export default addUploadCapabilities