React-router transition always creates handler - reactjs-flux

I just started, so I don't know if this is desired behavior or if I have missed something. I'm using the Flux architecture (specifically Reflux but that should not be relevant). In my super-simple test app:
var App = React.createClass({
render: function () {
return (
<div className="container">
<Header />
<RouteHandler />
<div>
);
}
});
var routes = (
<Route name="app" path="/" handler={App}>
<Route name="employees" handler={Employees}/>
<Route name="company" handler={Company}/>
<Route name="timesheets" handler={Timesheets}/>
<DefaultRoute handler={Company} />
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.getElementById('main'));
});
The handlers are React components, the simplest possible setup. When the app starts I land on the "company" page as expected. Then when I transition to "employees" all is well, but when I transition back to "company", I observe that an entirely new {Company} component is created, thus blowing away my state. This is going to result in many unecessary API calls, unless I'm doing something wrong or not understanding.
Is there a way to tell the Route to use an existing handler if one exists and just re-render it? Instead of creating a new class?

Keeping the state in a flux store
One solution for you would be to keep the state in a flux store. When the component is mounted, request data from the store in the getInitialState() function.
Keeping the state in a parent component
If you do not want to use a flux store (because it increases the complexity of your simple example), I recommend keeping the state in a parent component, and passing it to your as a prop.
Of course, this would only work if your parent component does not become unmounted. I believe that the root component <Handler /> in your example should stay mounted between state transitions in your example.
I recommend that you look at this tutorial that goes over how to pass props to a child component.
This tutorial goes over how to communicate to the parent from the child.

Related

React routing - react-router-dom pushState() call to history happens too many times

I am using react-router-dom (v6) and noticed that I cannot use the browsers back button as expected. It seems like my application (using react/redux) pushes the location multiple times to the browser history when I am routing to a different location in my app.
This results in an application crash in Safari too:
SecurityError: Attempt to use history.pushState() more than 100 times per 30 seconds
The code looks something like this:
App.tsx
...
<BrowserRouter>
<Routes>
<Route path={ROUTES.auth.login} element={<PublicRoute component={Login} />} />
<Route path={ROUTES.auth.register} element={<PublicRoute component={Register} />} />
<Route path={ROUTES.dashboard} element={<PrivateRoute component={Dashboard} />} />
</Routes>
</BrowserRouter>
...
PublicRoute.tsx
...
export default function PublicRoute({ propsForComponent = {}, ...props }: PublicRouteProps): ReactElement {
const { showLoadingScreen } = useSelector((state: RootState) => ({
showLoadingScreen: state.loading.showLoadingScreen,
}));
const { component: Component } = props;
if (showLoadingScreen) return <LoadingOverlay />;
return <Component {...propsForComponent} />;
}
...
I found out that the PublicRoute component gets called multiple times due to the connection to the redux store with the useSelector hook.
showLoadingScreen gets updated when the application is trying to fetch the user, during this time this variable changes.
So my question is now: how do I prevent multiple re-renders, which causes the history.pushState() calls?
I know I could move the LoadingOverlay component to a different entry point. But In PrivateRoute I am also connected to the redux state because I need to access the user object. (I used the PublicRoute component to show a minimal example of whats going on...)
Any help would be highly appreciated,
cheers!

Mixing Alpine.js with 'static' serverside markup, while getting the benefits of binding, etc

