How to replace nested stack with other component in react-native-navigation v2? - react-native-navigation

I'm trying to build a dynamic wizard with react-native-navigation v2.
I say dynamic because the number of steps may vary depending on which options the user selects.
I was thinking to use nested stacks, so the layout of my app would look something like this:
{
root: {
sideMenu: {
left: {/*...*/},
center: {
stack: {
children: [component1, /*...,*/ componentX]
}
}
}
}
}
ComponentX is where I start my wizard, so I push a new stack like this:
{
// ...
stack: {
children: [
component1,
//...,
componentX,
{
stack: {
children: [step1, step2, /*...,*/ stepN]
}
}
]
}
}
After the user make the last choice on stepN, I would like to replace the nested stack with a summary screen to have something like:
{
//...
stack: {
children: [
component1,
//...,
componentX,
summaryScreen
]
}
}
I could use Navigation.setRoot to reset the whole thing but that means I would probably have to store the navigation in Redux.
I've also tried using Navigation.setStackRoot but I'm under the impression that it's setting the parent stack root and not my nested stack...

I've finally managed to solve it.
Here's how:
At the start of my app, when I give an id to my main stack
const sideMenu = {
left: { /*...*/ },
center: {
stack: {
id: 'main', // this line is important
children: [/*...*/]
}
},
};
Navigation.setRoot({
root: { sideMenu },
});
When I want to start my wizard, I push a new stack
Navigation.push(componentId, {
stack: {
id: 'wizard',
children: [
{
component: { /*...*/ },
},
],
}
})
I push the screens on the new stack wizard as the user progresses
When I want to display the final summary screen, I call setStackRoot on the nested stack
Navigation.setStackRoot('wizard', [
{
component: { /*...*/ },
},
]);
On that summary screen, I have a button labelled 'Finish' which removes the nested stack
Navigation.pop('main');
EDIT: with this approach alone, if you click on the back arrow when you are on a nested screen, it will dismiss the entire nested stack instead of this screen only.
I had to use a custom back button as follows:
I solved it by using a custom back button:
1. When pushing a new screen where I want to override the button, use the options
import Icon from 'react-native-vector-icons/MaterialIcons';
/* ... */
const backIcon = await Icon.getImageSource('arrow-back', 24, '#000');
const component = {
id: screenID,
name: screenID,
passProps,
options: {
topBar: {
leftButtons: [
{
id: 'backButton',
icon: backIcon,
},
],
},
}
};
return Navigation.push(componentId, { component });
Create a HOC to implement you custom back action
import React, { Component } from 'react';
import { Navigation } from 'react-native-navigation';
const getDisplayName = WrappedComponent => WrappedComponent.displayName || WrappedComponent.name || 'Component';
export default function withCustomBackButton(WrappedComponent) {
class WithCustomBackButton extends Component {
componentDidMount() {
this.navigationEventListener = Navigation.events().bindComponent(this);
}
componentWillUnmount() {
if (this.navigationEventListener) this.navigationEventListener.remove();
}
navigationButtonPressed() {
// Your custom action
const { componentId } = this.props;
Navigation.pop(componentId);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
WithCustomBackButton.displayName = `WithCustomBackButton(${getDisplayName(WrappedComponent)})`;
return WithCustomBackButton;
}
When registering the screen with the custom back button, wrap it in your HOC
import withCustomBackButton from '../components/hoc/WithCustomBackButton';
/* ... */
Navigation.registerComponent('selectLocation', () => withCustomBackButton(SelectLocation));

Related

Specify response type for inline fragment graphql in js file

Here, inline fragment graphql is used. I'm not able to write the return type in the js file.
Graphql:
query MyQuery {
samples(dataset: "", view: "") {
edges {
node {
... on ImageSample {
id
}
... on PointCloudSample {
id
}
}
}
}
}
JS file: this raises a syntax error:
const SAMPLE_DATA = {
edges: {
node: {
... on ImageSample {
id
sample
}
... on PointCloudSample {
id
}
}
}
};
I've also tried with node: {id} but didn't help
Cannot query field 'id' on type 'SampleItem'. Did you mean to use an inline fragment on 'Sample', 'ImageSample', 'PointCloudSample', or 'VideoSample'?
Calling the GraphQL query like this:
const gqlQuery = jsonToGraphQLQuery({
query: {
samples: {
__args: {
...data,
},
...SAMPLE_DATA
}
}
}, { pretty: true });
Can anyone help me how we need to write the SAMPLE_DATA response type?
I authored the GraphQL API. The below is a perfectly valid query as of v0.18.0.
query {
samples(dataset: "datasetName", view: []) {
edges {
node {
... on ImageSample {
id
}
... on PointCloudSample {
id
}
}
}
}
}
I believe you just need to follow the json-to-graphql-query instructions for querying with multiple inline fragments.
const SAMPLE_DATA = {
edges: {
node: {
__on: [
{ __typeName: "ImageSample", id: true },
{ __typeName: "PointCloudSample", id: true }
]
}
}
};

testing a vue3 component with <router-link> with jest

I got this test from another site. They are injecting a mock route. I think I need to mock router itself or pass a real one into the test so the page can run. There is a way to do this in vue 2, but I haven't found an example for vue 3.
import { mount } from "#vue/test-utils";
import Nav from "./Nav.vue";
test("it displays a menu item", () => {
const mockRoute = {
params: {
id: 1,
},
};
const mockRouter = {
push: jest.fn(),
};
const wrapper = mount(Nav, {
props: {
isAuthenticated: true,
},
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
},
},
});
expect(wrapper.find("#navLabel_0").text()).toEqual("Appointments");
});
The component I'm testing has tags.
The test fails with:
Failed to resolve component: router-link
You have to pass the router-link as a stub: stubs: ['router-link'] when you mount the component:
const wrapper = mount(Nav, {
props: {
isAuthenticated: true,
},
global: {
mocks: {
$route: mockRoute,
$router: mockRouter,
},
},
stubs: ['router-link'] });

