Redux form - how to set fields as touched - redux

I'm working with form that consists of multiple pages and I want to solve validation.
When I hit Submit button all fields on the present page shows error messages beneath, but if I change the page then I need to hit submit again because these fields weren't set as touched.
My problem would be solved if I could for example set all fields on the page as touched, once the form has flag anyTouched: true.
I'm using redux-form: '^6.0.0-rc.4' and I have one container where I include redux-form and multiple components consisting of fields.

I think your problem was the opposite way around, but in case anyone lands here as I did looking for a way to have anyTouched set after any field in the form is touched...
In redux-form 6 and above you have to explicitly choose the behaviour you want with the form-level configurations touchOnChange and touchOnBlur - see the docs here - by default nothing is configured and so nothing happens.
const Form = reduxForm({
form: 'my-form',
touchOnChange: true,
touchOnBlur: true
})(...)
These flags make it so that any given field is marked as touched (and therefore anyTouched is marked true on the form) when that field's onChange or onBlur handler is called, respectively.

I should have looked better:
Redux form returns touch as a prop to the component. The function takes names of fields as a parameter, so I'm checking in componentWillUpdate when submitFailed will change and then I'm gonna touch all fields that are not valid.
componentWillUpdate(nextProps) {
const {
formName: { syncErrors },
submitFailed,
touch
} = this.props
if (submitFailed !== nextProps.submitFailed) {
const toTouch = []
for (const key in syncErrors) {
syncErrors.hasOwnProperty(key) && toTouch.push(key)
}
touch(...toTouch)
}
}

In redux-form 7.4.2. This can be achieved by checking to see if the form is valid.
If valid you can can load one of your other pages.
If the form is not valid, use reduxForms getFormSyncErrors selector and pass in the keys returned by this object to the reduxForm touch property.
import React, { Component } from 'react'
import { compose } from 'redux';
import { connect } from 'react-redux';
import { reduxForm, getFormSyncErrors } from 'redux-form';
class MyComponent extends Component {
...
this.props.valid ?
// navigate away
: this.props.touch(...Object.keys(this.props.formErrors))
...
}
function mapStateToProps(state) {
return {
formErrors: getFormSyncErrors('myForm')(state)
}
}
export default compose(
connect(mapStateToProps, null),
reduxForm({form: 'myForm'})
)(MyComponent)

Related

Why is a route dynamically rendered when it contains useSearchParams in Next 13?

I noticed that when a route contains at least one child client component that uses useSearchParams hook, the route becomes dynamically rendered.
In the docs, it states that a component is dynamically rendered only if it uses 1) dynamic functions or 2) fetch is made with {cache: "no-store"}. It also states that the dynamic functions in Next 13 are cookies and headers.
useSearchParams alone doesn't fulfil either criterion, so why would its containing route still be dynamically rendered?
Example:
// ClientComponent.tsx
"use client"
import { useSearchParams } from "next/navigation";
export default function ClientComponent() {
const searchParams = useSearchParams();
return <div>ClientComponent</div>;
}
// page.tsx
import ClientComponent from "./ClientComponent";
export default function Page() {
return (
<div>
<ClientComponent />
</div>
);
}

VurRouter transitions on more than one router

