Syncing React-Slick with Query Params - react-slick

I'm using React-Slick to render <Report /> components in a carousel. I would like to sync each <Report />'s reportId with query params.
For example, a user would be able to see a specific report by going to myapp.com/reports?id=1 and it would take them to that specific "slide".
The problem I'm having is that the report data is being loaded before the slides are initialized. I can't find any good examples of react-slick's onInit or onReInit.

Instead of using onInit or onReInit, I just utilized the initialSlide setting and used componentDidUpdate().
componentDidUpdate = (prevProps, prevState) => {
const queryParams = qs.parse(this.props.location.search)
if (prevProps.reports !== this.props.reports) {
const sortedReports = this.sortReportsByDate(this.props.reports)
this.setSlideIndex(sortedReports.findIndex(i => i.id === parseInt(queryParams.reportId, 10)))
this.setQueryParams({
reportId: this.sortReportsByDate(this.props.reports)[this.state.slideIndex].id
})
}
if (prevState.slideIndex !== this.state.slideIndex) {
this.setQueryParams({
reportId: this.sortReportsByDate(this.props.reports)[this.state.slideIndex].id
})
}
}
And the settings:
const settings = {
...
initialSlide: reportsSortedByDate.findIndex(i => i.id === parseInt(queryParams.reportId, 10))
...
}
I hope someone finds this useful!

Related

Jest/React Testing Library test failing - react router link click not working as expected

I have a failing test but can't work out why. I have react-router links which link to the URL structure: /classes/${weekday}.
Classes component then sets the activeWeekday in context by React Router location which is displayed by the Classes as {activeWeekday} Classes
Functionality works i nthe browser, but for some reason in my tests it's not updating the header so the test is failing.
TestingLibraryElementError: Unable to find an element with the text: /friday classes/i
Can anyone see why? I can't figure it out.
Thks so much in advance.
Update - Here is a codepen replicating the issue.
// Snapshot of the state passed to Classes via context
export const ClassesProvider = ({ children }: ClassesProviderProps) => {
const [activeWeekdayNumber, setActiveWeekdayNumber] = useState<number>(
new Date().getDay()
);
// Classes component
const Classes = () => {
const { activeWeekdayNumber, setActiveWeekdayNumber } = useContext(ClassesContext);
const location = useLocation();
useEffect(() => {
const day = location.pathname.replace("/classes/", "");
const dayIndex = daysOfWeekArray.indexOf(capitaliseFirstLetter(day));
if (dayIndex !== -1) {
setActiveWeekdayNumber(
daysOfWeekArray.indexOf(capitaliseFirstLetter(day))
);
}
}, [location, setActiveWeekdayNumber]);
return (
<>
<h2>{daysOfWeekArray[activeWeekdayNumber]} Classes</h2>
</>
);
};
// failing test - TestingLibraryElementError: Unable to find an element with the text: /friday classes/i
const setup = (value: ClassesContextType) =>
render(
<ClassesContext.Provider value={value}>
<MemoryRouter>
<Classes />
</MemoryRouter>
</ClassesContext.Provider>
);
test("displays the relevant heading when a day of the week link is clicked", () => {
const value = {
activeWeekdayNumber: 3, // wednesday
};
setup(value);
const link = screen.getByRole("link", { name: "Friday" });
fireEvent.click(link);
expect(screen.getByText(/friday classes/i)).toBeInTheDocument();
});
});
The menu list is a styled link:
<li>
<HorizontalMenuLink $active={weekdayNumber === 1} to="/classes/monday">
Monday
</HorizontalMenuLink>
</li>

Using Postgraphile in nodeJS, how to enable aggregate max of date field?

I am using postgraphile in NodeJS for graphql API based on Postgresql database. I need to get max(date_field), but postgraphile does not provide that option by default.
How can I enable aggregation of max on a date field?
I want something as follows. But inspection_Date field is not available under max
query Query {
allRooms {
aggregates {
max {
inspection_date
}
}
}
}
Using a slightly modified version of the approach outlined in the defining your own aggregates section of the pg-aggregates readme, you can create a new graphile plugin that uses a hook to modify the existing aggregate specs for "min" and "max" to use a different isSuitableType function that includes temporal types as well as numeric types:
import type { Plugin } from "graphile-build";
import type { AggregateSpec } from "#graphile/pg-aggregates/dist/interfaces";
import type { PgType } from "graphile-build-pg";
const addTemporalAggregatesPlugin: Plugin = (builder) => {
builder.hook(
"build",
(build) => {
const { pgAggregateSpecs } = build;
const isNumberLikeOrTemporal = (pgType: PgType): boolean =>
pgType.category === "N" || pgType.category === "D";
// modify isSuitableType for max and min aggregates
// to include temporal types see: https://www.postgresql.org/docs/current/catalog-pg-type.html
const specs = (pgAggregateSpecs as AggregateSpec[] | undefined)?.map(
(spec) => {
if (spec.id === "min" || spec.id === "max") {
return {
...spec,
isSuitableType: isNumberLikeOrTemporal,
};
}
return spec;
}
);
if (!specs) {
throw Error(
"Please that the pg-aggregates plugin is present and that AddTemporalAggregatesPlugin is appended AFTER it!"
);
}
const newBuild = build.extend(build, {});
// add modified aggregate specs to the build
newBuild.pgAggregateSpecs = specs;
return newBuild;
},
["AddTemporalAggregatesPlugin"],
// ensure this hook fires before other hooks in the pg-aggregates plugin
// that may depend on the "pgAggregatesSpecs" extension.
["AddGroupByAggregateEnumsPlugin"],
[]
);
};
export default addTemporalAggregatesPlugin;
Then just append this new plugin after the pg-aggregates plugin:
postgraphile(pool, "my_schema", {
pluginHook,
appendPlugins: [
PgAggregatesPlugin,
AddTemporalAggregatesPlugin,
],
// ...
})

