Replace form.change from react-final-form in react-admin v4 - react-admin

I have this component made for react-admin v3 that allows me to generate an id code. Now I'm upgrading to version 4 of react-admin and I don't know how to replace the part of the code where I do.
form.change("referredCode", code);
Here is the complete code of the component.
import React, { useEffect, useState } from "react";
import { TextInput, useDataProvider, LoadingIndicator } from "react-admin";
import { useForm } from "react-final-form";
import { randomIdGenerator } from "../../helpers/randomIdGenerator";
export default function UserReferredCode({ record }) {
const { referredCode } = record;
const [code, setCode] = useState("");
const [isLoading, setIsLoading] = useState(false);
const dataProvider = useDataProvider();
const form = useForm();
useEffect(() => {
if (!referredCode) {
// Generar id aleatorio
setIsLoading(true);
setCode(randomIdGenerator(6));
}
}, []);
useEffect(() => {
if (code) {
dataProvider
.getList("users", {
pagination: { page: 1, perPage: 1 },
filter: { referredCode: code },
})
.then(({ data }) => {
if (data.length > 0) {
setCode(randomIdGenerator(6));
} else {
setIsLoading(false);
}
})
.catch((e) => {
setIsLoading(false);
console.log(e);
});
}
form.change("referredCode", code);
}, [code]);
return (
<>
{isLoading ? (
<LoadingIndicator />
) : (
<TextInput
disabled
source="referredCode"
name="referredCode"
type="text"
placeholder="Code"
initialValue={referredCode || code}
/>
)}
</>
);
}

You should have a Form component wrapping all of this.
Check the Form documentation https://marmelab.com/react-admin/doc/4.0/Form.html
then you you can access it from useFormContext() (from react-hook-form).
the form has action setValue that accepts name and value
https://react-hook-form.com/api/useform/setvalue

Related

dynamic textInput re-renders the whole component

It is part of a course so library is not an option. Basically, given a json object, generate a form. I can see the elements but I can't type in them. From my understanding, on each typing, the component is being rendered so the useState is being re-initalized. The only way I can type is if i remove the
value={formFields[id]}
from the TextInput.
https://snack.expo.io/#wusile/tenacious-fudge
Here is my code:
/* eslint-disable react/jsx-closing-bracket-location */
import React, { useContext, useState, useEffect } from 'react';
import { View, ScrollView, Text } from 'react-native';
import { TextInput, Button } from 'react-native-paper';
/*
Build a form dynamically from jsonData
see examples/sampleform.json file
*/
const defineFormFields = (data) => {
/*
Define the state to use along with default values.
*/
const stateData = {};
data.forEach((e) => {
stateData[e.id] = e.defaultValue || '';
});
return stateData;
};
const MyComponent = ({ jsonData }) => {
const [formFields, updateFormFields] = useState(defineFormFields(jsonData));
const [currentSegmentElements, updateCurrentViewElements] = useState([]);
const updateFormData = (fieldName, value) => {
const updatedValue = {};
updatedValue[fieldName] = value;
updateFormFields({
...formFields,
...updatedValue
});
};
const elementTypes = {
text(label, id) {
return (
<TextInput
key={id}
accessibilityHint={label}
label={label}
defaultValue={formFields[id]}
value={formFields[id]}
placeholder={label}
onChangeText={(value) => updateFormData(id, value)}
/>
);
}
};
const buildSegment = () => {
/*
Which segment/portion of the json to show
*/
const uiElements = [];
jsonData.forEach((e) => {
const definition = elementTypes[e.type](
e.label,
e.id
);
uiElements.push(definition);
});
updateCurrentViewElements(uiElements);
};
useEffect(() => {
buildSegment();
}, []);
return (
<ScrollView>
<View>
<View>
{currentSegmentElements.map((m) => m)}
</View>
</View>
</ScrollView>
);
};
const FormBuilder = React.memo(MyComponent);
export default FormBuilder;
Now where I need a form, I do:
const jsonData = [
{
"id":"FN",
"label":"FIrst Name",
"type":"text"
},
{
"id":"SN",
"label":"Last Name",
"type":"text"
},
{
"id":"countryCode",
"label":"Country Code",
"defaultValue":"US",
"type":"text"
}
]
<FormBuilder jsonData={jsonData} />
replace your useEffect by this.
useEffect(() => {
buildSegment();
}, [formFields]);