I prepared a boiled-down example on stackblitz:
https://stackblitz.com/edit/quasarframework-vy4eiw?file=README.md
The problem I try to resolve is this:
A quasar 2 app build with vite and vue 3 (and GSAP) uses layouts
Currently there are 2 layouts: StartpageLayout for the startpage at route ´/´and MainpageLayout for all the other pages at route ´/main´ and any children of it (/main/:child´`)
The MainpageLayout also contains the navigation menu
The navigation menu should be created (later on with an animation) when any route starting with ´/main´ is hit and destroyed, when there is a change to any other route
While navigating through any ´/main[/:child]´ route, the nav menu shall remain "stable" (not rebuild or anything like that)
The app uses 2 router-views for this, one in App.vue, one in MainLayout.vue. Changes between those states should mainly be handled in onBeforeRouteLeave and onBeforeRouteUpdate
To check, whether the app is in a "layout context", the routes have a meta.layoutKey, which is used in router guards to check, whether sth changed or not:
// Example: src/layouts/MainLayout.vue
onBeforeRouteUpdate((to, from, next) => {
console.log('%cMAIN_LAYOUT: onBeforeRouteUpdate invoked', consColRouter);
// compare meta.layoutKeys of routes
if (from.meta.layoutKey !== to.meta.layoutKey) {
console.warn(' YES, invoke router guard onBeforeRouteUpdate - animate!');
next() // this would be actually be called form an onComplete animation callback
} else {
console.log(' NOPE, do not invoke router guard onBeforeRouteUpdate');
next() // invoked as written
}
})
A pinia store manages state that (should) remember(s) activateMenu:
// Pinia store "pageTransitions.js" (composition API)
import { ref, reactive, computed } from 'vue'
import { defineStore } from 'pinia'
export const usePageTransitionsStore = defineStore('pageTransitions', () => {
// Pinia state in composition API
const pageTransitions = ref({
parent: false,
activateMenu: false
})
const setPageTransitions = (level, bool) => {
switch(level) {
case 'parent': {
pageTransitions.value.parent = bool
break
}
default: { console.log('default... must never happen!'); }
}
}
const setActivateMenu = (bool) => {
pageTransitions.value.activateMenu = bool
}
return {
pageTransitions,
setPageTransitions,
setActivateMenu
}
})
If store.pageTransitions.activateMenu is true, show the menu, if false, remove it. It is imported in MainLayout in order to use the activateMenu constant to manage the state of the nav menu. The onMount method sets this store variable to true. And it should be set to false in a ònBeforeRouteLeave`... (not yet implemented)
While the change from the startpage at ´/´to the MainPage at ´/main´ and vice versa works fine (even with animation, due to the store variable store.pageTransitions.parent), I keep having troubles with changes from ´/main´ to any child route ´/main/:child´ and vice versa. E.g. when the app is at /main and the user clicks on ´items 101´, the whole MainLayout is reloaded - also App.vue runs through its onAppear hooks again (see console) – and the nav is set to false again.
The goal is to not influence the MainLayout not its nested nav menu at all.
I wonder, why those reloads happen? MainLayout's onBeforeRoute checks against meta.layoutKey which does not change. But then I also observe that the pinia store gets loaded again, and the actiavteMenu var is set up false again...
Does anybody see my error(s)?

How to remove a field after inserting it?

I am building a custom form, I have successfully been able to add new fields at run time into the form as:
Options.schema.properties= {...Options.schema.properties, [key]: {type: "string"} }
Options.uiSchema= {...Options.uiSchema, [key]: { "ui:widget": DefaultInput, classNames: "col-md-4"} }
Where key is the field id, and Options is observable via Mobx
In the form, I am using observable pattern via Mobx to update the schema.properties
Something like this:
class StudentsTab extends Component {
render() {
return (
<MyForm schema={Options.schema} uiSchema={Options.uiSchema} widgets={Options.widgets}
fields={this.customFields}
onChange={log("changed")}
onSubmit={log("submitted")}
onError={log("errors")}
/>
)
}
}
export default observer(StudentsTab);
Although a new fields can be added in this way, however, I could not remove them, my attempt was like:
delete Options.schema.properties[key]
delete Options.uiSchema[key]
I can see that the field id is removed, but its not removed from the DOM
Any idea? How would I remove a field after adding it?

How to populate variable from service after component has initialized angular2

I have a routed application that loads and works fine aside from the fact that I have an *ngIf element that does not show up on route change UNLESS I reload the page. I have a token variable from a service class where I store it in local storage, and I want to show my logout button when the token is not null.
When the site loads, it sets the token value to null which hides the button (expected behavior) but when logging in and seeing the token to the guid, the variable doesn't show a token value unless I reload the page which reinitializes the header component.
Abbreviated code below.
Import { Component } from '#angular/core';
Import { globalService } from './shared/globalService';
#Component({
selector: 'header-ele',
template: ` <div *ngIf="loginToken != null"><button>logout</button></div>
<router-outlet></router-outlet>`
})
export class headerComponent {
loginToken:any;
constructor(globalService: globalService){
this.logonToken = globalService.getUser(); //this either returns null or a token string
}
}
Then I have another component that changes the route which all works fine
//I have all of my correct imports and all works
export class loginComponent {
login(){
// I pass login params and get success
this.globalService.setUser(returnedData.LogonToken)
}
}
And in globalService I set logonToken = returnedData.LogonToken, BUT the button in my headerComponent doesn't show up unless I reload the page. So, I'm wondering if there is a way to reinitialize the headerComponent on route change success to get the token from globalService in the constructor function, or if there is a better way to share that parameter between the globalService and the headerComponent.
Abbreviated code due to submitting from mobile, but should get the idea.
Still learning the ins-and-outs of angular 2.
Your problem is that this.logonToken is only getting the value once in the constructor. So when you logout, the user token in your globalService is really null but not reflected in your header.component. One solution is to use observables to your header component and subscribe to your globalService. Or you directly use the global service variable to your template like this
<div *ngIf="globalService.getUser()"><button>logout</button></div>
Hope this helps.
According to Angular2 official documentation you can use ngOnInit function but I'll try to give you an example:
import { Component, OnInit } from '#angular/core';
import { Hero } from '../hero';
import { HeroService } from '../services/hero.service';
import { HeroDetailComponent } from '../hero-detail/hero-detail.component';
#Component({
selector: 'app-dashboard-component',
templateUrl: './dashboard-component.component.html',
styleUrls: ['./dashboard-component.component.css'],
providers: [HeroDetailComponent]
})
export class DashboardComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) {}
getHeroes(): void {
this.heroService.getHeroes().then(
(heroes) => this.heroes = heroes.slice(1,5)
);
}
ngOnInit(): void {
this.getHeroes();
}
}
This is, by the way, the example developed by Google's folks, ngOnInit will be invoked when you instantiate the component. Hope this answer your question and of course helps you. Cheers, sigfried.
Solution 1: Refactor your service so that it returns an observable, promise or event, then on your component make use of the async-pipe like so: <div *ngIf="(loginToken | async) != null">.
Solution 2: #Input the loginToken from a higher level component and Angular will run the change detection when the value changes.
Either way I would move the logic inside the constructor to a lifecycle hook like ngOnInit or ngOnChanges.

