Mobx React Form - How to implement Custom onSubmit - forms

I am using mobx-react-from and i have a a problem to figure how i can use an action that i have in my store inside obSubmit hook ....
the mobx form is working ok .. i can see the inputs and the validation
and when i submit the form all i want is to use an action from store ...
my AutStore file :
import {observable,action} from 'mobx';
class AuthStore {
constructor(store) {
this.store = store
}
#action authLogin=(form)=>{
this.store.firebaseAuth.signInWithEmailAndPassword().then(()=>{
}).catch(()=>{
})
}
}
export default AuthStore
my AuthForm File :
import {observable, action} from 'mobx';
import MobxReactForm from 'mobx-react-form';
import {formFields,formValidation} from './formSettings';
const fields = [
formFields.email,
formFields.password
];
const hooks = {
onSuccess(form) {
// Here i want to use an action - authLogin from my AuthStore
console.log('Form Values!', form.values());
},
onError(form) {
console.log('All form errors', form.errors());
}
};
const AuthForm = new MobxReactForm({fields}, {plugins:formValidation,
hooks});
export default AuthForm
i would like to know how can i connect all together thanks !!!

I haven't used mobx-react-form before but have used mobx and react extensively. There's a couple ways to do this. The way I have done it is as follows, assuming Webpack & ES6 & React 14 here. Instantiate the store, and use a Provider around the component that hosts the form.
import { Provider } from 'mobx-react'
import React, { Component, PropTypes } from 'react'
import AuthStore from '{your auth store rel path}'
import FormComponent from '{your form component rel path}'
// instantiate store
const myAuthStore = new AuthStore()
// i don't think the constructor for AuthStore needs a store arg.
export default class SingleFormApplication extends Component {
render() {
return (
<Provider store={myAuthStore} >
<FormComponent />
</Provider>
)
}
}
Your FormComponent class will need to take advantage of both the observer and inject methods of the mobx-react package that will wrap it in a higher order component that both injects the store object as a prop and registers a listener on the store for changes that will rerender the component. I typically use the annotation syntax and it looks like this.
#inject('{name of provider store prop to inject}') #observer
export default class Example extends Component {
constructor(props) {
super(props)
this.store = this.props.store
}
}
Finally, with the store injected, you can now pass an action from the store into an AuthForm method, which I would advise you modify accordingly. Have the AuthForm file export a method that takes an onSuccess method as an arg and returns the mobx-react-form object. I would also modify your store action to simply take the email and password as an arg instead of the whole form. In the FormComponent try this:
import { formWithSuccessAction } from '{rel path to auth form}'
then in constructor after this.store = this.props.store assignment...
this.form = formWithSuccessAction(this.store.authLogin)
then in your render method of the FormComponent use the this.form class variable to render a form as you would in the mobx-react-form docs.
To be as clear as possible, the AuthForm.formWithSuccessAction method should look something like this:
const formWithSuccessAction = (storeSuccessAction) => {
const fields = [
formFields.email,
formFields.password
];
const hooks = {
onSuccess(form) {
// Here i want to use an action - authLogin from my AuthStore
console.log('Form Values!', form.values());
// get email and password in separate vars or not, up to you
storeSuccessAction(email, password)
},
onError(form) {
console.log('All form errors', form.errors());
}
};
const AuthForm = new MobxReactForm({fields}, {plugins:formValidation,
hooks});
return AuthForm
}
Hopefully this helps you on your way.

Related

Redux toolkit createSlice not working as expected (state is not modified thought action seem to be fired)

