Remix.run - common shared components - remix.run

I’m just getting started learning remix.run and whilst I’ve gone through the tutorials there’s one bit I’m stuck on how I should implement it in remix.
If I wanted to display a common header that might toggle a sign in/sign out button based on the users logged in state where would this live?
My nextjs thinking would be to create the components and reference them in the common document. I know I can do this in the remix.server and remix.client files, but as my “login” component is and isn’t a route (I.e I might want to POST to the route when a user submits the login form but GET /login isn’t really a route) how would you structure something like this and would doing this even allow me to have loader and action functions in the shared component?
Do I just need to adjust my thinking about how to achieve this in remix or am I overthinking it and the above is perfectly valid?
I tried the following and it works. But then I end up just creating an empty "logout" route to process the form data with an action and loader that process the form in the case of the action or just redirect if a GET request via the loader. Is this the best approach?
export const SignIn = ({user}) => {
return (
<>
<form method="POST"action="/logout">
<input type="hidden" id="some" value="foo" />
{user ?
(
<button>sign out</button>
)
: (
<button>sign in</button>
)
}
</form>
</>
)
}
Thanks

based on https://remix.run/docs/en/v1/tutorials/jokes#build-the-login-form
it does indeed look like an empty route for logout:
import type { ActionFunction, LoaderFunction } from "#remix-run/node"
import { redirect } from "#remix-run/node"
import { logout } from "~/utils/session.server"
export const action: ActionFunction = async ({request}) => {
return logout(request);
};
export const loader: LoaderFunction = async () => {
return redirect("/");
};

Related

Show div only if user has logged in - Laravel Vue Inertia Jetstream

I've created a webapp using Laravel Jetstream. I'm also using Vue and Inertia. Jetstream is dealing with the backend. A new user will land on Welcome.vue and upon loggin in, he will get to Dashboard.vue. This is my web.php:
Route::middleware([
'auth:sanctum',
config('jetstream.auth_session'),
'verified',
])->group(function () {
Route::get('/dashboard', function () {
return Inertia::render('Dashboard', [
'user' => Auth::user()
]);
})->name('dashboard');
});
I'd like to simply show a message on the Dashboard navbar, like "Welcome, name". This would be just a test, as I'd want to show several buttons, texts and stuff to auth users only across different views.
Problem is, my Welcome and Dashboard views are almost identical, they're 95% made up of components. It look like this:
<script setup>
import { Head, Link } from '#inertiajs/inertia-vue3';
import Main_w_side from '../Components/Custom/main_w_side.vue';
import Navabar from '../Components/Custom/Navabar.vue';
import Our_team from '../Components/Custom/OurTeam.vue';
import Portfolio from '../Components/Custom/Portfoglio.vue';
import CustomFooter from '../Components/Custom/CustomFooter.vue';
import { provide } from 'vue'
defineProps({
canLogin: Boolean,
canRegister: Boolean,
laravelVersion: String,
phpVersion: String,
});
</script>
<template>
<Head title="Dashboard v0.1"/>
<Navabar class="bg-white shadow dark:bg-gray-800" />
<Portfolio/>
<Main_w_side/>
<Our_team/>
<CustomFooter/>
</template>
So the Navbar.vue that Dashboard uses is the same that Welcome uses. The same goes for portfolio, Main_w_side, Our_team,and such.
I know I should use the v-if method
<p class="pl-12" v-if="loggedIn">You're logged in</p>
to show a div if a certain condition is satisfied, but I haven't found any guidance online, as most of them refer to blade, or to webapps made without Jetstream (using a user controller which I don't currently have)
I was also thinking that I should probably use Provide/Inject to let every component across the web app know if the user visiting has logged in. But I still don't know how I would do that.
I feel like there has to be an industry standard for this that I'm not aware of, as virtually almost every website would need this feature (instead of re-creating whole pages just to have a different div somewhere)
As per the default InertiaJS installation, you should be able to do something like this:
v-if="$page.props.auth.user"
so your code should look like this:
<p v-if="$page.props.auth.user" class="pl-12" v-if="loggedIn">You're logged in</p>
There's no need for provide/inject and you are able to retrieve the authenticated user on every component, no matter how deep in the component tree the component is, using inertia's $page instance property or usePage() method.
As #Abdullah Hejazy mentioned, in Vue2 and Vue3 you can simply do:
<p v-if="$page.props.auth.user" class="pl-12">You're logged in</p>
$page.props.auth.user will be null if the user isn't logged in. It is comming from the HandleInertiaRequests middleware.
In Vue3 you can also do something like this:
<script setup>
import { usePage, computed } from '#inertiajs/vue3'
const loggedIn = computed(() => {
return !!usePage().props.auth.user
})
</script>
<template>
<p v-if="loggedIn" class="pl-12">You're logged in</p>
</template>