Pagination testing via cypress JS

I need to test that when I select some model car, as a result, I have only that model in all pages. So basically I do pagination testing. But I do something wrong that it does not moves to another page although selectors are correct. Please tell me what I am doing wrong.
findItem("AUDI")
});
async function findItem(value) {
async function findInPage(index) {
let found = false;
cy.get("li.page-item:not(.page-pre):not(.page-next)").as("pages");
await cy.get("#pages")
.its("length")
.then(async (len) => {
if (index >= len) {
return false;
} else {
await cy.get("#pages")
.eq(index)
.click();
await cy.get("table tr > td:nth-child(5) p")
.each(async ($itemNameEl, index) => {
const itemText = $itemNameEl.text().toUpperCase();
cy.log('item ', itemText);
if (itemText.includes(value)) {
found = true;
await cy.wrap($itemNameEl).eq(index);
//cy.get('.chakra-text.css-0').should('include', value)
cy.get('.css-1b4k5p > .chakra-text.css-0')
.each(($el) => {
expect($el.text().toUpperCase()).to.include(value)
})
return false;
}
})
.then(() => {
if (!found) {
findInPage(++index);
}
});
}
});
}
findInPage(0);
}
A simple example without aliases, async functions, etc, just using recursion and a single NEXT PAGE button (that I see you have in your screen shot) would look like this (tested and working on bootstrap pagination examples):
it('Some test', () => {
cy.visit('/')
const findInPage = () => {
cy.get('li:has(a.page-link:has(span:contains(»)))').then((el) => {
// do your test
if (el.hasClass('disabled')) {
// on last page, break out
return
}
cy.wrap(el).click()
findInPage()
})
}
findInPage()
});
How this works: Look for li element which represents a single pagination next-page button, in bootstrap case it has child a tag which has child span tag which contains an icon ». Once you reach last page, the button get's disabled by adding .disabled class to li tag which is checked for on every page. Using this it doesn't matter if you have 3 or 33 pages and if some numbers are hidden with ...
Reference: https://glebbahmutov.com/blog/cypress-recurse/

subscribing a http get call - why do I need to refresh my frontend page to get the data after it got altered?

So I have a web application built with angular on the frontend and node js on the backend. Im building an FAQ app where a user can add categories. See the photo below:
Those categories are stored in a pg database. When navigating to the page "Categories", I make a http call to my node js backend to get all categories from the database. My api looks like this:
app.get('/all-categories', async (req, res) => {
const result = await pool.query('select * from "Category"');
res.json(result.rows);
});
And following is my frontend, the categories.ts:
export class CategoriesComponent implements OnInit, OnDestroy {
categories: Category[] = [];
subscriptions = new Subscription();
constructor(private httpService: HttpService) { }
ngOnInit(): void {
const subscription = this.httpService.getAllCategories().subscribe(allCategories => {
this.categories = allCategories;
});
this.subscriptions.add(subscription);
}
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}
onDeleteButtonClick(categoryUUID: string) {
this.httpService.deleteCategoryByUUID(categoryUUID).pipe(
take(1)
).subscribe();
}
}
Now, whats the problem?
The problem is, when I navigate to the Categories page, all the categories are shown. But when I delete a category using the red delete button, I need to refresh the page in order to see the updated number of categories.
Why is this so? And what do I need to do to make it work?
I reckon to change the way of how you obtain the data from your service, you could subscribe to the observable in an asynchronous way with async pipe from the template, and when one category will be eliminated, you can update your observable.
on this way your .ts should be in this way:
export class CategoriesComponent implements OnInit {
categories$: Observable<Category[]> = [];
constructor(private httpService: HttpService) { }
ngOnInit(): void {
this.categories$ = this.httpService.getAllCategories();
}
async onDeleteButtonClick(categoryUUID: string) {
const deleteCategory = await this.httpService.deleteCategoryByUUID(categoryUUID).pipe(take(1))
.toPromise();
if(deleteCategory) {
this.categories$ = this.httpService.getAllCategories();
}
}
}
and your template something like this:
<div *ngIf="categories$ | async as categories">
<div *ngFor="let cat of categories">cat</div>
</div>
You can add tap operator in deleteCategoryByUUID, and filter categories:
this.categories = this.categories.filter(c => c !== c.uid);

