Render same component with different details each time - javascript

I want the same message with different URL, based on a prop. I basically have the below render method, which I call inside my main one.
renderNoBasicMode = () => {
const { securityMode } = this.props;
// Need this, while isFetching securityMode === '',
// Unless, we don't this is rendering on multipel renders.
if (securityMode !== SecurityMode.BASIC && securityMode !== SecurityMode.EMPTY) {
return (
<div className="badge badge-light" data-test="non-basic-mode">
<NoResource
icon="user-o"
title="Non Basic Security Mode"
primaryBtn="New User"
primaryCallback={this.openCreateUserModalPromise}
moreUrl={NonBasicSecurityMode.url}
>
No users available when Lenses is running on {securityMode} security mode.
</NoResource>
</div>
);
}
return null;
};
And I want to display a different url based on the value of the NonBasicSecurityMode, which I have here:
const NonBasicSecurityMode = [
{ securityMode: 'mode1', url: 'https://...' },
{ securityMode: 'mode2', url: 'https://...' },
{ securityMode: 'mode3', url: 'https://...' }
];
The securityMode, is deternment by an API request.
export const securityModeSelector = createSelector(
lensesConfigSelector,
config => (config.&& config['security.mode']) || ''
);
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
};
}
Basically, I tried mapping through them, and a forEach, but I was apparently wrong. Can you help me figure this out? Thanks!!

Related

How do I handle multiple query params in javascript?

I have a react app and I'm trying to use a table component.
This table component has multiple ways of filtering the table, and I want to be able to update the URL with these filters so that when I navigate away and go back, the table can read from the url params and have the table react accordingly.
I am able to get my table to already do something like this, but when working with multiple params, I'm a bit lost. My issue is that using history.replace or history.push` will overwrite the previous params, and then updating them will only just append more params when I want to simply replace them
Here's what I have working already:
const useQueryParams = () => {
const params = new URLSearchParams(useLocation().search);
const startingAfter = params.get('startingAfter');
const endingBefore = params.get('endingBefore');
const status = params.get('status');
return { params, startingAfter, endingBefore, status };
}
export const MyComponent = () => {
const history = useHistory();
const location = useLocation();
const { params, startingAfter, endingBefore, status } = useQueryParams();
const [statusFilter, setStatusFilter] = useState<IStatus | string>(
Status.Pending
);
const [dateFilter, setDateFilter] = useState<{
appointmentStartingAfter?: string;
appointmentEndingBefore?: string;
}>({
appointmentStartingAfter: undefined,
appointmentEndingBefore: undefined,
});
useEffect(() => {
if (startingAfter || endingBefore) {
setDateFilter({
appointmentStartingAfter: moment(startingAfter).toISOString() || undefined,
appointmentEndingBefore: moment(endingBefore).toISOString() || undefined,
});
}
if (status) {
setStatusFilter(status);
}
}, [startingAfter, endingBefore, status]);
// ......
const handleDateChange = (momentDateRange) => {
const startingAfter = momentDateRange[0].subtract(1, 'day');
const endingBefore = momentDateRange[1].add(1, 'day');
history.replace({
search: `startingAfter=${startingAfter.format('YYYY-MM-DD')}&endingBefore=${endingBefore.format('YYYY-MM-DD')}`,
});
setDateFilter({
appointmentStartingAfter: startingAfter.toISOString() || undefined,
appointmentEndingBefore: endingBefore.toISOString() || undefined,
});
};
const handleStatusFilter = (value: string) => {
history.replace({
search: `status=${value || 'ALL_DOCUMENTS'}`,
});
setStatusFilter(value);
};
return (
<>
// ......
<DatePicker.RangePicker defaultValue={initialDates} style={{ marginRight: 20 }} onChange={handleDateChange} />
<CareDocumentStatusFilter value={statusFilter} onChange={handleStatusFilter} />
</>
);
}
I'm able to get the URLs working with history.replace or history.push but it will overwrite each other when I want them to remain persistent.
For example,
history.replace({
search: `startingAfter=${startingAfter.format('YYYY-MM-DD')}&endingBefore=${endingBefore.format('YYYY-MM-DD')}`,
});
// localhost:3000/xyz?startingAfter=2021-08-06&endingBefore=2021-08-19
But then in handleStatusFilter
history.replace({
search: `status=${value || 'ALL_DOCUMENTS'}`,
});
// localhost:3000/xyz?status=CREATED
In the example above, status will overwrite the params when the intended URL should look something like:
localhost:3000/xyz?startingAfter=2021-08-06&endingBefore=2021-08-19&status=CREATED
in my different attempts, when I want to update this, instead of changing the params, the params just seem to append more and end up looking like this:
localhost:3000/xyz?startingAfter=2021-08-06&endingBefore=2021-08-19&status=CREATED?startingAfter=xxxx&endingBefore=yyyy&status=zzzz
I do have react-router-dom available to be used as well.
So I interestingly stumbled upon this answer and it seemed to solve my issue. I believe using URLSearchParams.set was the key here. I don't entirely super understand the inner workings... but it works! Here is my solution:
const handleDateChange = (momentDateRange) => {
const startingAfter = momentDateRange[0].subtract(1, 'day');
const endingBefore = momentDateRange[1].add(1, 'day');
// This was important!
params.set('startingAfter', startingAfter.format('YYYY-MM-DD'));
params.set('endingBefore', endingBefore.format('YYYY-MM-DD'));
history.push({
// and so was this ...?
pathname: location.pathname,
search: params.toString(),
});
setDateFilter({
appointmentStartingAfter: startingAfter.toISOString() || undefined,
appointmentEndingBefore: endingBefore.toISOString() || undefined,
});
};
const handleStatusFilter = (value: string) => {
// This was important!
params.set('status', value || 'ALL_DOCUMENTS');
history.push({
// ... and so was this?
pathname: location.pathname,
search: params.toString(),
})
setStatusFilter(value);
};