React navigation and loading component in react native

Hello I have a react component like which either display a list of items or opens another component which allowes me to select some value. Here is the code
import React, { PureComponent } from "react";
import { Text, View } from "react-native";
import { withNavigationFocus } from "react-navigation-is-focused-hoc";
import { NavigationActions } from "react-navigation";
import { connect } from "react-redux";
import ClassListing from '../../components/ClassListing/ClassListing';
import Actions from "../../state/Actions";
import { default as stackStyles } from "./styles";
import {
StyleCreator
} from "../../utils";
let styles = StyleCreator(stackStyles.IndexStyles)();
#withNavigationFocus
#connect(() => mapStateToProps, () => mapDispatchToProps)
export default class ClassList extends PureComponent {
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
return {
header: null,
tabBarOnPress({ jumpToIndex, scene }) {
params.onTabFocus();
jumpToIndex(scene.index);
},
};
};
constructor(props) {
super(props);
this.state = {};
console.ignoredYellowBox = ["Warning: In next release", "FlatList"];
}
componentDidMount() {
console.log("componentDidMount");
this.props.navigation.setParams({
onTabFocus: this.handleTabFocus
});
}
componentDidUpdate() {
console.log("I am updated");
}
_handleNavigationBar = () => (
<View style={styles.headerContainer}>
<Text style={styles.headerLeftTitle}>Klasselister</Text>
</View>
);
handleTabFocus = () => {
this.props.resetClassList();
// setTimeout(() => {
// this._openChildSchoolSelector();
// },500);
};
_openChildSchoolSelector = () => {
if (
!this.props.childrenSelection.selectedDependant ||
!this.props.childrenSelection.selectedSchool
) {
console.log("navigate to child selector");
this.props.navigation.dispatch(
NavigationActions.navigate({
routeName: "ChildSchoolSelector",
})
);
}
};
render() {
return (
<View>
{this._handleNavigationBar()}
{this.props.classList &&
this.props.classList.classList &&
this.props.classList.classList.length > 0 ? (
<ClassListing list={this.props.classList.classList} />
) : (
null
// this._openChildSchoolSelector()
)}
</View>
);
}
}
const mapStateToProps = (state) => {
const { classList, childrenSelection } = state.appData;
console.log("[Classlist] mapStateToProps", classList);
console.log("[Classlist] mapStateToProps", childrenSelection);
return {
classList,
childrenSelection
};
};
const mapDispatchToProps = (dispatch) => ({
resetClassList: () => {
dispatch(Actions.resetClassList());
},
});
My issue is that when I come to this tab, I want to reset the list which came from server and then open the school selector again, which I am doing in handleTabFocus. But there I have to first call
this.props.resetClassList();
this._openChildSchoolSelector()
The issue is that this._openChildSchoolSelector() is called while this.props.resetClassList() hasn't fininshed.
this.props.resetClassList() works like this
it calls an action like this
static resetClassList() {
return {
type: ActionTypes.RESET_CLASS_LIST
};
}
which then cleans the classlist like this
case ActionTypes.RESET_CLASS_LIST:
return createDefaultState();
in ClassListReducer.js
and also in ChildrenSelectionRedcuer.js
case ActionTypes.RESET_CLASS_LIST:
return createDefaultState();
Any hints to solve this? My project uses very old React navigation v1
One thing which I did was to use this
componentWillReceiveProps(nextProps) {
console.log(this.props);
console.log(nextProps);
if (
nextProps.isFocused &&
nextProps.focusedRouteKey == "ClassList" &&
(!nextProps.childrenSelection.selectedDependant ||
!nextProps.childrenSelection.selectedSchool)
) {
console.log("componentWillReceiveProps");
this._openChildSchoolSelector();
}
if (
this.props.isFocused &&
this.props.focusedRouteKey === "ClassList" &&
nextProps.focusedRouteKey !== "ClassList"
) {
console.log("reset");
this.props.resetClassList();
}
}
But not sure if this is an elegant way of doing this

I am using redux in react native application to fetch and display data but its not updating on data change from backend