React-native and Redux healthy way to call actions on props change

I've been using react-native with redux for a while, and the way i learn to call actions when something change on prop is using the componentWillReceiveProps, but when I use it I need to pass between if's and some times it goes to the wrong if, then I need to add more stuff to prevent it.
Here's an example I have done. I know this is not the best way to do it, but it is what I could think of.
componentWillReceiveProps(newProps) {
if(Object.keys(newProps.selected_product).length > 0) {
if(Object.keys(this.props.current_location).length > 0 || Object.keys(newProps.current_location).length > 0) {
this._handleNextPage(2);
this.props.verifyProductById(newProps.selected_product, newProps.current_location, this.props.token);
} else {
this.props.statusScanner(false);
this._handleNextPage(1);
}
} else if(Object.keys(newProps.historic_product_confirm).length > 0) {
if(newProps.historic_product_confirm.location._id == newProps.current_location._id)
this.props.handleModalConfirmPrice(!this.props.modal_confirmPrice_status)
} else if(newProps.scanResult != "") {
this.props.statusScanner(false);
if(Object.keys(newProps.current_location).length > 0) {
this._handleNextPage(2);
} else {
this._handleNextPage(1);
}
} else {
this._handleNextPage(0);
}
}
What I need is a healthy way to call my actions when the props change.
Edit:
Here i have the full OfferScene and an action file example:
OfferScene:
https://gist.github.com/macanhajc/0ac98bbd2974d2f6fac96d9e30fd0642
UtilityActions:
https://gist.github.com/macanhajc/f10960a8254b7659457f8a09c848c8cf
As mentioned in another answer, componentWillReceiveProps is being phased out, so I would aim for trying to eliminate it where possible. You'll be future-proofing your code and keeping your component logic more declarative and easy to reason about. As someone who has been responsible for (and been frustrated by) lifecycle method abuse like this, here are some things that have helped me.
Remember that when using redux-thunk, along with passing dispatch as the first argument, you can also pass getState as the second. This allows you to access state values in your action logic instead of bringing them into your component's props and adding clutter. Something like:
export const ExampleAction = update =>
(dispatch, getState) => {
const { exampleBool } = getState().ExampleReducer
if (exampleBool) {
dispatch({
type: 'UPDATE_EXAMPLE_STATE',
update
})
}
}
Using async/await in action logic can be a lifesaver when your action depends upon fetched results from an API call:
export const ExampleAction = () =>
async (dispatch, getState) => {
const { valueToCheck } = getState().ExampleReducer
, result = await someAPICall(valueToCheck)
.catch(e => console.log(e))
if (result.length > 0) {
dispatch({
type: 'UPDATE_EXAMPLE_STATE',
update: result
})
}
}
For cases where your component's rendering behavior depends upon certain state values after your state has been updated, I highly recommend reselect. A very basic example would be something like:
component.js
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import { shouldDisplayItems } from '../selectors'
import MyListviewComponent from './myListview'
class ItemList extends Component {
render() {
const { shouldDisplayItems, items } = this.props
return (
<>
{shouldDisplayItems && <MyListviewComponent items={items} />}
</>
)
}
}
const mapStateToProps = ({ ListItems }) => shouldDisplayItems(ListItems)
export default connect(mapStateToProps)(ItemList)
selectors.js:
(Assuming your ListItems reducer has the params items and visibilityFilter)
import { createSelector } from 'reselect'
export const shouldDisplayItems = createSelector(
[state => state],
({ items, visibilityFilter }) => {
return {
shouldDisplayItems: visibilityFilter && items.length > 0,
items
}
}
)
I should mention that another option would be using higher-order components, but it can be tricky to use this approach before having a good grasp on how to keep too much imperative logic out of your components (I learned this the hard way).
I agree with #AnuragChutani and #Goldy in terms of clarity of the code; break it down some more into more components or functions.
Now after some review of your componentWillReceiveProps function, it is definitely not specific enough to narrow down exactly which prop changes. If any connected redux variable changes, the componentWillReceiveProps function will be invoked each time.
So e.g. if 'token' or 'selected_product' updates, componentWillReceiveProps will be triggered, even though you did not want it to trigger for token updates.
You can use a comparison for a specific variable update in the props.
E.g Using lodash
if(!_.isEqual( nextProps.selected_product, this.props.selected_product ))
// if props are different/updated, do something
Secondly, you can call actions/callbacks in your actions to narrow down navigation.
E.g.
takePicture = (camera, options){
...
//on success
dispatch(handleModalConfirmPrice())
...
}}

Resources