How to pass data from one component to another in React.JS

For all React Gurus! The logic is that I make a query to overpass and get some GeoJSON, now I need to pass this GeoJSON object to another Component (which is not its child) so that there I could make some calculations and show it on the screen.
The general structure is like this: There is a Main.js component which has two children MapBox.js and CalculationResults.js. MapBox.js has a child OverpassLayer.js which gives me GeoJSON. This GeoJSON I need to pass to CalculationResults.js. I tried to implement callback function all the way from parent Main.js but it always returns me the GeoJSON from the previous query. Could you please help me with the correct way of passing data between Components.
This is my OverpassLayer.js
const OverpassLayer = (props) => {
const [geojson, setGeojson] = useState();
useEffect(() => {
makeQuery();
}, [props.street, props.houseNumber]);
const makeQuery = () => {
const query = `[out:json];nwr["addr:street"="${props.street}"]["addr:housenumber"="${props.houseNumber}"][building](59.3518076,24.55017,59.5915769,24.9262831);out geom;`;
const options = {
flatProperties: true,
overpassUrl: "https://overpass-api.de/api/interpreter",
};
overpass(query, dataHandler, options);
};
const dataHandler = (error, osmData) => {
if (
!error &&
osmData.features !== undefined &&
osmData.features[0] !== undefined
) {
console.log(osmData.features[0]);
let area = (getArea(osmData.features[0].geometry.coordinates[0]));
console.log(area);
setGeojson(osmData);
}
};
function keyFunction(geojson) {
if (geojson.features.length === 0) {
return "";
} else {
return geojson.features[0].id;
}
}
function getArea(array) {
if (array) {
let arrayPolygon = array;
let polygon = turf.polygon([arrayPolygon]);
let area = turf.area(polygon);
return area;
}
return 0;
}
return geojson ? <GeoJSON key={keyFunction(geojson)} data={geojson} /> : null;
};
the easiest way is to store the data in localStorage. For the example, below setGeojson(osmData) you can write localStorage.setItem("Geojson", JSON.stringify(Geojson)); and in CalculationResults.js you can call it in useEffect() or componentDidMount():
const getGeojson = JSON.parse(localStorage.getItem("Geojson"));
if(getGeojson){
if(getGeojson.length > 0){
setGeojson(getGeojson);
}
}
the more advanced way is to use redux

JavaScript, Redux: array not taking value

