My redux wizard form doesn't carry over form values - redux

so I have a 4 page form, the first 3 have form fileds while the last one is just a success page shown after the data is submitted.
I have initial values set for fields on each page in my quoteReducer like so:
import {
QUOTE_SUBMIT,
GET_QUOTES,
SET_QUOTE_INITIAL_VALUES
} from "../actions/types";
const initialQuoteForm = {
your_details: {
title: "Mr",
employment_status: "Employed"
},
your_house: {
alarm_type: "Standard",
two_or_more_smoke_alarms: false,
neighbourhood_watch: false,
property_type: "Bungalow",
cover_type: "Holiday Home",
heating_type: "Electric"
},
your_insurance_details: {
first_time_buyer: false,
currently_or_previously_insured: false,
landlord_or_tennant: "Landlord"
}
};
const initialState = {
quotes: [],
currentQuote: {},
quoteInitial: initialQuoteForm,
message: "",
ok: true,
error: []
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_QUOTES:
return {
...state,
ok: action.payload.ok,
message: action.payload.message,
errors: action.payload.errors,
quotes: action.payload.quotes
};
case QUOTE_SUBMIT:
return {
...state,
ok: action.payload.ok,
message: action.payload.message,
errors: action.payload.errors,
currentQuote: action.payload.quote
};
case SET_QUOTE_INITIAL_VALUES:
return {
...state,
quoteInitial: action.payload
};
default:
return state;
}
}
The initial values are set on the form when I look at it in the store. However, when I move to the next page, the values I entered that weren't set initially (for fields without initial values) aren't preserved and the only values in the store for that page are the initial ones again.
The values for fields on the final page ARE saved. Every form is setup to preserve form data like so (second page example)
export default reduxForm({
form: "quote", //Form name is same
destroyOnUnmount: false, // <------ preserve form data
forceUnregisterOnUnmount: true // <------ unregister fields on unmount
})(QuoteFormSecondPage);
Does anyone have any idea what would cause the form fields values not to be preserved.
Note, when I remove initial values the field values are preserved and do carry over!
Edit: Here is my quoteForm.component that displays each page/form
<div className="quote-container">
{page === 1 && (
<QuoteFormFirstPage
close={this.props.toggleModal}
onSubmit={this.nextPage}
currentPage={page}
initialValues={this.props.initialValues}
enableReinitialize={true}
/>
)}
{page === 2 && (
<QuoteFormSecondPage
close={this.props.toggleModal}
previousPage={this.previousPage}
onSubmit={this.nextPage}
currentPage={page}
initialValues={this.props.initialValues}
enableReinitialize={true}
/>
)}
{page === 3 && (
<QuoteFormThirdPage
close={this.props.toggleModal}
previousPage={this.previousPage}
toggleAlternateAddress={this.toggleAlternateAddress}
onSubmit={this.onSubmit}
currentPage={page}
showAlternateAddress={this.state.showAlternateAddress}
initialValues={this.props.initialValues}
enableReinitialize={true}
/>
)}
{page === 4 && (
<QuoteFormSuccessPage
close={this.props.toggleModal}
previousPage={this.previousPage}
currentPage={page}
/>
)}
</div>

Solved, in case anyone runs into the issue in future. I was passing
initialValues={this.props.initialValues}
enableReinitialize={true}
to each component, it only needs to be passed in once, to the first one.

Related

Adding a new item when using useSWRInfinite pushes other items out of the list

I am building a comment system where new replies are added to the start (top) of the list. The pagination is cursor-based.
At the moment, I use mutate to add the newly created comment as its own page to the front of the list.:
const {
data: commentsPages,
: commentsPagesSize,
: setCommentsPagesSize,
//TODO: Not true on successive page load. But isValidating refreshes on refetches
isLoading: commentsLoading,
error: commentsLoadingError,
mutate: mutateCommentPages,
} = useSWRInfinite(
getPageKey,
([blogPostId, lastCommentId]) => BlogApi.getCommentsForBlogPost(blogPostId, lastCommentId));
<CreateCommentBox
blogPostId={blogPostId}
title="Write a comment"
onCommentCreated={(newComment) => {
const updatedPages = commentsPages?.map(page => {
const updatedPage: GetCommentsResponse = { comments: [newComment, ...page.comments], paginationEnd: page.paginationEnd };
return updatedPage;
})
mutateCommentPages(updatedPages, { revalidate: false });
}}
/>
The problem is, SWR immediately starts revalidating the list and pushes the comment at the bottom out of the data set. This behavior is kind of awkward.
Is my only choice do disable automatic revalidation completely? How would you handle this?