I am new to Redux toolkit. I have a working app in which would like to implement it in place of existing "regular" reducer.
import { createSlice, PayloadAction } from "#reduxjs/toolkit";
import { SelectedMinifig } from "types";
const initialState = {} as SelectedMinifig;
const selectedMinifigSlice = createSlice({
name: "selectedMinifigX",
initialState,
reducers: {
setSelectedMinifigX(state, action: PayloadAction<SelectedMinifig>) {
state = action.payload;
console.log("state and action payload from slice", state, action.payload);
},
},
});
export default selectedMinifigSlice.reducer;
export const { setSelectedMinifigX } = selectedMinifigSlice.actions;
Please note that in the code I use postfix "X" to differentiate new names from existing ones.
From the above slice, exports are consumed like this:
import selectedMinifigReducer from "reduxware/reducers/selectedMinifigSlice";
import { partsApi } from "../api/partsApi";
const rootReducer = combineReducers({
fetch: fetchReducer,
selected: selectedReducer,
teasers: teasersReducer,
selectedMinifigX: selectedMinifigReducer,
[partsApi.reducerPath]: partsApi.reducer,
});
Above I consume reducer, and with two files below I consume action (the latest file is my usual workaround not to useDispatch in components directly):
index.ts:
export { setSelectedMinifigX } from "reduxware/reducers/selectedMinifigSlice";
useDispatchAction.ts
import { useDispatch } from "react-redux";
import { bindActionCreators } from "redux";
import { actionCreators } from "reduxware";
const useDispatchAction = () => {
const dispatch = useDispatch();
return bindActionCreators(actionCreators, dispatch);
};
export default useDispatchAction;
The actions are fired like this (the new action is setSelectedMinifigX(selected), the old is setSelectedMinifig(selected), both with the same argument) :
onClick={e => {
e.stopPropagation();
setSelectedMinifig(selected);
setSelectedMinifigX(selected);
history(Paths.order);
}}
And in the moment of firing action, I really receive in console comment "state and action payload from slice " with expected content. That is why I claim action is actually fired.
The problem is that when I reach for state it is still empty object like initial state.
I have a component that is linked with state like below:
const mapStateToProps = (state: RootStateType) => ({
selectedMinifig: state.selected.selectedMinifig,
selectedMinifigX: state.selectedMinifigX,
});
and within this component, selectedMinifigX is an empty object. What is wrong here?
Hmm some of this looks a little foreign / extra to me.
Your reducer looks correct. Inside of your React component try to do something like this:
const SomeComponent = () => {
const dispatch = useDispatch()
const someHandlerFn = (e) => {
e.stopPropagation();
dispatch(setSelectedMinifigX(selected))
history(Paths.order);
}
return <button onClick={someHandlerFn}>Test Me</button>
}
I'm not sure if bindActionCreators is still valid redux. Was something I use to do before redux toolkit when using class based components. You should see your reducer fire inside of your reducer file.
The useDispatchAction.ts seems like extra stuff you don't need.

How to test the component refresh behavior with react-testing-library?

I'm writing a test for a component that takes a few props such as "isLoading", "clients" and "refreshClients". refreshClients is defined like this:
function refreshClients() {
setIsLoading(true)
getClients().then(response => {
setClients(response)
setIsLoading(false)
})
}
clients and isLoading are states from the parent component that are used as props of the child component. The client component also gets the refreshClients prop, which allows it to update its own props (isLoading and clients), through the function passed in by the parent component.
This is one of the use cases: after taking an action over a client, like deletion, the component will call refreshClients, which should take care of reloading the clients table displayed in the component. While the new listing is being loaded the table isn't displayed (isLoading is true). The component works well, however I'm unsure on how to properly test it using testing-library. I'm basically calling rerender in the tests but I feel there should be a way to replicate this behavior in the tests...
Is there a way to create states to pass as props to the tested component? Or is there another recommended approach to handle cases like this?
In case it makes it easier to visualize the idea, here is a complete simple example of how it would work:
import {createRoot} from 'react-dom'
import React, {useState} from 'react';
export function App() {
const [isLoading, setIsLoading] = useState(false);
const [clients, setClients] = useState(['david', 'sara']);
const refreshClients = () => {
setIsLoading(true)
setTimeout(() => {
setClients(['john', 'mary'])
setIsLoading(false)
}, 1000)
}
return <ClientsTable isLoading={isLoading} clients={clients} refreshClients={refreshClients} />
}
function ClientsTable({ isLoading, clients, refreshClients}) {
const deleteClient = () => {
console.log('TODO: delete client')
refreshClients()
}
return (
<div>
{isLoading && <p>Loading... please wait</p>}
{!isLoading && clients.map(client => (
<div>{client} <button onClick={deleteClient}>delete</button></div>
))}
</div>
);
}
createRoot(
document.getElementById('root')
).render(<App />)

sharing data between two tabs in ionic react

i have App.tsx which is app root and then two tabs tab1.tsx and tab2.tsx.
The tab1.tsx has a variable declared as
const [coins, setCoins] = useState(0)
and tab2.tsx also i have declared the same
const [coins, setCoins] = useState(0)
but this is just a workaround as of now. how do i have it declared it at just one place be able to share it between the two tabs. i don't think passing it between the tabs as parameter is a good solution.
in ionic angular i used a service to delcare this kind of variables and easily referred then through out the application. what is the solution for ionic-react?
Use useContext React Hook.
https://reactjs.org/docs/hooks-reference.html#usecontext
1- Create a new file in the project, such as my-context.tsx
import React, { createContext, useState } from "react";
//create the context
export const MyContext = createContext<any>(undefined);
export const MyProvider: React.FC = ({ children }) => {
const [coins, setCoins] = useState(0)
let state = {
coins,
setCoins
};
//wrap the application in the provider
return <MyContext.Provider value={state}>{children}</MyContext.Provider>;
};
export default MyContext;
2- Wrap App.tsx inside <AuthProvider> </AuthProvider> like this:
const App: React.FC = () => {
return (
<IonApp>
<AuthProvider>
<IonReactRouter>
...
</IonReactRouter>
</AuthProvider>
</IonApp>
);
};
export default App;
3- Every time you want to use this state in other components/pages, import the context and use it like this:
const {
coins,
setCoins
} = React.useContext(MyContext);
In this way, you share states and set states through out the app.