I'm new to Alpine and struggling to wrap my head around how to make a scenario like this work:
Let's say I have a serverside built page, that contains some buttons, that represent newsletters, the user can sign up to.
The user might have signed up to some, and we need to indicate that as well, by adding a css-class, .i.e is-signed-up.
The initial serverside markup could be something like this:
<button id='newsletter-1' class='newsletter-signup'>Newsletter 1</button>
<div>some content here...</div>
<button id='newsletter-2' class='newsletter-signup'>Newsletter 2</button>
<div>more content here...</div>
<button id='newsletter-3' class='newsletter-signup'>Newsletter 3</button>
<div>and here...</div>
<button id='newsletter-4' class='newsletter-signup'>Newsletter 4</button>
(When all has loaded, the <button>'s should later allow the user to subscribe or unsubscribe to a newsletter directly, by clicking on one of the buttons, which should toggle the is-signed-up css-class accordingly.)
Anyway, then I fetch some json from an endpoint, that could look like this:
{"newsletters":[
{"newsletter":"newsletter-1"},
{"newsletter":"newsletter-2"},
{"newsletter":"newsletter-4"}
]}
I guess it could look something like this also:
{"newsletters":["newsletter-1", "newsletter-2", "newsletter-4"]}
Or some other structure, but the situation would be, that the user have signed up to newsletter 1, 2 and 4, but not newsletter 3, and we don't know that, until we get the JSON from the endpoint.
(But maybe the first variation is easier to map to a model, I guess...)
Anyway, I would like to do three things:
Make Alpine get the relation between the model and the dom elements with the specific newsletter id (i.e. 'newsletter-2') - even if that exact id doesn't exist in the model.
If the user has signed up to a newsletter, add the is-signed-up css-class to the corresponding <button> to show its status to the user.
Bind to each newsletter-button, so all of them – not just the ones, the user has signed up to – listens for a 'click' and update the model accordingly.
I have a notion, that I might need to 'prepare' each newsletter-button beforehand with some Alpine-attributes, like 'x-model='newsletter-2', but I'm still unsure how to bind them together when Alpine has initialising, and I have the data from the endpoint,
How do I go about something like this?
Many thanks in advance! 😊
So our basic task here is to add/remove a specific item to/from a list on a button click. Here I defined two component: the newsletter component using Alpine.data() creates the data (subs array), provides the toggling method (toggle_subscription(which)) and the checking method (is_subscribed(which)) that we can use to set the correct CSS class to a button. It also handles the data fetching in the init() method that executes automatically after the component is initialized. I have also created a save method that we can use to send the subscription list back to the backend.
The second component, subButton with Alpine.bind() is just to make the HTML code more compact and readable. (We can put each attribute from this directly to the buttons.) So on click event it calls the toggle_subscription with the current newsletter's key as the argument to add/remove it. Additionally it binds the bg-red CSS class to the button if the current newsletter is in the list. For that we use the is_subscribed method defined in our main component.
.bg-red {
background-color: Tomato;
}
<script src="https://unpkg.com/alpinejs#3.x.x/dist/cdn.min.js" defer></script>
<div x-data="newsletter">
<button x-bind="subButton('newsletter-1')">Newsletter 1</button>
<button x-bind="subButton('newsletter-2')">Newsletter 2</button>
<button x-bind="subButton('newsletter-3')">Newsletter 3</button>
<button x-bind="subButton('newsletter-4')">Newsletter 4</button>
<div>
<button #click="save">Save</button>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('newsletter', () => ({
subs: [],
init() {
// Fetch list of subscribed newsletters from backend
this.subs = ['newsletter-1', 'newsletter-2', 'newsletter-4']
},
toggle_subscription(which) {
if (this.subs.includes(which)) {
this.subs = this.subs.filter(item => item !== which)
}
else {
this.subs.push(which)
}
},
is_subscribed(which) {
return this.subs.includes(which)
},
save() {
// Send this.sub to the backend to save active state.
}
}))
Alpine.bind('subButton', (key) => ({
'#click'() {
this.toggle_subscription(key)
},
':class'() {
return this.is_subscribed(key) && 'bg-red'
}
}))
})
</script>

Array no reactive when modifying an element, only when adding or deleting

I'm doing crud of publications with Vue components and Laravel.
I have one parent component called publications.vue which has 2 childs called create.vue and list.vue, and list.vue I have another to childs called remove.vue and update.vue.
The thing is when I add or remove a publication in the array it works perfect, but when I modify an element it doesn't react. The controller works perfect, but unless I refresh I don't get anything on the screen.
This is my code:
<template>
<div class="main">
<create
:user="user"
v-if="showCreate"
#create="addPublicationOnClient"
/>
<list
v-for="(publication, index) in publications"
:key="publication.id" :publication="publication" :user="user"
#deleted="deletePublicationOnClient(index)"
#updated="updatePublicationOnClient(index, ...arguments)"
/>
</div>
</template>
addPublication(publication) {
this.publications.unshift(publication); // works perfect
},
deletePublication(index) {
this.publications.splice(index, 1); // works perfect
},
updatePublication(index, editedPublication) {
console.log(editedPublication); // shows the correct value of the edited publication
Vue.set(this.publications, index, editedPublication); // do not react. do not show anything
this.publications.splice(index, 1, editedPublication) // do not react neither. do not show anything
console.log(this.publications); // shows the correct values in the array
}
I will really appreciate any help because I really stuck and I have read a lot of posts, but can't find a solution.
Vue has some really tricky behavior when it comes to arrays of objects.
Vue is watching your array, and when the array's .length is modified or if one of its values is modified vue can "see" that change.
When you update fields of an object in the array you will not get the reactivity, because to Vue the array has not changed. This is because the array's values are references to the object, and when you update the object, those references don't change.
Your approach above seems fine to me, but again there can be really weird issues.
I will highlight two tools to combat these reactivity issues. The first is better for your situation I believe.
Explicitly modify the length of the array.
updatePublication(index, editedPublication) {
this.deletePublication(index);
this.addPublication(index, editedPublication);
}
Force re-rendering using :key. When a key changes in a template, it will force re-rendering of all child elements.
<template>
<div class="main" :key="'updated-'+updated">
...
</template>
data() {
return {
updated: 0,
};
},
...
updatePublication(index, editedPublication) {
this.publications.splice(index, 1, editedPublication);
this.updated++;
}

Binding Vue.js to all instances of an element, without(?) using Components