Remix run - submitting an action and getting errro "root" - does not have an action, but you are trying to submit to it

Im having a bit of trouble getting my action to dispatch in remix run - I have an Aside which comes out with all the data from my shopping cart - I have a form that collates all the data - and when I want the checkout to be created I want to call the action
<Form action='/products' method="post">
{cart.map((item, idx) => (
<div key={idx}>
<input readOnly value={item.product.id} type="hidden" name="id"/>
<input readOnly value={item.quantity} type="hidden" name="quantity"/>
</div>
))}
<button
className="mr-2 m"
> Add to Cart
</button>
</Form>
export const action: ActionFunction = async ({request}) => {
// get the form data from the POST
const formData = await request.formData()
const id = formData.getAll('id')
const quantity = formData.getAll('quantity')
const newObj = id.map((data, index) => {
return { variantId: data, quantity: quantity[index] }
} )
const cart = await createCheckout(newObj)
return cart
}
From the data that is requested here my checkout URL is generated so i need to wait for the response. When I submit i get a 405 error saying method not allowed
react_devtools_backend.js:4026 Route "root" does not have an action, but you are trying to submit to it. To fix this, please add an `action` function to the route
This is the error message but I cant seem to find anywhere in the docs how to add a action function to the route? because I swear I am already doing this?
tldr;
I ran into this same issue and was able to solve by changing my action url to include ?index at the end.
Details
My "products" file was located at /products/index.tsx
In order for remix to not think I was referring to root I had to use the following action route:
action="/products?index"
Just using action="/products" alone did not work.
Once I added the ?index part to the action, everything worked as expected.
According to the remix docs:
If you want to post to an index route use ?index in the action: action="/accounts?index" method="post" />
For more details, see: https://remix.run/docs/en/v1/api/remix#form
Also, note that most of the time you can just leave off the action and the Form will use the route in which it is rendered.

React Router Switch Redux Dispatch

I have the below switch statement that routes the user to correct component based on the link they are on.
const Router = (props) => {
switch(props.page) {
case 'Equities' :
this.props.pageName('Equities');
this.props.pageURL('/Equities');
return <Equities />;
case 'Daily' :
return <Daily />;
default :
return ( <Redirect to="/Equities" /> )
}
}
const content = ({ match }) => {
return (
<div className="content">
<Router page={match.params.type} />
</div>
);
}
const mapDispatchToProps = {
pageURL,
pageName
};
export default connect(mapDispatchToProps)(content);
On the 4th line above, I am trying to dispatch an action to Redux to update page name and URL in the redux store that the user is on. I get the below error:
How can I dispatch actions based on the page user is on so I update name and URL to whichever page user is visiting?
So, for anyone experiencing this problem, it seems to be caused by my error on adding redux to the page crashing with the compose module.
My component structure for the app is like this:
App -> Skeleton -> TopBar, Sidebar, Content
So inside Content component I have a switch that displays the correct content for user. I was trying to add this functionality to Content. Now I added to Skeleton, and it works fine and is much better because I don't need to add now 8 different statements inside switch saying if match this do this do that. Now all I have is this.props.pageName(match.url); this.props.pageURL(match.params.type); so I record in redux the user is on and that's all.

Avoid double ajax call

I'm really new to React so I'm trying to manage it by made some examples. I made this component that made an ajax call to render a simple list.
import React from "react";
import axios from 'axios';
import Page from '../components/Page/Page';
import ListJobs from '../components/ListJobs/ListJobs';
let state ;
class Home extends React.Component{
constructor(props){
super(props);
this.state ={jobs:[]};
}
componentDidMount(){
var _this = this;
this.serverRequest = axios.get("http://codepen.io/jobs.json")
.then(function(result){
_this.setState({
jobs:result.data.jobs
});
});
}
componentWillUnmount() {
}
render(){
return(
<div>
<Page title="Home">
<p>Home</p>
<ul>
{this.state.jobs.map(function(job,index) {
return(
<ListJobs key={index} job={job}/>
);
})}
</ul>
</Page>
</div>
);
}
}
export default Home;
It calls another child component to render the li elements.
Everytime I call this component it starts with this ajax call, so I was wondering if there is a way to save the result of this ajax call and re-use it, instead of launching every time the request. I tried to do like this https://jsfiddle.net/corvallo/mkp4w8vp/
But I receive this error in the developer console:
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of Home
Thank you in advance for your help
If you want the ajax calls only when the app launches, then you can make ajax calls on the parent component (probably App) and then pass it as a props to the Home component
EDIT
if you want to call the ajax only from the component, I think you can implement a cache or some sort e.g using localstorage
example
componentDidMount(){
let cache = JSON.parse(localStorage.getItem('homeCache') || "{}");
if(cache.hasOwnProperty('cached') && cache.hasOwnProperty('jobs')){
this.setState({jobs:cache.jobs});
}else{
(/*YOUR API HERE*/).then(function(result){
_this.setState({jobs:result.data.jobs});
localStorage.setItem('homeCache', JSON.stringify({cached: true, jobs: result.data.jobs}));
});
}
}
and everytime when the user exits the app, clear the cache (or anytime you want the cache to be cleared)
localStorage.setItem('homeCache', JSON.stringify({}));
I think that's one solution which popped out of my head right now..

