react-admin edit component does not call update function - javascript

I'm working on a react-admin application and having an issue where the update function in my dataProvider is not called when the 'Save' button is clicked under the edit view.
My App.js looks like this:
import React, { useEffect } from 'react';
import { Switch, Route, Redirect, useLocation } from 'react-router-dom';
import { setGlobal, getGlobal } from 'reactn';
import ReactGA from 'react-ga';
import PrivateRoute from './Auth/PrivateRoute';
import UserLayout from './components/user/layout/Layout';
import AuthLayout from './components/auth/Layout';
import defaultGlobalState from './defaultGlobalState';
import portalApiDataProvider from "./providers/portalApiDataProvider";
import {Admin, ListGuesser, EditGuesser, ShowGuesser, Resource, Layout} from "react-admin";
import { UserList, UserEdit, UserShow } from '../src/components/admin/users';
import { AccountList } from "./components/admin/serviceAccounts";
import authProvider from "./providers/authProvider";
ReactGA.initialize(process.env.REACT_APP_GA_KEY);
const jwt = JSON.parse(localStorage.getItem('jwt'));
setGlobal({ ...defaultGlobalState, jwt });
const App = () => {
const { userId } = getGlobal();
let location = useLocation();
useEffect(() => {
ReactGA.pageview(location.pathname + location.search);
}, [location]);
return (
<Admin dataProvider={portalApiDataProvider} authProvider={authProvider}>
<Resource name="users" options={{label: 'Customer Profiles'}} list={UserList} show={UserShow} edit={UserEdit}/>
<Resource name="serviceAccounts" list={AccountList}/>
</Admin>
);
};
export default App;
My UserEdit is as follows:
<Edit title={<UserTitle/>} {...props}>
<SimpleForm>
<TextInput source="first_name" />
<TextInput source="last_name" />
<TextInput source="email" />
<TextInput source="phone" />
<TextInput source="default_account_number" />
<BooleanInput source="validated" format={v => v === 1 ? true : false}/>
</SimpleForm>
</Edit>
I've seen this issue brought up a couple of times, but without any details on resolution. This link mentions using a Notification component in the Layout (I'm pretty sure I'm using the default layout), or disabling undo (which does work but is not ideal): https://github.com/marmelab/react-admin/issues/3049
This link also discusses a similar issue with reference to a demo project, but I'm not able to find the relevant code: https://github.com/marmelab/react-admin/issues/5082
What am I missing here?

For some reason, the undoable prop on Edit is causing problem. Set it to false and it will call the update properly. I don't know why, I am using this under a special endpoint as a "sub-app", so might be due to conflicting routers, too tired to find out.

You should set the mutationMode of Edit into "pessimistic"
So your will be
<Edit
{...props}
mutationMode="pessimistic"
>
Document here: https://marmelab.com/react-admin/CreateEdit.html#mutationmode

Related

Finding the buttons on the screen that have no text for the test