I am trying to get some statistics and problems for a user using a Redux action and pass it to a React component. The problem is, I have the array of objects curPageExercisesMarked, which I use for the pagination of the page, but it does not take the values I assign it to.
The stranger thing is that the other fields in the Redux store get updated, but not this one. I tried consoling the object in the action, but it just prints this:
It is important to mention that I am doing something similar in another action, using the exact same assignment and it works there. I've lost already an hour trying to figure this thing out so any help is welcomed.
The Redux action:
export const setStatistics = (
problems,
problemsSolved,
filter = ''
) => dispatch => {
let payload = {
subject1: 0,
subject2: 0,
subject3: 0,
total: 0,
exercisesMarked: [],
curPageExercisesMarked: []
};
for (let i = 0; i < problems.length; i++) {
if (problems[i].S === '1' && problemsSolved.includes(problems[i]._id)) {
payload.subject1++;
payload.total++;
payload.exercisesMarked.push(problems[i]);
} else if (
problems[i].S === '2' &&
problemsSolved.includes(problems[i]._id)
) {
payload.subject2++;
payload.total++;
payload.exercisesMarked.push(problems[i]);
} else if (
problems[i].S === '3' &&
problemsSolved.includes(problems[i]._id)
) {
payload.subject3++;
payload.total++;
payload.exercisesMarked.push(problems[i]);
}
}
payload.curPageExercisesMarked = payload.exercisesMarked.slice(0, 10);
dispatch({
type: SET_USER_STATISTICS,
payload
});
};
The redux reducer:
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_USER_STATISTICS:
return {
...state,
exercisesMarked: payload.exercisesMarked,
curPageExercisesMarked: payload.curPageExercisesMarked,
subject1: payload.subject1,
subject2: payload.subject2,
subject3: payload.subject3,
total: payload.total
};
case CHANGE_PAGE_MARKED:
return {
...state,
page: payload,
curPageExercisesMarked: state.exercisesMarked.slice(
(payload - 1) * state.pages_count,
payload * state.pages_count
)
};
default:
return state;
}
}
This is the part that does not function:
payload.curPageExercisesMarked = payload.exercisesMarked.slice(0, 10);
EDIT
I've discovered that if I go a component which loads all the problems and come back to this component, it actually gets the correct value.
Now, the interesting is that I do get the same problems here as well. Is it the way I use React Hook?
This is the part where I call the redux action in the react component:
const Dashboard = ({
problems: { problems },
auth: { user },
getProblems,
dashboard: {
curPageExercisesMarked,
page,
exercisesMarked,
pages_count,
subject1,
subject2,
subject3,
total
},
setStatistics
}) => {
useEffect(() => {
if (problems === null) {
getProblems();
} else if (user !== null) {
setStatistics(problems, user.problemsSolved);
}
}, [problems, user]);
// rest of the code
}
You can first simplify code as below. Update/Print console.log(JSON.stringify(payload)). I think if(problemsSolved.includes(problems[i]._id)) not working as expected
export const setStatistics = (
problems,
problemsSolved,
filter = ""
) => dispatch => {
let payload = {
subject1: 0,
subject2: 0,
subject3: 0,
total: 0,
exercisesMarked: [],
curPageExercisesMarked: []
};
for (let i = 0; i < problems.length; i++) {
if(problemsSolved.includes(problems[i]._id)) {
payload["subject"+ problems[i].S]++
payload.total++;
payload.exercisesMarked.push(problems[i]);
}
}
payload.curPageExercisesMarked = payload.exercisesMarked.slice(0, 10);
dispatch({
type: SET_USER_STATISTICS,
payload
});
};
// Also
case SET_USER_STATISTICS:
return {
...state,
...payload
};

Global loaded data in VueJs is occasionally null

I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.

How to sort array of data objects in React.js

I'm trying to implement a simple way to sort an array of data pulled from the server in React. The implementation I'm using shown below is probably not the best way to go about it, but I feel it should be working, but is not. It seems the sorting function is never being called, and the data is being displayed in the default order that it comes in from the server.
I'm trying to make a simple orderByRecent function reverse the order of the data, which is returned in chronological order. I know there are ways to accomplish this server-side but this is just an experiment for more complex client-side sorting.
sorting function:
orderByDate: (threads) => {
return threads.sort((a, b) => {
return Date.parse(a.date) > Date.parse(b.date)
})
},
Feed.js:
class ThreadList extends Component {
render() {
var threadNodes, sortedThreadNodes
if (this.props.data) {
var sorted = this.props.sortFunc(this.props.data)
var threadNodes = sorted.map(function (thread) {
return (
<Thread victim={ thread.victim }
date={ thread.date }
author={ thread.author }
ct={ thread.included.length }
likes={ thread.likes }
dislikes={ thread.dislikes }
id={ thread._id}
key={ thread._id }>
{ thread.text }
</Thread>
)
})
}
return (
<div className="threadList">
{ threadNodes }
</div>
)
}
}
var ThreadsBox = React.createClass({
handleThreadSubmit: function (thread) {
var threads = this.props.feed
var newThreads = threads.concat([thread])
this.setState({feed: newThreads})
$.ajax({
url: config.apiUrl + 'threads',
dataType: 'json',
type: 'POST',
data: thread,
xhrFields: {withCredentials: true},
success: function (data) {
// this.setState({feed: feed})
}.bind(this),
error: function (xhr, status, err) {
this.setState({data: threads})
console.log(this.url, status, err.toString())
}.bind(this)
})
},
// Feed nav buttons default to order by date
getInitialState: function () {
return {feed: [], sortFunc: helpers.orderByDate}
},
changeState: function (state, value) {
this.setState({[state]: value})
},
render: function () {
return (
<div className="threadsBox">
<ThreadForm friends={this.props.friends}
onThreadSubmit={ this.handleThreadSubmit }/>
<div className="feedNav">
<NavButtonList divId={"feedNav"}
eventFunc={this.changeState}
state={"sortFunc"}
buttons={buttonObs.mainNavButtons} />
</div>
<ThreadList data={ this.props.feed }
sortFunc={ this.state.sortFunc } />
</div>
)
}
})
module.exports = ThreadsBox
Here's a different approach.
Keep your threads in an object that maps threadId to the actual Thread data.
Keep a separate array that has the threadIds in the order that you want to sort them. When you sort your threads, you only change the ordering of the elements of the array.
When you want to sort the data differently, dispatch an action that will sort based on whatever constraints you have. Rendering the threads is as simple as performing a map over the array and getting the proper thread.

Categories