I have a number of steps in a redux-form wizard.
How the steps validate is through submit validation (which is where the main problem lies i think)
When I validate on submit on Step1, the submitSecceeded flag is set to true :
class Step1 extends React.Component {
constructor(props) {
super(props)
}
render() {
this.props.submitSucceeded //when submit succeeds
}
}
export default reduxForm({
form: 'wizard',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
})(Step2);
However when I step into Step2 then the submitSecceeded flag is also set to true.
class Step2 extends React.Component {
constructor(props) {
super(props)
}
render() {
this.props.submitSucceeded //also true!
}
}
export default reduxForm({
form: 'wizard',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
})(Step2);
I am guessing redux form is treating the whole wizard as one form. So if the first passes, then it is true for all the other forms.
My question is, does the wizard allow each page to be treated like an individual form?
Related
I'm need to verify that entered text im my TextField is an email. I'm usually using Form Group to do this. In my Form Control property im using "updateOn: 'blur'. But I have problem if I have only 1 TextField in current view. It seems like (blur) is not called.
I'm do this in few other places in my app but only there it don't work. Only difference is that there is only one TextField in component, and in others is few more (2-5).
'''''HTML
<TextField id="field" class="input" hint="E-mail" keyboardType="email" returnKeyType="done" formControlName="email" #emailQ [ngClass]="{niepoprawne: !emailQValid}" (returnPress)="wyslij()"></TextField>
<Button text="Wyślij" class="button" (tap)="wyslij()"></Button>
'''''''TS
export class ZapomnialemComponent implements OnInit {
form: FormGroup;
#ViewChild('emailQ', { static: false }) emailQRef: ElementRef<TextField>;
emailQValid: boolean = true;
wyslanePrzyp: boolean = false;
constructor() {
}
ngOnInit() {
this.form = new FormGroup({
email: new FormControl(null, { updateOn: 'blur', validators: [Validators.required, Validators.email] })
});
this.form.get('email').statusChanges.subscribe(status => {
this.emailQValid = status === 'VALID';
});
}
wyslij()
{
if(!this.form.valid)
{
this.displayAlertDialog("Wprowadź poprawny adres e-mail")
return;
}
this.wyslanePrzyp = true;
}
You have only one text field in your form and have set updateOn to blur. Your text field will not hit blur unless you change the focus to next field Or programatically remove focus from the text field.
I'm building an app with React, Redux and TypeScript.
In the top navbar I have a "Log in" link which when clicked dispatches an action. For now, all this action does is set a boolean called auth to true.
I have a lot of connected components which listen to that auth property of the redux store and decide which sub-components to render, based on ternary expressions that evaluate this.props.auth.
I was surprised to see that when I clicked "Log in" some components would rerender as expected while others would have their state successfully changed but would only alter their display if I refreshed the page or routed away and came back. After some hours of hair-pulling I believe I finally isolated the difference between the two kinds of components described above: if the ternary expression that evaluates this.props.auth is directly inside the render() method, the component behaves as expected, however, if the ternary expression is inside a .map() function which is then called by the render() method, then this weird behavior happens where I have to refresh in order for the correct rendering to match the prop values. What's going on? Does this lose it's value, is this a sync/async problem?
class LatestArticles extends Component<LatestArticlesProps> {
public latestArticlesList: JSX.Element[] = Articles.map((a: IArticle) => {
return (
<React.Fragment key={a.id}>
// some TSX
{this.props.auth === true ? <UserImgOverlay /> : <UnlockButton />}
// some more TSX
</React.Fragment>
)
});
public render(): JSX.Element {
return (
<React.Fragment>
// some TSX
{this.latestArticles}
// some more TSX
</React.Fragment>
)
}
Do let me know if you need any more context but I would like to ask for help understanding what's going on. Why does the prop change only trigger a rerender if the ternary expression is directly inside the render() method and is there any way to go around this while still mapping the data? Thank you for your attention.
EDIT
Here's my mapStateToProps:
// Components/Navbar/index.tsx
const mapStateToProps = ({ articles, auth }: IApplicationState) => {
return {
articlesPerPage: articles.articlesPerPage,
articlesPerPageStep: articles.articlesPerPageStep,
auth: auth.auth
}
}
I actually have another example of this kind of behavior happening with an onClick method:
class LatestArticles extends Component<LatestArticlesProps> {
public latestArticlesList: JSX.Element[] = Articles.map((a: IArticle) => {
return (
<React.Fragment key={a.id}>
// some TSX
<StarsRating rating={3} onClick={this.handleRatingClick} />
// some more TSX
</React.Fragment>
)
});
constructor(props: LatestArticlesProps & IOwnProps) {
super(props)
this.handleRatingClick = this.handleRatingClick.bind(this);
}
public render(): JSX.Element {
return (
<React.Fragment>
// some TSX
{this.latestArticles}
// some more TSX
</React.Fragment>
)
}
public handleRatingClick = () => {
alert('Clicked!')
}
}
^ When I click on the star nothing happens, but if I extract <StarsRating rating={3} onClick={this.handleRatingClick} /> from the .map function and put it directly inside the render() method, I get the alert saying 'Clicked!'... I suspect this is losing its value but I'm not sure how to test that.
I think the problem is with the latestArticlesList variable. It is initialized too early. It should be a function:
public latestArticlesList: JSX.Element[] = () => Articles.map((a: IArticle) => {
I assume Articles should be this.props.articlesPerPage.
And finally when you use it, call it:
<React.Fragment>
// some TSX
{this.latestArticles()}
// some more TSX
</React.Fragment>
in a react UI I have a table component. You can edit one row of the table by clicking a edit button or you can add a new record by clicking a "new-record-button". When clicking the edit button an redux-action is triggered which takes the row and sets a visible property of a modal dialog. When the "new-record-button" is clicked an action is triggered which creates a new empty data item and the same modal dialog is triggered.
In the modal dialog I have several text components with onChange method.
in this onChange-method the data-item is written.
When to user clicks a save-button the edited dataItem is saved to the database.
So my code looks like:
const mapStateToProps = (state) => ({
dataItem: state.datItemToEdit || {},
...
});
...
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
dataItem.carId = text;
break;
}
...
}
this.forceUpdate();
}
...
<TextField
...
onChange={event => this.handleTextChange(event)}
/>
I have several question regarding this approach. First I do not understand why in handleTextChange we can write to dataItem. It does work apparently.
dataItem.carId is set in the example code but I thought
const {dataItem} = this.props;
gives us a local read-only variable dataItem just to read from the props...
Next thing I think is a poor design. After reading in a book about react I think we should not write to props but only set a state.
In my example I get the the dataItem from the redux-state. The mapStateToProps maps it to the (read-only) props of the component, right?!. But I want to EDIT it. So I would have to copy it to the state of my component?
But where to do it?
Once in the state of my component I could simply call this.setState for the various text-fields and the component would render and I could abstain from forceUpdate(), right?!
Can someone explain how the redux status plays together with the component status and props for this example?
In redux or react, you shouldn't write to the props directly because you should keep your props as immutable. Redux forces us to use immutable state because state is a source of truth for the application. If the reference to state changes then only your app should render. If you'll mutate your state (objects) then the references don't get changed and your app doesn't know whether some state has been changed or not. React/Redux doesn't give you read-only objects automatically. You can mutate them anytime but as I told you, it can cause problems that Your app won't know when to re-render. If you want to have this read-only property inherently, you should probably use immutable.js
About your second question that you'll have to copy the props to the component's state and where you should do it. You should do it in the constructor of the component and you should use immutibility helper
import React from React;
import update from 'immutibility-helper';
class Modal extends React.Component {
constructor(props){
this.state = {
dataItem: dataItem,
};
}
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.state.dataItem, text); //fire a redux action to update state in redux
this.setState(update(this.state, {
dataItem: {
carId: {$set: text},
}
});
break;
}
...
}
}
}
You wouldn't have to do forceUpdate in such case because the reference to state will change and the component will re-render itself.
Also, you can use forceUpdate in your application but personally I don't find it a great idea because when React/Redux is giving you the flow of state, by using forceUpdate, you're breaking the flow.
The last question is how redux and react state plays together. That is also a matter of choice. If I have a app level state, e.g., in your case you've some app level data, you should put that in your redux state and if you have a component level things, such as opening a modal or opening a third pane. That's the convention I follow but that can really depend on how you want to exploit react and redux state.
Also, in above code, I put the redux state in component state too (because you asked where to put that) but Ideally you should fire a redux action and update in redux state. In this way, you will restrict yourself from state duplication in react and redux.
import React from React;
import {updateItem} from './actions';
class Modal extends React.Component {
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.props.dataItem, text); //fire a redux action to update state in redux
break;
}
...
}
}
}
const mapStateToProps = (state) => ({
dataItem: getDataItem(state), //get Data Item gets Data from redux state
});
export default connect(mapStateToProps, {updateItem: updateItem})(Modal);
in Actions:
updateItem = (dataItem, text) => dispatch => {
dispatch({type: 'UPDATE_ITEM', payLoad: {dataItem, text});
};
in Reducer:
export default (state = {}, action) => {
switch(action){
case 'UPDATE_ITEM': {
return {
...state,
dataItem: {
...action.dataItem,
carId: action.text,
}
};
}
}
}
In this way, your state will be pure and you don't have to worry about immutibility.
EDIT:
As constructor will be called only once, you should probably use componentWillReceiveProps so that whenever you render the component, you get the next updated props of the component. You can check whether the carId of dataItem is same or not and then update the state.
componentWillReceiveProps(nextProps){
if(nextProps.dataItem.carId !== this.props.dataItem.carId){
this.setState({dataItem: nextProps.dataItem});
}
}
You should only use redux when you want different, unrelated components in your app to know and share the specific state.
e.g. - When a user logs in to your app, you might want all components to know that user so you'll connect your different containers to the user reducer and then propagate the user to the components.
Sounds like in this case you have a classic use case for using the inner state.
You can use the parent of all TextFields to maintain all rows, edit them by index, etc.
Once you start using redux, it's really easy to make the mistake of transferring the entire state of the components to the reducers, I've been there and stopped doing it a while ago :)
We are using Animated and react-native-animatable quite heavily and starting to notice slowness on some old devices. All animations set useNativeDriver which makes us believe that we may have a few too many animations.
Is there a way to overwrite the Animated prototype to completely disable animations? I looked into this and it didn't seem simple.
Another option I'm considering is to leave my fade animations in but set the initial value in the constructor to the final value. This approach definitely doesn't show any animations but would it also bypass the animation in the native bridge as the value isn't changing?
class Item extends Component {
constructor(props) {
super(props);
this.state = {
opacity: 1 // Notice how this is set to 1
}
}
componentDidMount() {
setTimeout(() => {
this.setState({opacity: 1})
}, 1000)
}
render() {
return (
<Animatable.View style={{opacity}} easing='ease-in' transition='opacity' duration={500} useNativeDriver={true} />
)
}
}
Just create a wrapping component for it and use that instead of Animated.View
export default const AnimatedViewWrapper = (props) => {
if (/* slow device check */) {
return React.createElement(View, props)
} else {
return React.createElement(Animated.View, props)
}
}
You might need to filter the props you receive because View does not have many of the props that Animated.View has. You can get them through View.propTypes. You might need to do this only if __DEV__ is true as propTypes are stripped out in production builds
I have a save button binded to the dirty state, when form is dirty, enable the button, otherwise disable it.
This is good for editing an item. But when I add an item, the save button is disabled by default, because dirty is false by default.
Is there a way to set the form to dirty state?
According to Action Creators documentation of redux-form, you can do it when you initialize data for your form:
import { initialize } from 'redux-form';
export const initializeForm = (formName, newData) => async (dispatch, getState) => {
await dispatch(initialize(formName, newData, false));
};
Also, you can set additional options in that function f.e
import { initialize } from 'redux-form';
export const initializeFormWithOldValues = (formName, newData) => async (dispatch, getState) => {
await dispatch(initialize(formName, newData, false, { keepSubmitSucceeded: true, keepValues: true }));
};
Nobody answered the question yet.
What I am doing now is to create a canSave prop to my component.
canSave && dirty can determine the visibility status of the save button.
When adding an item, canSave is set to true, save button is always visible.
When editing an item, canSave is false, the visibility is determined on dirty prop.
I'm still hoping to find a more elegant answer here.
You can control the save button state yourself. All you need to do is to not enable the Save/Submit button