I am trying to write the tests for the NavBar component (using react-native-testing-library) that has several buttons that are basically just icons (using ui-kitten for react native). So I can't get these buttons by text (as there is none) but other methods didn't work for me either (like adding accesibilityLabel or testID and then getting by the label text / getting by test ID). Any ideas what I am doing wrong?
// NavBar.tsx
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {HomeBtn, SaveBtn} from '../components/buttons';
import UserSignOut from './UserSignOut';
const NavBar = ({
navigation,
pressHandlers,
}) => {
return (
<View style={styles.navBar}>
<View>
<HomeBtn navigation={navigation} />
<SaveBtn pressHandler={pressHandlers?.saveBtn ?? undefined} />
</View>
<UserSignOut />
</View>
);
};
export default NavBar;
// HomeBtn.tsx
import React from 'react';
import {Button} from '#ui-kitten/components';
import {HomeIcon} from '../shared/icons';
import styles from './Btn.style';
export const HomeBtn = ({navigation}: any) => {
return (
<Button
accesibilityLabel="home button"
style={styles.button}
accessoryLeft={props => HomeIcon(props, styles.icon)}
onPress={() => navigation.navigate('Home')}
/>
);
};
// NavBar.test.tsx
import React from 'react';
import {render, screen} from '#testing-library/react-native';
import * as eva from '#eva-design/eva';
import {RootSiblingParent} from 'react-native-root-siblings';
import {EvaIconsPack} from '#ui-kitten/eva-icons';
import {ApplicationProvider, IconRegistry} from '#ui-kitten/components';
import NavBar from '../../containers/NavBar';
describe('NavBar', () => {
const navBarContainer = (
<RootSiblingParent>
<IconRegistry icons={EvaIconsPack} />
<ApplicationProvider {...eva} theme={eva.light}>
<NavBar />
</ApplicationProvider>
</RootSiblingParent>
);
it('should render the buttons', async () => {
render(navBarContainer);
// this test fails (nothing is found with this accesibility label)
await screen.findByLabelText('home button');
});
});
Query predicate
The recommended solution would be to use:
getByRole('button', { name: "home button" })
As it will require both the button role, as well as check accessibilityLabel with name option.
Alternative, but slightly less expressive way would be to use:
getByLabelText('home button')
This query will only check accessibilityLabel prop, which also should work fine.
Why is query not matching
Since you're asking why the query is not working, that depends on your test setup. It seems that you should be able to use sync getBy* query and do not need to await findBy* query, as the HomeBtn should be rendered without waiting for any async action.
What might prevent that test from working could be incorrect mocking of any of the wrapping components: RootSiblingParent, ApplicationProvider, they might be "consuming" children prop without rendering it. In order to diagnose the issue you can use debug() function from RNTL to inspect the current state of rendered components. You can also run your tests on render(<NavBar />) to verify that.
Does await screen.findByA11yLabel('home button') work? It should match the accessibilityLabel prop.

Push to new route without any further actions on the component

We use an external componet which we don't control that takes in children which can be other components or
used for routing to another page. That component is called Modulation.
This is how we are currently calling that external Modulation component within our MyComponent.
import React, {Fragment} from 'react';
import { withRouter } from "react-router";
import { Modulation, Type } from "external-package";
const MyComponent = ({
router,
Modulation,
Type,
}) => {
// Need to call it this way, it's how we do modulation logics.
// So if there is match on typeA, nothing is done here.
// if there is match on typeB perform the re routing via router push
// match happens externally when we use this Modulation component.
const getModulation = () => {
return (
<Modulation>
<Type type="typeA"/> {/* do nothing */}
<Type type="typeB"> {/* redirect */}
{router.push('some.url.com')}
</Type>
</Modulation>
);
}
React.useEffect(() => {
getModulation();
}, [])
return <Fragment />;
};
export default withRouter(MyComponent);
This MyComponent is then called within MainComponent.
import React, { Fragment } from 'react';
import MyComponent from '../MyComponent';
import OtherComponent1 from '../OtherComponent1';
import OtherComponent2 from '../OtherComponent2';
const MainComponent = ({
// some props
}) => {
return (
<div>
<MyComponent /> {/* this is the above component */}
{/* We should only show/reach these components if router.push() didn't happen above */}
<OtherComponent1 />
<OtherComponent2 />
</div>
);
};
export default MainComponent;
So when we match typeB, we do perform the rerouting correctly.
But is not clean. OtherComponent1 and OtherComponent2 temporarily shows up (about 2 seconds) before it reroutes to new page.
Why? Is there a way to block it, ensure that if we are performing router.push('') we do not show these other components
and just redirect cleanly?
P.S: react-router version is 3.0.0

react/no-multi-comp is showing up as warnings