How to add 'slug' tag automatically in markdown files using NetlifyCMS and Gatsby?

Codesandbox link here.
Any time I try to publish a new blog post using NetlifyCMS, it says it publishes. However my Netlify build fails and doesn't actually push any blog posts live.
Here's the error I get:
12:44:22 PM: error Your site's "gatsby-node.js" must set the page path when creating a page.
12:44:22 PM: The page object passed to createPage:
12:44:22 PM: {
12:44:22 PM: "path": null,
12:44:22 PM: "component": "/opt/build/repo/src/templates/blogTemplate.js",
12:44:22 PM: "context": {
12:44:22 PM: "slug": null
12:44:22 PM: }
12:44:22 PM: }
12:44:22 PM: See the documentation for the "createPage" action — https://www.gatsbyjs.org/docs/actions/#createPage
12:44:22 PM: not finished createPages - 0.042s
The reason why I get this error is because when new posts are published, the markdown file of the new blog post does not automatically get the 'slug' tag added. Example:
---
title: 10 of the best SEO strategies for 2021
slug: /posts/10-best-seo-strategies-2021/ <-- I had to manually add this in the markdown file. This line is completely missing when pushing new blog posts live. This is causing the site build to fail.
date: 2021-03-26T23:53:24.128Z
excerpt: >-
In this post, we go over 10 of the best SEO strategies for 2021. If you want
more business, read more now!
---
Once I manually go and add the blog post as a markdown file outside of NetlifyCMS, and add the slug tag and push up to master, it successfully builds. Obviously I don't want to do that every time, I want my site to publish normally from NetlifyCMS.
gatsby-node.js:
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions
const blogPostTemplate = require.resolve(`./src/templates/blogTemplate.js`)
const result = await graphql(`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 1000
) {
edges {
node {
frontmatter {
slug
}
}
}
}
}
`)
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.frontmatter.slug,
component: blogPostTemplate,
context: {
// additional data can be passed via context
slug: node.frontmatter.slug,
},
})
})
}
GraphQL pageQuery in my /src/pages/posts.js file:
export const pageQuery = graphql`
query {
allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {
edges {
node {
id
excerpt(pruneLength: 250)
frontmatter {
date(formatString: "MMMM DD, YYYY")
slug
title
}
}
}
}
}
`
Config.yml:
backend:
name: github
repo: my-repo
media_folder: uploads
public_folder: /uploads
collections:
- name: "posts"
label: "Posts"
folder: "posts"
create: true
slug: "{{slug}}"
fields:
- { label: "Title", name: "title", widget: "string" }
- { label: "Publish Date", name: "date", widget: "date" }
- { label: "Excerpt", name: "excerpt", widget: "string" }
- { label: "Body", name: "body", widget: "markdown" }
blogTemplate.js file:
export const pageQuery = graphql`
query($slug: String!) {
markdownRemark(frontmatter: { slug: { eq: $slug } }) {
html
frontmatter {
date(formatString: "MMMM DD, YYYY")
slug
title
excerpt
}
}
}
`
Any idea why this may be happening?
Any idea why this may be happening?
Well, you are trying to query for a slug field and it's never been set (at least at the beginning). Your frontmatter has these fields:
Title
Publish
Excerpt
Body
But not a slug.
The standard way is to add it in your config.yml:
- { name: slug, label: Slug, required: true, widget: string }
Adding this, your query will work automatically.
Another method is to use the built-in listeners and the resolvers (Node APIs) from Gatsby to generate a slug based on a parameter previously set, but you will need to change your query. On your gatsby-node.js add:
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
if (node.internal.type === `MarkdownRemark`) {
let value = createFilePath({ node, getNode });
createNodeField({
name: `slug`,
node,
value,
});
}
};
With onCreateNode you are creating a new node based on some rules (more details). That will create a new collection to be queried named fields with a slug inside. So you only need to adapt it like:
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions
const blogPostTemplate = require.resolve(`./src/templates/blogTemplate.js`)
const result = await graphql(`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 1000
) {
edges {
node {
fields{
slug
}
frontmatter {
slug // not needed now
}
}
}
}
}
`)
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.fields.slug,
component: blogPostTemplate,
context: {
// additional data can be passed via context
slug: node.frontmatter.slug,
},
})
})
}
There's no "automated" way to achieve this without diving into more Node schemas. You are only creating a markdown file and querying its content. Which are the logic to create a slug from scratch? slug fields should be always required.
You can try changing the following:
createNodeField({
name: `slug`,
node,
value,
});
To add a custom value based on some logic if the slug is not defined.
Another thing outside the topic. You are creating duplicated an excerpt:
One in your markdown (coming from Netlify's CMS):
- { label: "Excerpt", name: "excerpt", widget: "string" }
One created automatically in your GraphQL query. GraphQL + Gatsby filesystem adds a custom excerpt field that results from a splitting of the content of the body by using a pruneLength filtering outside the frontmatter:
export const pageQuery = graphql`
query {
allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {
edges {
node {
id
excerpt(pruneLength: 250)
frontmatter {
date(formatString: "MMMM DD, YYYY")
slug
title
}
}
}
}
}
`
I think that you are mixing stuff here, I would recommend using only one of them to avoid misunderstandings in your code.

Angular 2: Load data using Server data source into my ng smart table

Has any one figure out on how to load the server data from the ng2-smart table plugin of Angular2.
I have few products data that is retrieved from Node API and Im able to display the same onClick event in the browser log.
I need to display the same in this 3rd party plugins table area which they have provided in this documentation below:
Frontend : https://akveo.github.io/ng2-smart-table/#/examples/populate-from-server
Under "Server Data Source Example"
Code: https://github.com/akveo/ng2-smart-table/blob/master/src/app/pages/examples/server/advanced-example-server.component.ts
Accordingly i have configured in my code as below:
blank-page.component.ts
import { ServerDataSource } from 'ng2-smart-table';
import { Component } from '#angular/core';
import { Http } from '#angular/http';
#Component({
selector: 'advanced-example-server',
template: `
<ng2-smart-table [settings]="settings" [source]="source"></ng2-smart-table>
`,
})
export class BlankPageComponent implements OnInit {
settings = {
columns: {
id: {
title: 'ID',
},
albumId: {
title: 'Album',
},
title: {
title: 'Title',
},
url: {
title: 'Url',
},
},
};
source: ServerDataSource;
//Doubt or Problem here!!!
constructor(http: Http) {
this.source = new ServerDataSource(http, { endPoint: 'https://jsonplaceholder.typicode.com/photos' });
}
//Tried like this too (Which is not the right way of calling)
constructor(http: Http) {
this.source = new ServerDataSource(http, { endPoint: this.productService.getProductsOncategory(this.categoryid) });
}
//Dint work this too!!
constructor(http: Http) {
this.source = new ServerDataSource(http, { endPoint:'http://localhost:5000/products/getProductsOncategory ' });
}
}
Where my service.ts file is like, which actually displays the products data in my browser log which i need to show in my table data
getProductsOncategory(category_id){
let catUrl="http://localhost:5000/products/getProductsOncategory"
let headers = new Headers();
headers.append('Content-Type','application/json');
let catIdObj = JSON.stringify({category_id:category_id})
console.log(catIdObj)
return this.http.post(catUrl,catIdObj,{headers:headers})
.map((response:Response)=>response.json())
.do(data=>console.log(JSON.stringify(data)))
.catch(this.handleError);
}
Error if i use my projects url in endpoint
Error: Uncaught (in promise): Error: Data must be an array. Please check that data extracted from the server response by the key '' exists and is array.
This is what i did and worked perfect for me, i used smart table server side paging, but build my own filter for custom filtration experience.
1- Define Server Data Source
source: ServerDataSource;
2- set it in constructor with config object
this.source = new ServerDataSource(http,
{
endPoint: 'full-url-for-endpoint',
dataKey: 'your-list-path-from-response' for example 'data.records' ,
pagerPageKey: 'your backend param excpected for page number key',
pagerLimitKey: 'your backend param excpected for page size',
totalKey: total records returned in response path for example 'data.total',
filterFieldKey: your filter keys template should set to '#field#' if you need to send params as you set, Default is '#field#_like'
});`
3- add settings object
settings = {
actions: {
custom: [ // if you need custom actions do like that
{ name: 'view-something', title: '<i title="Exception" class="nb-alert"></i>' },
{ name: 'do-custom', title: '<i class="fa fa-pencil"></i>' }
],
add: true, //if you don't need default add button set to false
edit: true, //if you don't need default add button set to false
delete: true, //if you don't need default delete button set to false
position: 'right' // buttons position
}, // remove add , edit , delete objects if you don't use
add: {
addButtonContent: '<i class="nb-plus"></i>',
createButtonContent: '<i class="nb-checkmark"></i>',
cancelButtonContent: '<i class="nb-close"></i>',
},
edit: {
editButtonContent: '<i class="nb-edit"></i>',
saveButtonContent: '<i class="nb-checkmark"></i>',
cancelButtonContent: '<i class="nb-close"></i>',
},
delete: {
deleteButtonContent: '<i class="nb-trash"></i>',
confirmDelete: true,
},
pager: {
display: true // set to false if no need for pagination
},
columns: {
Id: { // set up table cols - Id is a prop name returned from backend
title: 'ID', // display name in table header
type: 'number',
filter: false // add text filter for it or not
},
Name: {
title: 'Full Name',
type: 'string',
filter: false
}
}
};
// Add Filter Data , i used a custom form binded with ngModel above table for filtration, so assume you have a model called filter which get data from external form
FilterData() {
this.source.reset(true); // reset your old filtered data
this.source.setPage(1, false); // set page to 1 to start from beginning
let filterArr = this.getFilterArray(); // add a new filter data, but be careful to not sent any empty data, as it throws an exception
if (filterArr.length)
this.source.setFilter(filterArr, false, false);
this.source.refresh(); // this will call the server with new filter and paginatio data
}
getFilterArray() { // setup new filter
let filterArray = [];
if (this.filter.id)
filterArray.push({ field: 'id', search: this.filter.id });
if (this.filter.name)
filterArray.push({ field: 'name', search: this.filter.name});
return filterArray;
}
onCustomAction(event) { // custom buttons code
switch (event.action) {
case 'view-something':
// put your code here
break;
default:
console.log('Not Implemented Action');
break;
}
}
With this example my data is resource so the datakey is set resource
find below sample code
{
source: ServerDataSource;
constructor(http: HttpClient) {
this.source = new ServerDataSource(http, { dataKey: 'resource', endPoint:'http://localhost:8080/api/v2/mysql/_table/able' })
}
You need to set the dataKey for the ServerDataSource. For example, if your JSON is { data: [...], total: .. }, you need to set dataKey = 'data'.
this worked for me on Angular 8, But Search box functionality needs to be handeled from backend (i.e: localhost:5000/session_info/Seance1?temp_like=30), so backend need to filter (temp_like = value) in database, which make search box retrive live data.
Here is the entire component with Edit and Delete, Enjoy it!
import {Component} from '#angular/core';
import {ServerDataSource} from 'ng2-smart-table';
import {HttpClient} from "#angular/common/http";
import {Setting} from "../../setting";
#Component({
selector: 'ngx-session-man',
templateUrl: './sessions-man.component.html',
styleUrls: ['./sessions-man.component.scss'],
})
export class SessionsManComponent {
settings = {
mode: 'inline',
add: {
addButtonContent: '<i class="nb-plus"></i>',
createButtonContent: '<i class="nb-checkmark"></i>',
cancelButtonContent: '<i class="nb-close"></i>',
},
edit: {
editButtonContent: '<i class="nb-edit"></i>',
saveButtonContent: '<i class="nb-checkmark"></i>',
cancelButtonContent: '<i class="nb-close"></i>',
confirmSave: true,
},
delete: {
deleteButtonContent: '<i class="nb-trash"></i>',
confirmDelete: true,
},
columns: {
name: {
title: 'Séance',
type: 'string',
},
start: {
title: 'Début',
type: 'any',
},
end: {
title: 'Fin',
type: 'any',
},
},
};
source: ServerDataSource;
constructor(private httpClient: HttpClient) {
this.source = new ServerDataSource(this.httpClient, {endPoint: Setting.baseUrl + 'all_sessions_table'});
}
onDeleteConfirm(event): void {
if (window.confirm('Are you sure you want to delete ' + event['data']['name'] + '?')) {
event.confirm.resolve();
this.httpClient.delete<any>('http://localhost:5000/del-seance/' + event['data']['name']).subscribe(
temps => {});
} else {
event.confirm.reject();
}
}
onEditConfirm(event): void {
if (window.confirm('Are you sure you want to edit ' + event['data']['name'] + '\'s name to ' + event['newData']['name'] + '?')) {
event.confirm.resolve();
this.httpClient.post<any>('http://localhost:5000/mod-seance/' + event['data']['name'] + '/' + event['newData']['name'], { title: 'Session deleted' }).subscribe(
temps => {});
} else {
event.confirm.reject();
}
}
}

JayData incorrect behaviour with WebApi v2 Odata during entity Create with kendoGrid

this my Controller:
public class ProductEntityController : EntitySetController<
ProductEntity, int>
{
public IQueryable< ProductEntity> Get(ODataQueryOptions< ProductEntity> parameters)
{
return productList.AsQueryable();
}
public ProductEntity Create(ProductEntity entity)
{
productList.Add(entity);
return entity;
} }
//---------------------------------------
this my JS code:
var context = new $data.initService('/odata');
context.then(function (db) {
var dsD = db.ProductEntity.asKendoDataSource();
grid= $('#gridD').kendoGrid({
dataSource: dsD,
filterable: true,
sortable: true,
pageable: true,
selectable: true,
height: 400,
columns: [
{ field: 'Name' },
{ field: 'Created' },
{ field: 'Index' },
{ field: 'LargeNum' },
{ command: ["edit", "destroy", "update"] }
],
toolbar: ["create", "save", "cancel"],
editable: "inline"
}).data("kendoGrid");
}).fail(function (args) { });
//------
when i "Add New Record" or "Save Changes" , two request are sent to the server(GET and then POST).
i have an error :'result count failed ' at GET Response.
i found a problem in following code in kendo.js:
create: function (options, model) {
var query = self;
query.entityContext.onReady().then(function () {
if (model.length > 1) {
..............
..........
}
else {
console.dir(ctx.storeToken);
model[0]
.innerInstance() // when i comment this line everything is gonna be ok
.save(ctx.storeToken)
.then(function () {
options.success();
})
.fail(function () {
console.log("error in create");
options.error({}, arguments);
});
}
});
}
why is called innerInstance() before save?
how can i fix my problem?
i use Jaydata 1.3.6 with kendo ui and webApi2 Odata on MVC 5
please help me
this problem occurred when i changed default value of key Field(Id) in create mode at Client side. JayData send GET request with filter to the server.therefore i had and error because i didn't have this Id on my entity.then send POST new Entity to the server.
conclusion:don't have permission to edit your key in kendoGrid and handle it at server.

Resources