Is it OK to put propTypes and defaultProps as static props inside React class?

This is the way I've been doing it for quite some time now:
export default class AttachmentCreator extends Component {
render() {
return <div>
<RaisedButton primary label="Add Attachment" />
</div>
}
}
AttachmentCreator.propTypes = {
id: PropTypes.string,
};
But I've seen people doing it this way:
export default class AttachmentCreator extends Component {
static propTypes = {
id: PropTypes.string,
};
render() {
return <div>
<RaisedButton primary label="Add Attachment" />
</div>
}
}
And in fact I've seen people setting initial state outside the constructor as well. Is this good practice? It's been bugging me, but I remember a discussion somewhere where someone said that setting default props as a static is not a good idea - I just don't remember why.
In fact, it's exactly the same in terms of performance. React.JS is a relatively new technology, so it's not clear yet what are considered good practices or don't. If you want to trust someone, check this AirBNB's styleguide:
https://github.com/airbnb/javascript/tree/master/react#ordering
import React, { PropTypes } from 'react';
const propTypes = {
id: PropTypes.number.isRequired,
url: PropTypes.string.isRequired,
text: PropTypes.string,
};
const defaultProps = {
text: 'Hello World',
};
class Link extends React.Component {
static methodsAreOk() {
return true;
}
render() {
return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>
}
}
Link.propTypes = propTypes;
Link.defaultProps = defaultProps;
export default Link;
They are declaring a const with the propTypes object literals, keep the class pretty clean and assign them later to the static properties. I personally like this approach :)
Oh yes, it's totaly legit with Babel (or other transpilers)
class DataLoader extends React.Component {
static propTypes = {
onFinishedLoading: PropTypes.func
}
static defaultProps = {
onFinishedLoading: () => {}
}
}
...as it gets transpiled to this anyway.
class DataLoader extends React.Component {}
DataLoader.propTypes = {
onFinishedLoading: PropTypes.func
};
DataLoader.defaultProps = {
onFinishedLoading: () => {}
};
Static fields get transpiled as properties on the class object under the hood.
Look here at Babel REPL.
Moreover, assigning state or other class fields directly in the class body gets transpiled into the constructor.
non-function properties are not currently supported for es2015 classes. its a proposal for es2016. the second method is considerably more convenient, but a plugin would be required to support the syntax (theres a very common babel plugin for it).
On the other end, a hand full of open source projects are beginning to treat proptypes like TypeScript interfaces, or ActionConstants and actually create separate folders/files that house various component prop types and are then imported into the component.
So in summary, both syntaxes are ok to use. but if you want to only use strictly ES2015, the latter syntax is not yet supported in the specification.
If the component is state-less, meaning it does not change it's own state at all, you should declare it as a stateless component (export default function MyComponent(props)) and declare the propTypes outside.
Whether it's good practice to put initial state declaration in constructor is up to you. In our project we declare initial state in componentWillMount() just because we do not like the super(props) boilerplate you have to use with the constructor.

Create an instance of a React class from a string