I am using hook router 1.2.5 and I have a very simple home page as below:
import { useRoutes } from "hookrouter";
import React from "react";
import Nav from "./pages/Nav";
import AboutPage from "./pages/About";
const HomePage = () => {
const routeResult = useRoutes({
"/about": () => <AboutPage />
});
return (
<div fluid>
<div xs={3} md={1} lg={1} className="nav-container">
<Nav />
</div>
<div xs={9} md={11} lg={11}>
{routeResult || <AboutPage />}
</div>
</div>
);
};
export default HomePage;
But when I run lint, I see below warnings show up.
8:10 warning Component definition is missing display name react/display-name
8:10 warning Declare only one React component per file react/no-multi-comp
I know I can disable these eslint warnings. But I would like to know how to fix them. For example, I don't have another component in my file. So why would it show react/no-multi-comp warning, or did I miss something? Any helps are appreciated.
UPDATE
I was able to fix react/display-name by replacing the arrow function as below:
const routeResult = useRoutes({
"/about"() {
return <AboutPage />;
}
});

Tests React component with jest and enzyme

i have components presented below. I am totally new in unit testing. Can anyone give any one give me advice how and what should I test in this component? I was trying to shallow render it, to check is text in h2 is present but i still getting errors.
import React, { useEffect } from 'react';
import { Form, Field } from 'react-final-form';
import { useHistory, Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { loginUser, clearErrorMessage } from '../../redux/auth/authActions';
import Input from '../Input/Input';
import ROUTES from '../../routes/routes';
import './LoginForm.scss';
const LoginForm = () => {
const dispatch = useDispatch();
const history = useHistory();
const { loading, isLogged, errorMessage } = useSelector(state => state.auth);
useEffect(() => {
if (isLogged) {
history.push('/');
}
return () => {
dispatch(clearErrorMessage());
};
}, [dispatch, history, isLogged]);
const handleSubmitLoginForm = values => {
if (!loading) {
dispatch(loginUser(values));
}
};
const validate = ({ password }) => {
const errors = {};
if (!password) {
errors.password = 'Enter password!';
}
return errors;
};
return (
<article className="login-form-wrapper">
<h2>SIGN IN</h2>
<Form onSubmit={handleSubmitLoginForm} validate={validate}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit} autoComplete="off" className="login-form">
<div className="login-form__field">
<Field name="email" component={Input} type="email" label="E-mail" />
</div>
<div className="login-form__buttons">
<button type="submit" className={loading ? 'button-disabled' : ''}>
Sign in
</button>
</div>
</form>
)}
</Form>
</article>
);
};
export default LoginForm;
I am open for any advices :)
First of all, I am not recommending using shallow in your tests and here is a great article why.
I also recommend to check out react-testing-library instead of Enzyme as it is much nicer to use.
Now, to answer your question. You are using here hooks for redux and react-router, so you need to provide the necessary data to your componenent in test so that it can use those hooks. Let me show you an example test (that checks text in h2 element):
import React from 'react';
import { mount } from 'enzyme';
import {Provider} from 'react-redux';
import {MemoryRouter, Route} from 'react-router';
import LoginForm from './LoginForm';
describe('Login Form', () => {
it('should have SIGN IN header', () => {
const store = createStore();
const component = mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/login']}>
<Route path="/:botId" component={LoginForm} />
</MemoryRouter>
</Provider>
)
expect(component.find('h2').text()).toEqual('SIGN IN');
});
});
Some explanation to this example.
I am using mount instead of shallow as I prefer to render as much as possible in my test, so that I can check if everything works together as it should.
You can see that I am not rendering my component directly, but rather it is wrapped with other components (Provider from react-redux and MemoryRouter from react-router). Why? Because I need to provide context to my Component. In this case it's redux and router context so that the data used inside exists and can be found (for example useSelector(state => state.auth) must have some state provided so that it can access auth property). If you remove any of them you would get some error saying that this context is missing - go ahead and check for yourself :).
See here for some details around testing with router context
As for testing with redux in my example there is a createStore function that I didn't define as there are a few approaches to this. One involves creating a real store that you use in your production application. This is the one that I prefer and colleague of mine wrote great article around this topic here. Other is to create some kind of mock store, like in this article. Again, I prefer the first approach, but whichever is better for you.
Answering your other question on what should you test in this example. There are multiple possibilities. It all depends mostly on you business case, but examples that I would test here includes:
Typing something into an input, clicking a button and observing that login is successful (by redirecting to new path - / in this case)
not typing a password and clicking a button - error should be shown
Checking if button class changes when it's loading
Do not dispatch login action twice, when already loading
And so on...
That's really just a tip of an iceberg on what could be written around testing, but I hope it helps and gives you a nice start to dig deeper into the topic.

