Nested Stencil Routes/Components - stenciljs

I’ve been trying to implement nested routes/components. Can someone just please explain to me how to nest routes/components. The stencil route docs aren’t of much help.
Say In my component I have on the left a sidenav with a couple of stencil-route-link then on the right I should show the routed component.

As the Stencil team has still not released a wiki entry for this, here's a little update to this topic using #stencil/core: 1.3.2 and #stencil/router: 1.0.1.
The trick is to use the routeRender property in combination with slots and inline child routes:
<stencil-router>
<stencil-route-switch>
<stencil-route url="/"
exact={ true }
component="app-home"
/>
<stencil-route url="/me"
routeRender={ () => (
<app-me>
<stencil-route url="/me/login"
component="app-login"
/>
<stencil-route url="/me/profile"
component="app-profile"
/>
</app-me>
) }
/>
</stencil-route-switch>
</stencil-router>;
If you define the child routes inside your component (in this example app-me) the route may not be recovered after a reload or by navigating directly to it. Thus you have to define them inside your global stencil-route-switch.

StencilJS docs state:
You should have one single stencil-router component in your project. This component controls all interactions with the browser history and it aggregates updates through an event system.
So there's going to be one place to define your routes. There's also an example on their webpage:
<stencil-router>
<stencil-route url="/" component="landing-page" exact={true}/>
<stencil-route url="/demos" component="demos-page"/>
<stencil-route url="/demos/rendering" component="fiber-demo"/>
</stencil-router>
Entering /demos/rendering would render both demos-page and fiber-demo components. They won't be nested though.
The docs also mention the routeRender attribute, which can be used to do some sort of nesting. For example:
<stencil-route url="/" exact={true} routeRender={(props) => (
<app-root history={props.history}>
<app-sidebar />
<app-content />
</app-root>
) />

I know this is kind of old question but took me some time to figure this out, so I want to share here in case someone run into similar issues.
<stencil-router>
<stencil-route-switch scrollTopOffset={0}>
<stencil-route url="/" component="app-home" exact={true} />
<stencil-route url="/profile/:name" component="app-profile" />
<stencil-route>
<stencil-route-switch scrollTopOffset={0}>
<stencil-route url="/" component="app-home" exact={true} />
<stencil-route url="/profile/:name" component="app-profile" />
<stencil-route url="/docs" routeRender={()=>
<div>
<div>Document Heading</div>
<stencil-route-link url="/docs/setup">Setup</stencil-route-link>
<stencil-route-link url="/docs/todo">TODO</stencil-route-link>
<stencil-route-link url="/docs/profile">Profile</stencil-route-link>
<stencil-route url="/docs/setup" routeRender={()=>
<div>Set up document</div>}>
</stencil-route>
<stencil-route url="/docs/todo" routeRender={()=>
<div>TODO document</div>}>
</stencil-route>
<stencil-route url="/docs/profile" component="app-home" />
</div>
}>
</stencil-route>
</stencil-route-switch>
</stencil-router>
Some quick explanation:
As titsjmen shared, looks like it is important to have only one component within your app
You can, however nest "stencil-route" and hopefully with the code example above you can see it how it works. I tested it with both using routeRender as well as component approach (both works!)
Hopefully this will be useful to someone :)
EDIT (additional note):
In case this is not clear, this code block represents the area where you want to swap components based on the URL - back to original question, to achieve what you want, side nav can be just a matter of having bunch of "stencil-router-link" (which doesn't have to be placed in the same component - I have separate component called "sidenav-item" and seem to work well)

Related

Slot for wrapper content in Vue

Imagine that you have a user list component UserList that shows users in scrollable view. Each users details are represented with UserDetails component wrapped with Card component.
UserList
<template>
<Card>
<UserDetails />
</Card>
<Card>
<UserDetails />
</Card>
...
</template>
Now imagine that you're using this UserList component and want to re-implement the wrapper for UserDetails without modifying it's contents.
Adding a slot would work for replacing the wrapper but everything inside the wrapper would need to be re-implemented as well.
It would be nice if we could write Vue like this:
UserList
<template>
<slot name="wrapper">
<Card>
<template #content>
<UserDetails />
</template>
</Card>
</slot>
...
</template>
Consuming component:
<UserList>
<template #wrapper>
...
<NewImplementation>
<slot :name="content" />
</NewImplementation>
</template>
</UserList>
It would work by using slots in "reverse" way of how we're used to.
This isn't valid syntax but I bet someone else has thought about the same problem. The need to replace some content with slot but not all of it.
Wrapper component could be given as property but I think it's not the correct solution because we have slots to avoid doing just that.
Are there any good solutions?
If I understood right, what you're looking for is Teleports (previously called Portals) which is only available in version 3.0
What you want is problematic. Vue has no direct support for something like that.
Let me establish some naming conventions before I try to explain why:
Child - UserList component
Parent - component consuming Child
Main problem in this scenario is that slot content is completely generated in the consuming component and just passed to a Child component using
$slots - this is for normal slots. $slots.wrapper is just a static array of VNodes
$scopedSlots - for scoped slots. $scopedSlots.wrapper is a function. When Child renders it calls that function and renders returned an array of VNodes
(Note: In Vue 3 it is just $slots. All slots are functions)
What that means is that Child component has no way to somehow change the content of the slot (e.g. include something in the middle). It can pass some data into it (slot scope) but that's all...
Possible workarounds:
1. Just pass a wrapper component as a prop
Wrapper component could be given as property but I think it's not the correct solution because we have slots to avoid doing just that.
Well not really. Slots have a different purpose. If you want to change the wrapper and let the rest of the Child intact, this is pretty good and simple solution
2. Give a Parent the component it should render inside the wrapper using slot scope
This is something very well demonstrated in route-view component of Vue Router 4.x - see the docs
<UserList v-slot="{ UserComponent, UserData }">
<NewWrapper>
<component :is="UserComponent" v-bind:data="UserData" />
</NewWrapper>
</UserList>
So you have <component :is="UserComponent" /> instead of <UserDetails />. This is useful as the UserList component can now control what component is rendered inside the wrapper (and even change it dynamically)

React Native and React Native Navigation - Handle Back (goBack) issue

thank you for taking time to read & answer this question. I'll give my best to explain the issue I'm having.
The HomeStack component holds bottom navigation for four screens. One screen that is giving me a headache is ProjectsStack
export const ProjectsStack = (): ReactElement => {
return (
<ProjectsStackNav.Navigator initialRouteName='ProjectsScreen' screenOptions={defaultScreenOptions}>
<ProjectsStackNav.Screen name='ProjectsScreen' component={ProjectsScreen} />
<ProjectsStackNav.Screen name='CompletedProjectsScreen' component={CompletedProjectsScreen} />
<ProjectsStackNav.Screen name='ProjectTasksScreen' component={ProjectTasksScreen} />
<ProjectsStackNav.Screen name='CompletedProjectsTasksScreen' component={ProjectTasksScreen} />
</ProjectsStackNav.Navigator>
);
};
As you can see the ProjectTasksScreen is a component that renders a project based on the props => if it's open it will render ProjectTasksScreen otherwise it will render CompletedProjectsTasksScreen which is basically the same screen (reusability at its finest lol)
In order to go to the CompletedProjectsTasksScreen, you need to come from ProjectTasksScreen.
Now, the issue is when you want to go back from CompletedProjectsTasksScreen it will not go to ProjectTasksScreen, rather it will navigate to the previous screen of ProjectTasksScreen which is ProjectsScreen.
Is there a better solution to this problem than refactoring everything into it's screen & component?
navigation.push method instead of navigation.navigate may resolve your issue
https://reactnavigation.org/docs/navigating/#navigate-to-a-route-multiple-times

React-Admin: How to make <Filter> mandatory with "alwaysOn" using "list"

I couldn't find a way to ONLY FETCH or LIST resources, when a user fills in the <SelectInput> (as rendered by <ReferenceInput>).
Due to a domain restriction,I need to ask the user to choose some Customer in order to query the Orders.
Is that possible? How can I pull it off?
To be clear, in their Demo project, the "Orders" can be listed using "Customer" as a <Filter>. (see image below)
A workaround could be also valid. Thanks.
Ok, this is an interesting scenario.
Let's closely look at the resource, and how it routes to various components.In this case, our interest is within the <list> and what exactly it renders:
// Set up the filter
const CustomerFilter = (props) => (
<Filter {...props}>
// setup your `ReferenceInput` and `SelectInput`
</Filter>
);
// Within your rendered component (OrderList)
<List {...props} filters={<CustomerFilter />}>
<Datagrid>
{console.log(props)} // Look out for the `match.params` prop
// ...
// Check for "customer_name" param within your props (verbose)
{match.params.customer &&
<TextField label="Customer" source="customer_name" />
}
</Datagrid>
</List>
Please note: don't just copy/paste this code. It's a verbose version.
The implication is that you can render customer name (or whatever) within your list depending on a passed parameter.

React-Moment gives: "Invariant Violation: Text strings must be rendered within a <Text> component"

I've narrowed this down to find that the error occurs only when I try to pass the <Moment /> component as described in the React-Moment documentation. So far, I haven't found any explanations specific to this package and hope someone out there has had a similar issue!
The docs spell out a usage like this:
import Moment from 'react-moment';
// then within the class component:
return (
const dateToFormat = '1976-04-19T12:59-0500';
<Moment>{dateToFormat}</Moment>
);
And I'd like to take a raw date string like this:
<Text>Created {this.props.postDate}</Text>
which is stored thusly: "postDate": "2019-01-31T04:13:31.224Z"
but so far, anytime I add <Moment>{this.props.postDate}</Moment>, whether that be outside of or inside of the existing <Text /> block I get red:
It's in the documentation:
https://github.com/headzoo/react-moment#usage-with-react-native
<Moment element={Text}>{dateToFormat}</Moment>

How to make react-admin resource route default to show instead of edit?

The react-admin docs explain the default routing for resources as:
/posts/:id maps to PostEdit
/posts/:id/show maps to PostShow
I'd like my app to default to viewing, and only allow editing if the user clicks the edit icon. However the default for all the reference links is to point to /resource/:id.
Is there a way to swap the routing such that /resource/:id maps to Show and /resource/:id/edit is used for Edit? Or maybe change the reference links to /resource/:id/show?
This may not be exactly the thing you are looking for, but default actions can be switched, so click on item can lead to show instead of edit:
https://marmelab.com/react-admin/List.html#the-datagrid-component
export const PostList = (props) => (
<List {...props}>
<Datagrid rowClick="show">
...
</Datagrid>
</List>
);
Reference links can be set to reference "show" by add link="show" to the ReferenceField.
See https://github.com/marmelab/react-admin/issues/247#issuecomment-287819508
You might want to create some custom routes matching your needs redirecting to custom Create/Show components like
const customRoutes = [
<Route key="editPost" exact path="/posts/:id" component={PostEdit} />,
<Route key="showPost" exact path="/posts/:id/show" component={PostShow} />,
];
function App() {
return (
<Admin
...
customRoutes={customRoutes}
>
...
</Admin>
)
}
Source: https://marmelab.com/react-admin/Admin.html#customroutes