Structuring a reducer for a simple CRUD application in redux

So I'm creating what is at it's core a very simple CRUD-style application, using React + Redux. There is a collection of (lets call them) posts, with an API, and I want to be able to list those and then when the user clicks on one, go into a detail page about that post.
So I have a posts reducer. Originally I started using the approach taken from the redux real-world example. This maintains a cache of objects via an index reducer, and when you do a "get post" it checks the cache and if it's there, it returns that, else it makes the appropriate API call. When components mount they try to get things from this cache, and if they're not there they wait (return false) until they are.
Whilst this worked OK, for various reasons I now need to make this non-caching i.e. everytime I load the /posts/:postId page I need to get the post via the API.
I realise in the non-redux world you would just do a fetch() in the componentDidMount, and then setState() on that. But I want the posts stored in a reducer as other parts of the app may call actions that modify those posts (say for example a websocket or just a complex redux-connected component).
One approach I've seen people use is an "active" item in their reducer, like this example: https://github.com/rajaraodv/react-redux-blog/blob/master/public/src/reducers/reducer_posts.js
Whilst this is OK, it necessitates that each component that loads the active post must have a componentWillUnmount action to reset the active post (see resetMe: https://github.com/rajaraodv/react-redux-blog/blob/master/public/src/containers/PostDetailsContainer.js). If it did not reset the active post, it will be left hanging around for when the next post is displayed (it will probably flash for a short time whilst the API call is made, but it's still not nice). Generally forcing every page that wants to look at a post to do a resetMe() in a componentWillUnmount fells like a bad-smell.
So does anyone have any ideas or seen a good example of this? It seems such a simple case, I'm a bit surprised I can't find any material on it.
How to do it depends on your already existing reducers, but i'll just make a new one
reducers/post.js
import { GET_ALL_POSTS } from './../actions/posts';
export default (state = {
posts: []
}, action) => {
switch (action.type) {
case GET_ALL_POSTS:
return Object.assign({}, state, { posts: action.posts });
default:
return state;
}
};
It is very easy to understand, just fire an action to get all your posts and replace your previous posts with the new ones in the reducer.
Use componentDidMount to fire the GET_ALL_POSTS action, and use a boolean flag in the state to know if the posts where loaded or not, so you don't reload them every single time, only when the component mounts.
components/posts.jsx
import React from 'react';
export default class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
firstLoad: false
};
}
componendDidMount() {
if (!this.state.firstLoad) {
this.props.onGetAll();
this.setState({
firstLoad: true
});
}
}
// See how easy it is to refresh the lists of posts
refresh() {
this.props.onGetAll();
}
render () {
...
// Render your posts here
{ this.props.posts.map( ... ) }
...
}
}
We're just missing the container to pass the posts and the events to the component
containers/posts.js
import { connect } from 'react-redux';
import { getPosts } from './../actions/posts';
import Posts from './../components/posts.jsx';
export default connect(
state => ({ posts: state.posts }),
dispatch => ({ onGetAll: () => dispatch(getPosts()) })
);
This is a very simple pattern and I've used it on many applications
If you use react-router you can take advantage of onEnter hook.

Resources