react native - On button press load the whole screen with parent component

I am trying to load my parent component from child component on button press. But it's not rendering the parent components from btnPress method. I am not getting any error.
onButtonPress
<Button onPress={() => btnPress(parent_id, id)}>
<Icon name="arrow-forward" />
</Button>
btnPress Function
function btnPress(parent_id, id) {
const App = () => (
//I have tried this way but this didn't work. No any error, i can see log on console
<Container>
<Headerc headerText={'Fitness sdaf'} />
<ExerciseList pId={parent_id} mId={id} />
</Container>
);
console.log(id);
AppRegistry.registerComponent('weightTraining', () => App);
}
full code(child component)
import React from 'react';
import { Right, Body, Thumbnail, Container, ListItem, Text, Icon } from 'native-base';
import { AppRegistry
} from 'react-native';
import Headerc from './headerc';
import ExerciseList from './exerciseList';
import Button from './Button';
const ExerciseDetail = ({ exercise }) => {
const { menu_name, menu_icon, parent_id, id } = exercise;
function NumberDescriber() {
let description;
if (menu_icon === 'noimg.jpg') {
description = `http://www.xxxxxx.com/uploads/icons/${menu_icon}`;
} else if (menu_icon === 'noimg.jpg') {
description = menu_icon;
} else {
description = `http://www.xxxxx.com/uploads/icons/${menu_icon}`;
}
return description;
}
function btnPress(parent_id, id) {
const App = () => (
<Container>
<Headerc headerText={'Fitness sdaf'} />
<ExerciseList pId={parent_id} mId={id} />
</Container>
);
console.log('-------------------------------');
console.log(id);
console.log('+++++++++++++++++++++++++++');
AppRegistry.registerComponent('weightTraining', () => App);
}
return (
<ListItem>
<Thumbnail square size={80} source={{ uri: NumberDescriber() }} />
<Body>
<Text>{menu_name}</Text>
<Text note> {menu_name} exercise lists</Text>
</Body>
<Right>
<Button onPress={() => btnPress(parent_id, id)}>
<Icon name="arrow-forward" />
</Button>
</Right>
</ListItem>
);
};
export default ExerciseDetail;
Please do let me know, if you need more information.
I would not suggest doing that way, it look totally anti-pattern and not.
better try with navigation or create a pattern like this
inside your index.js or index.android.js or index.ios.js
import App from './App' //your app level file
AppRegistry.registerComponent('weightTraining', () => App);
now in your app js file
export default App class extends React.Component{
constructor(props){
super(props);
this.state ={
component1:false,
component2:true,
}
}
btnPressed =()=>{
//handle state update logic here
}
render(){
if(this.state.component1) return <Component1/>
return <Component2/>
}
}
**** not the best solution available, play around and you will get best
To navigate from this component to your parent component unless you want to implement your own navigation which isn't recommended, you should look into one that's already built and adopted by many in the react-native ecosystem.
Some of the biggest ones:
React Native Navigation
React Navigation
React Native Router
I personally highly recommend option number 1, since it seems to be the most production tested and production ready implementation out there

Categories

Resources