Ember-Validation Issue w/ Ember CLI & Ember Data

I'm attempting to implement form validation on a new Contact in my app using the ember-validations library. I'm currently using Ember Data with fixtures, and I've opted to place the validations in the model like the example in this video. I've been grappling with this for days now, and still can't seem to figure out why the validations aren't working. I'm not getting any indication that errors are even firing.
//app/models/contact.js
import DS from "ember-data";
import EmberValidations from 'ember-validations';
//define the Contact model
var Contact = DS.Model.extend(EmberValidations, {
firstName: DS.attr('string'),
lastName: DS.attr('string'),
});
//Create Contact fixtures
Contact.reopenClass({
FIXTURES: [...]
});
Contact.reopen({
validations: {
firstName: {
presence: true,
length: { minimum: 2 }
},
lastName: {
presence: true
}
}
});
export default Contact;
I'm new to Ember, and have been advised to put the following logic in routes instead of the controller. I haven't seen any examples of this being done with ember-validations, so I'm unsure if that's my issue regarding validations.
app/routes/contacts/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('contact');
},
actions: {
createContact: function() {
var contact = this.get('currentModel');
this.transitionTo('contacts');
contact.save();
alert(contact.errors);
},
cancelContact: function() {
var contact = this.get('currentModel');
contact.destroyRecord();
this.transitionTo('contacts');
}
}
});
My other suspicion is that I may not be handling the errors in html correctly?
//app/templates/contacts/new.hbs
{{#link-to 'contacts' class="btn btn-primary"}}Contacts{{/link-to}}
<form>
<label>First Name:</label>
{{input type="text" value=model.firstName}}<br>
<span class="error"></span>
<label>Last Name:</label>
{{input type="text" value=model.lastName}}<br>
<span class="error"></span>
</form>
<button {{action "createContact"}} class="btn btn-primary">Submit</button>
<button {{action "cancelContact"}} class="btn btn-warning">Cancel</button>
<br>
Here is my controller
//app/controllers/contacts.js
import Ember from "ember";
export default Ember.Controller.extend({
});
I'm enjoying Ember, but this issue is stonewalling me greatly. Any help would be much appreciated.
I'm going through this exact thing and have some advice. First I would say validate where you need to ask something if it is valid. You might need to do this on the component if it's a form, you might need to do this on a model if you want to ensure it's valid before saving, or maybe on a route if there are properties there that you're wanting to check.
In any case whatever method you pick, I would highly recommend using the ember-cp-validations addon. For what it's worth, Stephen Penner (ember.js core team) has contributed to the addon, too. It's all ready for Ember CLI as well.
The setup is actually very similar to what you're doing, but instead of reopening the class, you would define with it a mixin (example from their docs). To create the mixin that's used they have a factory called buildValidations. The nice thing is that you can use this on any Ember object.
Once you've defined your validation and mixed it into the create of your object, ie: Ember.Object.create(Validations, {}); where Validations is the mixin you've created just above (like in the docs). You are all set.
Within scope of that object you now have a validations property on the object, so something like:
var Validations = buildValidations({
greeting: validator('presence', true)
});
export default Ember.Object.create(Validations, {
greeting: 'Hello World',
actions: {
announce: function() {
alert('The current validation is: ' + this.get('validations.isValid'));
// per property validation is at:
alert('The individual validation is: ' + this.get('validations.attrs.greeting.isValid'));
}
}
})
Handlebars:
Looks like the form is {{ validations.isValid }}.
You can also <a {{action announce}}>announce the validation</a>.
Also, take a look at all the docs, there are is even more properties and use cases that this addon takes care of, including handlebars helpers, ajax/async resolution of validation, and some validators to use. If you don't find the one you're after make a function validator. Using the function validator all over, easy, make it re-usable with ember generate validator unique-username.
Hope this gets you off in the right start. This is a relatively new project but has decent support and responses on the issues has been quick.
Should also mention that because these validations are based on computed properties they work in an "Ember way" that should work great, including your models.

Resources