I have a string which contains a name of the Class (this is coming from a json file). This string tells my Template Class which layout / template to use for the data (also in json). The issue is my layout is not displaying.
Home.jsx:
//a template or layout.
var Home = React.createClass({
render () {
return (
<div>Home layout</div>
)
}
});
Template.jsx:
var Template = React.createClass({
render: function() {
var Tag = this.props.template; //this is the name of the class eg. 'Home'
return (
<Tag />
);
}
});
I don't get any errors but I also don't see the layout / Home Class. I've checked the props.template and this logs the correct info. Also, I can see the home element in the DOM. However it looks like this:
<div id='template-holder>
<home></home>
</div>
If I change following line to:
var Tag = Home;
//this works but it's not dynamic!
Any ideas, how I can fix this? I'm sure it's either simple fix or I'm doing something stupid. Help would be appreciated. Apologies if this has already been asked (I couldn't find it).
Thanks,
Ewan
This will not work:
var Home = React.createClass({ ... });
var Component = "Home";
React.render(<Component />, ...);
However, this will:
var Home = React.createClass({ ... });
var Component = Home;
React.render(<Component />, ...);
So you simply need to find a way to map between the string "Home" and the component class Home. A simple object will work as a basic registry, and you can build from there if you need more features.
var components = {
"Home": Home,
"Other": OtherComponent
};
var Component = components[this.props.template];
No need to manually map your classes to a dictionary, or "registry", as in Michelle's answer. A wildcard import statement is already a dictionary!
import * as widgets from 'widgets';
const Type = widgets[this.props.template];
...
<Type />
You can make it work with multiple modules by merging all the dictionaries into one:
import * as widgets from 'widgets';
import * as widgets2 from 'widgets2';
const registry = Object.assign({}, widgets, widgets2);
const widget = registry[this.props.template];
I would totally do this to get dynamic dispatch of react components. In fact I think I am in a bunch of projects.
I had the same problem, and found out the solution by myself. I don't know if is the "best pratice" but it works and I'm using it currently in my solution.
You can simply make use of the "evil" eval function to dynamically create an instance of a react component. Something like:
function createComponent(componentName, props, children){
var component = React.createElement(eval(componentName), props, children);
return component;
}
Then, just call it where you want:
var homeComponent = createComponent('Home', [props], [...children]);
If it fits your needs, maybe you can consider something like this.
Hope it helps.
I wanted to know how to create React classes dynamically from a JSON spec loaded from a database and so I did some experimenting and figured it out. My basic idea was that I wanted to define a React app through a GUI instead of typing in code in a text editor.
This is compatible with React 16.3.2. Note React.createClass has been moved into its own module.
Here's condensed version of the essential parts:
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import createReactClass from 'create-react-class'
const spec = {
// getDefaultProps
// getInitialState
// propTypes: { ... }
render () {
return React.createElement('div', null, 'Some text to render')
}
}
const component = createReactClass(spec)
const factory = React.createFactory(component)
const instance = factory({ /* props */ })
const str = ReactDOMServer.renderToStaticMarkup(instance)
console.log(str)
You can see a more complete example here:
https://github.com/brennancheung/02-dynamic-react/blob/master/src/commands/tests/createClass.test.js
Here is the way it will work from a string content without embedding your components as statically linked code into your package, as others have suggested.
import React from 'react';
import { Button } from 'semantic-ui-react';
import createReactClass from 'create-react-class';
export default class Demo extends React.Component {
render() {
const s = "return { render() { return rce('div', null, rce(components['Button'], {content: this.props.propA}), rce(components['Button'], {content: 'hardcoded content'})); } }"
const createComponentSpec = new Function("rce", "components", s);
const componentSpec = createComponentSpec(React.createElement, { "Button": Button });
const component = React.createElement(createReactClass(componentSpec), { propA: "content from property" }, null);
return (
<div>
{component}
</div>
)
}
}
The React class specification is in string s. Note the following:
rce stands for React.createElement and given as a first param when callingcreateComponentSpec.
components is a dictionary of extra component types and given as a second param when callingcreateComponentSpec. This is done so that you can provide components with clashing names.
For example string Button can be resolved to standard HTML button, or button from Semantic UI.
You can easily generate content for s by using https://babeljs.io as described in https://reactjs.org/docs/react-without-jsx.html. Essentially, the string can't contain JSX stuff, and has to be plain JavaScript. That's what BabelJS is doing by translating JSX into JavaScript.
All you need to do is replace React.createElement with rce, and resolve external components via components dictionary (if you don't use external components, that you can skip the dictionary stuff).
Here is equivalent what in the code above. The same <div> with two Semantic UI Buttons in it.
JSX render() code:
function render() {
return (
<div>
<Button content={this.props.propA}/>
<Button content='hardcoded content'/>
</div>
);
}
BabelJS translates it into:
function render() {
return React.createElement("div", null, React.createElement(Button, {
content: this.props.propA
}), React.createElement(Button, {
content: "hardcoded content"
}));
}
And you do replacement as outlined above:
render() { return rce('div', null, rce(components['Button'], {content: this.props.propA}), rce(components['Button'], {content: 'hardcoded content'})); }
Calling createComponentSpec function will create a spec for React class.
Which then converted into actual React class with createReactClass.
And then brought to life with React.createElement.
All you need to do is return it from main component render func.
When you use JSX you can either render HTML tags (strings) or React components (classes).
When you do var Tag = Home, it works because the JSX compiler transforms it to:
var Template = React.createElement(Tag, {});
with the variable Tag in the same scope and being a React class.
var Tag = Home = React.createClass({
render () {
return (
<div>Home layout</div>
)
}
});
When you do
var Tag = this.props.template; // example: Tag = "aClassName"
you are doing
var Template = React.createElement("aClassName", null);
But "aClassName" is not a valid HTML tag.
Look here