How do I filter lists, retrieved via async await, in Svelte?

I'm using Svelte for the first time, and I'm trying to figure out how to apply filtering to a list retrieved from an API. My EventsIndex component is very simple, and comprises the following logic:
import FetchPosts from '../services/postsByType'
import EventCard from '../components/EventCard.svelte'
export let post_type
let events = new FetchPosts( post_type ).get()
let currentCategory = 0
function updateCategory( catId ) {
currentCategory = catId
}
Everything works just as expected.
I'm also using the await template syntax to display the relevant data:
{#await events}
<h1>loading...</h1>
{:then events}
<section class="grid">
{#each events as event}
<EventCard event={event} on:filter={updateCategory}/>
{/each}
</section>
{:catch error}
<p>Something went wrong...</p>
{/await}
The missing piece is the reactive filtered list. I thought I could do the following, but it generates the error events.map is not a function, which makes sense, as the events variable would be an unresolved promise when filteredEvents is invoked.
$: filteredEvents = () => {
if (currentCategory === 0) return events
return events.filter( event => {
return event.categories !== null
? event.categories.some( cat => cat.id == currentCategory )
: false
})
}
So how would I add a reactive filteredEvents function, that I can also include in my template?
Filtering on the events inside the {#each} loop should work. If the filter function was very short like this I'd apply it inline
{#each events.filter(e => e.id === currentId) as event}
Just as an example. Since in your case the filter function is more complex, moving it to a function might be the better option
filterEvents(events, currentCategory) {
if (currentCategory === 0) return events
return events.filter( event => {
return event.categories !== null
? event.categories.some( cat => cat.id == currentCategory )
: false
})
}
</script>
...
{#each filterEvents(events, currentCategory) as event}
(I think you can replace this
return event.categories !== null
? event.categories.some( cat => cat.id == currentCategory )
: false
by this
event.categories?.some( cat => cat.id === currentCategory )

next.js how to pass data to a page

Hi guys I'm learning some next.js and I'm trying to pass data from a data.js file to a page in the pages folder. I tried using getStaticProps but that needs an absolute URL. Below ill show an example of what I'm trying to do. Firstly is the page itself.
const page = ({ data }) => {
return (
<>
<p>{data.name}</p>
</>
);
};
export default page;
Then the data.js file looks like such.
export const user = [
{
id: 'Banana1',
password: 'Apple123',
name: 'Banana',
surname: 'Orange',
birthday: '10 March 2077',
cellNumber: '011 111 1111',
email: 'Banana#apple.com',
}
]
I know there is probably better methods of keeping the data but I'm just looking for something really simple for what I'm trying to do.
With help from #trash_dev I added the import { user } from '../path/to/datafile' in the page.js and also in the page.js removed the ({ data }) as that wasn't needed.
Then with help from #juliomalves when trying to use the data the array position had to be used so in my example it would be as follows:
const page = () => {
return (
<>
<p>{user[0].name}</p>
</>
);
};

AsyncStorage to save state with React Native

I want to save the switch button state so users can't vote twice.
I have a message error : Exception '-[NSDictionaryM length...was thrown while invoking multiSet on target AsyncLocalStorage Any Idea ?
this.state= {
trueSwitchIsOn: false,
};
onSwitchChange(_key){
const{trueSwitchIsOn}=this.state;
switch (this.state.trueSwitchIsOn){
case false:
return(
<TouchableHighlight onClick={this.onPressIcon(_key)}>
{this.setState({trueSwitchIsOn: true})}
</TouchableHighlight>
);
case true:
return(
<TouchableHighlight onClick={this.onUnPressIcon(_key)}>
{this.setState({trueSwitchIsOn: false})}
</TouchableHighlight>
);
}
}
onPressIcon(word){
AsyncStorage.setItem('AlreadyLiked', {trueSwitchIsOn});
const{trueSwitchIsOn}=this.state;
this.setState({trueSwitchIsOn : true});
}
onUnPressIcon(word){
AsyncStorage.setItem('NotAlreadyLiked', {trueSwitchIsOn: false});
const{trueSwitchIsOn}=this.state;
this.setState({trueSwitchIsOn : false});
<Switch>
onValueChange={(value)=>this.onSwitchChange(_key)}
</Switch>
The value passed to AsyncStorage.setItem() needs to be a string. You need to either pass your object to JSON.stringify() or just use a pure string value instead of an object with a "trueSwitchIsOn" boolean property.

How to determine active route in react router

I'm have a Redux app that uses react router with nested routes, like this:
<Provider store={store}>
<Router history={browserHistory}>
<Route name="sales" path="/" component={App}>
<IndexRoute name="home" component={Home} />
<Route name="reports" path="/reports" component={Reports}>
<IndexRoute name="reports-home" component={ReportsHome} />
<Route name="report-1" path="/reports/report-1" component={Report1}/>
<Route name="report-2" path="/reports/report-2" component={Report2}/>
</Route>
</Route>
</Router>
</Provider>
I'm trying to write a breadcrumbs component; so would like to be able to deterime the current route.
I've configured the component to receive the router using the withRouter function provided by react router:
Breadcrumbs = withRouter(Breadcrumbs);
This gives me a router object that looks like this:
Object {__v2_compatible__: true}
__v2_compatible__: true
createHref: createHref(location, query)
createKey: createKey()createLocation: createLocation(location)
createPath: createPath(location, query)
go: go(n)
goBack: goBack()
goForward: goForward()
isActive: isActive(location)
listen: listen(listener)
listenBefore: listenBefore(hook)
push: push(location)
pushState: ()
registerTransitionHook: registerTransitionHook(hook)
replace: replace(location)
replaceState: ()
setRouteLeaveHook: listenBeforeLeavingRoute(route, hook)
setState: ()
transitionTo: transitionTo(nextLocation)
unregisterTransitionHook: unregisterTransitionHook(hook)
__proto__: Object
Can I use this to determine the current route? Is there better way?
Getting location etc. via withRouter was added in react-router version 3.0. Dan Abramov recommends upgrading to 3.0 to use withRouter. From 2.7 to 3.0, it only provided the functions you saw.
Source: https://github.com/ReactTraining/react-router/blob/master/CHANGES.md#v300-alpha1
There is already a module that does this for you, I believe it's called react-router-breadcrumbs. I haven't tried it though.
If you want a custom solution, here's what you could do:
Use the this.props.routes and this.props.params objects. You can then map through the routes and for each entry make a lookup for such key in the params object. You can then create a string with said parameters.
Note that I have given each route (except IndexRoutes) a path attribute, because sometimes I want to display a custom name for a given page. For example:
<Route path="/:productId" name="Start" title="Start" component={StartView} />
Here's the solution on my app:
componentDidMount = () => {
this._prepareBreadCrumbs(this.props);
}
componentWillReceiveProps = (newProps) => {
this._prepareBreadCrumbs(newProps);
}
_prepareBreadCrumbs = (props) => {
let {routes, params} = props;
let breadcrumbPath = "";
let temp = routes.map(
(item, i) => {
if(item.path == null) return null; //if we are visiting an item without a path, ignore it.
if(i < routes.length-1 && routes[i+1].path != null) {
let arr = item.path.split(/[:/]|(:\/)/g); //sometimes the path is like "/:product_id/details" so I need to extract the interesting part here.
arr = arr.map(function(obj) {
return (obj in params) ? params[obj] : obj; //We now have ":product_id" and "details" - the first one will exist in the "params" object.
});
breadcrumbPath += arr.filter(Boolean).join("/") + "/"; //clean out some garbage and add the "/" between paths.
if(i == 0) return <li key={i}><Link to={breadcrumbPath}>YourSite.com</Link></li> //If on the root - display the site name
return <li key={i}><Link to={breadcrumbPath}>{item.name}</Link></li>
} else {
document.title = "YourSite.com - " + item.title; //set the document title if you want
if(i == 0) return <li key={i} className="active"><span>YourSite.com</span></li>
return <li key={i} className="active"><span>{item.name}</span></li>
}
}
);
this.setState({breadcrumbPath: temp});
}
render() {
<p>{this.state.breadCrumbPath || ""}</p>
}
You'd want to put this in your top-level React Component.

Resources