I am using Redux in my React-Native application.
I am fetching the data from api call and on success rendoring it on ListItem.
I am able to fetch and display data but data is not auto updating unless and until I revisit the page.
Even values are not storing into the app
I am calling method from actions in constructor and componentDidMount method.
Can you Please check the code and tell me where am I going wrong.
Here is action.js
import {
FETCHING_PRODUCT_REQUEST,
FETCHING_PRODUCT_SUCCESS,
FETCHING_PRODUCT_FAILURE
} from './types';
export const fetchingProductRequest = () => ({
type : FETCHING_PRODUCT_REQUEST
});
export const fetchingProductSuccess = (json) => ({
type : FETCHING_PRODUCT_SUCCESS,
payload : json
});
export const fetchingProductFailure = (error) => ({
type : FETCHING_PRODUCT_FAILURE,
payload : error
});
export const fetchProduct = () => {
return async dispatch => {
dispatch(fetchingProductRequest());
try {
let response = await fetch("http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop");
let json = await response.json();
dispatch(fetchingProductSuccess(json));
} catch(error) {
dispatch(fetchingProductFailure(error));
}
}
}
My reducer.js
import {
FETCHING_PRODUCT_REQUEST,
FETCHING_PRODUCT_SUCCESS,
FETCHING_PRODUCT_FAILURE
} from './../actions/types';
const initialState = {
loading : false,
errorMessage : '',
shops: []
}
const products = ( state = initialState, action ) => {
switch(action.type) {
case FETCHING_PRODUCT_REQUEST :
return { ...state, loading: true} ;
case FETCHING_PRODUCT_SUCCESS :
return { ...this.state, loading: false, shops: action.payload };
case FETCHING_PRODUCT_FAILURE :
return { ...state, loading: false, errorMessage: action.payload};
}
};
export default products;
product.js
import * as React from 'react';
import { FlatList , ActivityIndicator} from 'react-native';
import { ListItem } from 'react-native-elements';
import { fetchProduct } from './../../actions/products';
import { connect } from 'react-redux';
import propTypes from 'prop-types';
class Product extends React.Component {
constructor(props) {
super(props);
this.props.fetchProduct();
this.state = {
loading : true,
shops : '',
isFetching: false,
active : true,
}
}
fetchProducts() {
const shopid = 13;
fetch(`http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop`)
.then(response => response.json())
.then((responseJson)=> {
this.setState({
loading: false,
shops: responseJson
})
alert(JSON.stringify(this.state.shops));
})
.catch(error=>console.log(error)) //to catch the errors if any
}
componentDidMount(){
// this.fetchProducts();
this.props.fetchProduct().then(this.setState({loading : false}));
}
renderItem = ({ item }) => (
<ListItem
title={item.name}
subtitle={item.name}
leftAvatar={{
source: item.avatar && { uri: item.avatar },
title: item.name[0]
}}
bottomDivider
chevron
/>
)
render () {
if(!this.state.loading)
{
if(this.props.shopsInfo.loading)
{
return (
<ActivityIndicator/>
)
}
else
{
return (
<FlatList
vertical
showsVerticalScrollIndicator={false}
data={this.props.shopsInfo.shops}
renderItem={this.renderItem}
/>
)
}
}
else
{
return (
<ActivityIndicator/>
)
}
}
}
Product.propTypes = {
fetchProduct: propTypes.func.isRequired
};
const mapStateToProps = (state) => {
return { shopsInfo: state };
}
function mapDispatchToProps (dispatch) {
return {
fetchProduct: () => dispatch(fetchProduct())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Product);
1. Not updating on data change from backend.
You have to call an api on regular interval to get updated data. Redux implementation doesn't mean it will fetch data from server whenever there is any change.
2. Even values are not storing into the app
If you are expecting redux will store data even if you will close/kill an application than it will not. You have persist data in-order to use it or store it in cache. Take a look at redux-persist
The problem is your passing wrong props in mapStateToProps function.
In reducer your updating the response value in shop props.
In order to get the updated value you need to pass shops property to get the value.
const mapStateToProps = (state) => {
const { shops: state };
return {shops};
}

Can't I return a form custom error anymore with "Altering the Form Values before Submitting"?

React-admin version 2 used redux-form. Together with the documentation "Altering the Form Values before Submitting" i created a custom button that returns a form error from a custom function, or does a save.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { crudCreate, SaveButton, Toolbar } from 'react-admin';
import { SubmissionError } from 'redux-form';
const myValidate = (values) => {
const errors = {};
if (values.files < 1) errors.files = 'resources.Order.errors.files';
return errors;
};
// A custom action creator which modifies the values before calling the default crudCreate action creator
const saveWithNote = (values, basePath, redirectTo) =>
crudCreate('posts', { ...values, average_note: 10 }, basePath, redirectTo);
class SaveWithNoteButtonView extends Component {
handleClick = () => {
const { basePath, handleSubmit, redirect, saveWithNote } = this.props;
return handleSubmit(values => {
return new Promise((resolve, reject) => {
const errors = myValidate(values);
if (errors) {
reject(new SubmissionError(errors))
} else {
resolve( saveWithNote(values, basePath, redirect) );
};
}
});
});
};
render() {
const { handleSubmitWithRedirect, saveWithNote, ...props } = this.props;
return (
<SaveButton
handleSubmitWithRedirect={this.handleClick}
{...props}
/>
);
}
}
const SaveWithNoteButton = connect(
undefined,
{ saveWithNote }
)(SaveWithNoteButtonView);
Now in React-admin version 3 "Altering the Form Values before Submitting" Can't I do this anymore?
This code does not work:(
import React, { useCallback } from 'react';
import ProcessIcon from '#material-ui/icons/HeadsetMic';
import { useForm, useFormState } from 'react-final-form';
import { SaveButton } from 'react-admin';
const validateProcess = (values) => {
const errors = {};
if (values.files < 1) errors.files = ['resources.Order.errors.files'];
return errors
};
const SaveWithProcessButton = ({ handleSubmitWithRedirect, ...props }) => {
const form = useForm();
const formState = useFormState();
const handleClick = useCallback(() => {
return new Promise((resolve, reject) => {
const errors = validateProcess(formState.values);
if (errors) {
reject(errors)
} else {
form.change('status', "DRAFT");
resolve( handleSubmitWithRedirect('show') );
};
});
}, [form]);
return <SaveButton {...props} handleSubmitWithRedirect={handleClick} label="ra.action.process" icon={<ProcessIcon />} />;
};
export default SaveWithProcessButton;
I manage to do this. Try my code below, I hope it will works for also for you.
const handleClick = useCallback(() => {
if (!formState.valid) {
form.submit();
return;
} else { ... your code goes here }
}, [form]);

react native setInterval cannot read property apply

I am new in react native I am trying to render the count of unread notification for that I called my API in HOC it is working fine for initial few seconds but after that, I started to get the below error
func.apply is not a function
below is my code
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Modal, View } from "react-native";
import { themes } from "./constants";
import { AsyncStorage } from "react-native";
export default (OriginalComponent, animationType) =>
class extends Component {
static propTypes = {
handleFail: PropTypes.func,
theme: PropTypes.string,
visible: PropTypes.bool
};
state = {
modalVisible: true
};
static getDerivedStateFromProps({ visible }) {
if (typeof visible === "undefined") {
setInterval(
AsyncStorage.getItem("loginJWT").then(result => {
if (result !== null) {
result = JSON.parse(result);
fetch(serverUrl + "/api/getUnreadNotificationsCount", {
method: "GET",
headers: {
Authorization: "Bearer " + result.data.jwt
}
})
.then(e => e.json())
.then(function(response) {
if (response.status === "1") {
if (response.msg > 0) {
AsyncStorage.setItem(
"unreadNotification",
JSON.stringify(response.msg)
);
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}
})
.catch(error => {
alert(error);
// console.error(error, "ERRRRRORRR");
});
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}),
5000
);
return null;
}
return { modalVisible: visible };
}
handleOpenModal = () => {
this.setState({ modalVisible: true });
};
handleCloseModal = () => {
const { handleFail } = this.props;
this.setState({ modalVisible: false }, handleFail);
};
render() {
const { modalVisible } = this.state;
const { theme } = this.props;
return (
<View>
<Modal
animationType={animationType ? animationType : "fade"}
transparent={true}
visible={modalVisible}
onRequestClose={this.handleCloseModal}
>
<View style={themes[theme] ? themes[theme] : themes.transparent}>
<OriginalComponent
handleCloseModal={this.handleCloseModal}
{...this.props}
/>
</View>
</Modal>
</View>
);
}
};
I have not used getDerivedStateFromProps but, according to the docs, it is called on initial component mount and before each render update.
Thus your code is creating a new interval timer on each update without clearing any of the earlier timers, which could be causing a race condition of some sort.
You may want to consider using the simpler alternatives listed in the docs, or at a minimum, insure that you cancel an interval before creating a new one.