Today I'm learning Vue.js, and I have a few ideas of where it might be really useful in a new project that's an off-shoot of an existing, live project.
I like the idea of trying to replace some of my existing functionality with Vue, and I see that Components may be quite handy as quite a lot of functionality is re-used (e.g. Postcode lookups).
Once of the pieces of functionality I've used for an age is for invalid form elements - currently in jQuery when a form input or textarea is blurred I add a class of form__blurred, and that is coupled with some Sass such as:
.form__blurred {
&:not(:focus):invalid {
border-color:$danger;
}
}
This is to avoid styling all required inputs as errors immediately on page load.
I'm totally fine with doing this in jQuery, but I figured maybe it could be done in Vue.
I have an idea of how I might do it with components thanks to the laracasts series, but my form inputs are all rendered by Blade based on data received from Laravel and it doesn't seem like a neat solution to have some of the inputs rendered in Javascript, for a number of reasons (no JS, confusion about where to find input templates, etc).
I figured something like the following simplified example would be handy
<input type="text" class="form__text" v-on:blur="blurred" v-bind:class="{ form__blurred : isBlurred }" />
<script>
var form = new Vue({
el : '.form__input',
data : {
isBlurred : false
},
methods : {
blurred : function() {
this.isBlurred = true;
}
}
});
</script>
That actually works great but, as expected, it seems like using a class selector only selects the first instance, and even if it didn't, I'm guessing changing the properties would apply to all elements, not each one individually.
So the question is - is this possible with pre-rendered HTML, in a way that's smarter than just looping through a selector?
If it is, is there a way to create the Vue on a .form element and apply this function to both .form__input and .form__textarea?
Or, as is probably the case, is this just not a good use-case for Vue (since this is actually a lot more code than the jQuery solution).
Sounds like a great use case for a Custom Directive.
Vue allows you to register your own custom directives. Note that in Vue 2.0, the primary form of code reuse and abstraction is components - however there may be cases where you just need some low-level DOM access on plain elements, and this is where custom directives would still be useful.
<div id="app">
<input type="text" name="myforminput" v-my-directive>
</div>
<script>
Vue.directive('my-directive', {
bind: function (el) {
el.onblur = function () {
el.classList.add('form__blurred');
}
}
});
var app = new Vue({
el: '#app'
});
</script>
You can also add the directive locally to a parent component, if it makes sense for your application.

Bind model to view from controller with alloy appcelerator

EDIT: Heres the example: https://github.com/prakash-anubavam/alloypreloadedsqlitedb/tree/master/app
And sumarizing, how does the view details.xml know which model is it taking the data from?
Coming from asp.net mvc, im having some issues understanding this MVC.
I can understand how to use tables and such on the view like:
<TableView id="table" dataCollection="fighters" onClick="showId" dataTransform="transformData">
And fetch the data in the controller, i know it will use the global (singleton) collection of fighters and that will bind the model to the view.
But i have come across an example (i cant really find now) where it had a View, with no table, just some labels and text='{variableName}', which i assume it gets from the model.
However the controller, did not assign the model (coming from an args[0] because it was always called from another controller which had the actual table), but it never assigned the model instance to the view in any way... so the question is how did it work? Is alloy smart enough to detect the actual model instance and use it? How would i do it? Something like $model = ...; or $.details.model = ...; or something like that? How did the view know where to take '{variableName}' from if the model was never assigned with a table or something.
This is actually a carryover hack, that may not work in the future, according to this thread.
If you take a look at index.js in your example (the controller), the model is assigned by the onClick event of the TableView:
function showId(e) {
if (e.row.model) {
var detailObj=fighters.get(e.row.model);
// Assigning to the $model actually sets this model as the one to bind too
var win=Alloy.createController('detail',{"$model":detailObj});
win.getView().open();
}
}
This is a "specai" variable that is automagically assigned for databinding, and is how it works underneath the covers (or did work under the covers).
This is undocumented and NOT ideal or recommended.
I've found Tony Lukasavage answer the cleaner aproach to bind a existing model to view:
You can find it here Josiah Hester answer is based on it (yeah, beware it's kind of a hack).
Although, Fokke Zandbergen gave an alternative worth looking at, maybe less hackish, don't know.
Expanding Josiah answer, you could do as follows:
On Master view:
<Alloy>
<Collection src="modelName" />
<View id="topview" class="container">
<TableView id="tblModels" dataCollection="modelName" dataTransform="transformModel">
<Require src="rowModel"/>
</TableView>
</View>
</Alloy>
Then, on master controller:
//retrieve the id of the model
var thisId = e.row.thisIndex;
//pass special key $model
var detailController = Alloy.createController("detail", { "$model": Alloy.Collections.detail.get(thisId) });
detailController.getView().open();
Then, on detail view:
<Alloy>
<View class="container" >
<Label text="{id}"/>
<Label text="{fullName}"/>
</View>
</Alloy>
On detail controller: do nothing special.
If you have a transform function on master controller, it returns a object and you can use its properties inside detail view like "{fullName}".

Resources