How to pass more than 1 ref to a child component in SolidJS? - solid-js

Parent Component:
function ParentComponent() {
return (
<section>
<ChildComponent ref={sectionRef} ref1={headerRef} />
</section>
);
}
Child Component:
function ChildComponent(props) {
return (
<section ref={props.ref}>
<article>
<h2 ref={props.ref1}>Lorem Ipsum</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime mollitia,
molestiae quas vel sint commodi repudiandae consequuntur voluptatum laborum
numquam blanditiis harum quisquam eius sed odit fugiat iusto fuga praesentium optio, eaque rerum!</p>
</article>
</section>
);
}
My goal is to be able to target different DOM elements in the child component from the parent component so that I can animate them based on a scroll event from the parent.
I have tried to pass the refs as different data structures:
<ChildComponent ref={{sectionRef, headerRef}} />
and:
<ChildComponent ref={[sectionRef, headerRef]} />
and:
<ChildComponent section={sectionRef} header={headerRef} />
But continuously get errors that the 2nd ref is undefined. I can only get it to work if I pass in a single ref per child component. Any ideas?
Links to reference material I looked at:
https://www.solidjs.com/tutorial/bindings_forward_refs
https://www.solidjs.com/docs/latest/api#ref

When Refs set in Components
There's 3 ways to reference a DOM element inside a component.
Pass regular variable
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl) // logs button element
})
return (
<button ref={buttonEl}>Click</button>
);
}
Pass signal setter
function Component() {
const [buttonEl, setButtonEl] = createSignal(null);
onMount(() => {
console.log(buttonEl()) // logs button element
})
return <button ref={setButtonEl}>Click</button>;
}
Pass callback
function Component() {
let buttonEl;
const refCallback = (el) => {
buttonEl = el;
};
onMount(() => {
console.log(buttonEl); // logs button element
});
return <button ref={refCallback}>Click</button>;
}
When Refs set in Child Components
However when referencing a DOM element that is set in child components, the situation is different. For demonstration we won't use ref prop for Child component but instead PASSREF prop.
Pass regular variable to PASSREF. The variable doesn't update and is undefined.
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl); // logs `undefined`
});
return <Child PASSREF={buttonEl} />;
}
function Child(props) {
return <button ref={props.PASSREF}>Click</button>;
}
Pass signal setter to PASSREF, works.
function Component() {
const [buttonEl, setButtonEl] = createSignal(null)
onMount(() => {
console.log(buttonEl()); // logs button element
});
return <Child PASSREF={setButtonEl} />;
}
function Child(props) {
return <button ref={props.PASSREF}>Click</button>;
}
Pass callback, that is declared in same scope as buttonEl, to PASSREF, works.
function Component() {
let buttonEl;
const refCallback = (el) => {
buttonEl = el;
};
onMount(() => {
console.log(buttonEl); // logs button element
});
return <Child PASSREF={refCallback} />;
}
function Child(props) {
return <button ref={props.PASSREF}>Click</button>;
}
To fix #1 solution where you use the regular variable let buttonEl;, you use the correct component prop ref in order to set the element to the variable.
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl); // logs button element
});
return <Child ref={buttonEl} />;
}
function Child(props) {
return <button ref={props.ref}>Click</button>;
}
So why does this work? Well because in the compiled output the Child prop argument where ref is used is actually replaced by an inline callback, that way it lives in the same scope where buttonEl is declared and can be updated.
// This is NOT how the Compiled Output actually looks,
// but ref argument is replaced by an inline callback
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl); // logs button element
});
return <Child ref={(el) => buttonEl = el} />;
}
function Child(props) {
return <button ref={props.ref}>Click</button>;
}
Doesn't that look familiar? This structured almost exactly to the #3 solutions, where you pass a callback function to update the buttonEl.
Solution
Honestly it depends on your use case, either use signals setters, from createSignal, to pass refs, or use callback functions declared in the parent to set your plain variables.
In this solution example, both sectionRef and headerRef are unassigned variables. sectionRef is passed to ref prop, where behind the scenes, it's wrapped in a callback. A callback function refCallback is passed to ref1 prop where it sets headerRef to passed element value.
function ParentComponent() {
let sectionRef;
let headerRef;
const refCallback = (el) => {
headerRef = el
}
onMount(() => {
console.log(sectionRef); // logs section el
console.log(headerRef); // logs header el
});
return (
<section>
<Overview ref={sectionRef} ref1={refCallback} />
</section>
);
}
function Overview(props) {
return (
<section ref={props.ref}>
<article>
<h2 ref={props.ref1}>Lorem Ipsum</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
mollitia, molestiae quas vel sint commodi repudiandae consequuntur
voluptatum laborum numquam blanditiis harum quisquam eius sed odit
fugiat iusto fuga praesentium optio, eaque rerum!
</p>
</article>
</section>
);
}
How
To reiterate again. The way that Solid makes it work is that in the compiled output, if a component property is named ref, it is replaced by an object method (in this context has the same strategy like the callback function) that is located in the same place where "ref" variable (such as sectionRef) is created, that way the "ref" variable can be assigned to it.
If you're curious, here's the actual compiled output of solution, where you can see how ref actually looks like.
// Compiled Output
function ParentComponent() {
let sectionRef;
let headerRef;
const refCallback = el => {
headerRef = el;
};
// ...
return (() => {
const _el$ = _tmpl$.cloneNode(true);
insert(_el$, createComponent(Overview, {
ref(r$) {
const _ref$ = sectionRef;
typeof _ref$ === "function" ? _ref$(r$) : sectionRef = r$;
},
ref1: refCallback
}));
// ...
})();
}

Since you want to interact with children on parent's scroll, which has nothing to do with the state, you can skip all the ceremony and access children directly by taking advantage of solid being compiled to native JavaScript.
import { render } from 'solid-js/web';
const App = () => {
const handleScroll = (event: any) => {
const header = event.currentTarget.querySelector('header');
const section = event.currentTarget.querySelector('section');
console.log(header, section);
};
return (
<div
style={`position: fixed; overflow: scroll; width: 100vw; height: 100vh`}
onScroll={handleScroll}
>
<header style={`height: 500px`}>Header</header>
<section style={`height: 500px`}>Section</section>
</div>
)
};
render(App, document.body);
Live Demo: https://playground.solidjs.com/anonymous/19a789fe-d710-40ba-8b3e-a256ab23c1e2
See, you don't need all that React's twisted mindset.
If your child component somehow depends on parent's state, i.e its y position, it is best to pass the parent's state as prop and add event inside the child's body:
import { render } from 'solid-js/web';
import { Accessor, Component, createEffect, createSignal } from 'solid-js';
const Child: Component<{ y: Accessor<number> }> = (props) => {
createEffect(() => {
console.log(props.y());
});
return <div>Child Component</div>
}
const App = () => {
const [y, setY] = createSignal<number>(0);
setInterval(() => {
setY(v => v + 1);
}, 1000);
return (
<div>
<Child y={y} />
</div>
)
};
render(App, document.body);
https://playground.solidjs.com/anonymous/55405c86-b811-4ef7-8cfc-788f74ea0681
If state is object but you need only one property, you can derive the state:
// interface State { x: number, y: number }
const y = () => state().y;
// Then pass it as a prop
<Child y={y} />
Refs are for accessing element inside the component's body for things like adding and removing event listeners. Don't pass them around if you can help it and certainly don't overuse them. Because state+effects brings predictability, refs bring chaos.

Related

On refresh react application I need to get from componentWillReceiveProps values for input text

I have the following problem and I really need help on that.
export class DeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
let data = this.props.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
componentWillMount() {
let data = this.props.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
componentWillReceiveProps(newProps) {
let data = newProps.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { error } = this.props;
return (
<FormLabel>Internal ID</FormLabel>
<input type="text" defaultValue={this.state.value} onChange= .
{this.handleChange} />
</Form.Label>)
}
}
So what I want is that when I refresh the page, I want to get the the this.state.value on my input.. which in this case I am not able to do that. So I would like to know what I am doing wrong here. If I set it on value on the input I did get what I want, but then I have an warning like that:
A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
What can I do?
Actually, you should handle changes there and you can use just value on the input field instead of defaultValue.
For example:
export class AdminDeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: '',
// if it comes from props by default
// you can use, if not just leave as it is
value: props.value
};
}
handleChange = e => {
this.setState({value: e.target.value});
}
render() {
const { error } = this.props;
return (
<form>
<FormLabel>Internal ID</FormLabel>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</form>
)
}
}
Hope it will helps.
So from what I understand you want to make controlled input but use props.value as a default value. What if you do:
export class AdminDeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: props.value,
};
this.handleChange = this.handleChange.bind(this);
}
componentWillReceiveProps(newProps) {
if(this.props.value !== newProps.value) {
this.setState({ value: newProps.value }) // reset input value
}
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { error } = this.props;
return (
<FormLabel>Internal ID</FormLabel>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</Form.Label>)
}
}
Certainly get rid of componentWillMount and componentDidMount. You don't need them here.

react-google-maps multiple markers breaks on open

My app loads all my markers just fine, but crashes when I try and click a marker. I get the following error:
"React.Children.only expected to receive a single React element child."
In the main map component I build the map and bring in all the markers from Firebase. I wanted to componentize the markers and build them in a separate file just to keep things a bit straighter in my head.
Here is my Map component
const MyMapComponent = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=xxxxx&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `500px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap
)((props) =>
<GoogleMap
defaultZoom={15}
defaultCenter={{ lat: 34.6781445, lng: -82.8455519 }}
>
{props.markers.map(marker => (
<LotMarker key={marker.key} index={marker.key} lat={marker.latitude} lng={marker.longitude} />
))}
</GoogleMap>
);
class TheMap extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
test:'test',
lots: [],
isOpen: false
};
}
componentWillMount() {
base.bindToState('lots', {
context: this,
state: 'lots',
asArray: true,
});
}
handleMarkerClick = (key) => {
console.log(key);
}
render() {
return (
<MyMapComponent
isMarkerShown={this.state.isMarkerShown}
onMarkerClick={this.handleMarkerClick}
markers={this.state.lots}
/>
)
}
}
export default TheMap;
Here I put the marker together. I've housed the InfoWindow component inside the marker and then ask it to show when the marker is clicked. However, when I do click a marker it just crashes the whole app and white screens.
This is my LotMarker component:
class LotMarker extends React.Component {
// Constructor.
constructor(props) {
// Super Props.
super(props);
// State.
this.state = {
isOpen: false
}
}
onToggleOpen = () => {
console.log(this);
this.setState({isOpen: !this.state.isOpen});
}
// Render.
render() {
// console.log(this);
// Extract Props.
const {
props: {
index,
lat,
lng,
open
}
} = this
return (
<Marker key={index} position={{ lat: lat, lng: lng }} onClick={this.onToggleOpen}>
{this.state.isOpen && <InfoWindow onCloseClick={this.onToggleOpen}>{index}</InfoWindow>}
</Marker>
)
}
}
export default LotMarker;
Alright, it turns out there was a really simple fix here. The content inside the InfoWindow has to be wrapped in a JSX compliant element.
Rather than this:
<InfoWindow onCloseClick={this.onToggleOpen}>{index}</InfoWindow>
I had to have this:
<InfoWindow onCloseClick={this.onToggleOpen}><span>{index}</span></InfoWindow>

Setting state in react. Is there a better way to write this without warning errors?

I am working on a registration form on react. I am a bit stuck with the validation part of it.
As of now I am getting the following warnings four times on the console: "warning Do not mutate state directly. Use setState() react/no-direct-mutation-state."
I am guessing the reason I am getting these errors is because of statements like these "this.state.errors.firstName = "First name must be at least 2 characters.";" and like this"this.state.errors = {};" in my code.
However, I do not know how to make this better and eliminate the warnings. If you can provide a better way for me to do this that would be awesome. Any help will be highly appreciated. Thanks so much in advance!
import React, { Component } from 'react';
import {withRouter} from "react-router-dom";
import HeaderPage from './HeaderPage';
import Logo from './Logo';
import RegistrationForm from './RegistrationForm';
import axios from 'axios';
class Registration extends Component {
mixins: [
Router.Navigation
];
constructor(props) {
super(props);
this.state = {
firstName:'',
lastName:'',
email:'',
errors:{},
helpText: '',
helpUrl: '',
nextLink:''
};
this.setUserState = this.setUserState.bind(this);
this.registrationFormIsValid = this.registrationFormIsValid.bind(this);
this.saveUser = this.saveUser.bind(this);
}
setUserState(e){
const target = e.target;
const value = target.value;
const name = target.name;
this.setState({[name]: value});
//delete this line
console.log(this.state[name]);
}
registrationFormIsValid(){
var formIsValid = true;
this.state.errors = {};
//validate first name
if(this.state.firstName.length < 2){
this.state.errors.firstName = "First name must be at least 2 characters.";
formIsValid = false;
}
//validate last name
if(this.state.lastName.length < 2){
this.state.errors.lastName = "Last name must be at least 2 characters.";
formIsValid = false;
}
//validate email
if(this.state.email.length < 2){
this.state.errors.email = "Email must be at least 2 characters.";
formIsValid = false;
}
this.setState({errors : this.state.errors});
return formIsValid;
}
saveUser(e, { history }){
e.preventDefault();
// const errorWrappers = document.getElementsByClassName('input');
// for (var i=0; i < errorWrappers.length; i++) {
// const isError= errorWrappers[i].innerHTML;
// if (isError.length > 0){
// errorWrappers[i].previousSibling.className = "error-input"
// }
// }
if(!this.registrationFormIsValid()){
return;
}
const values = {
firstName: this.state.firstName,
lastName: this.state.lastName,
email: this.state.email,
password: this.state.password,
phone: this.state.phone,
address: this.state.address,
dob: this.state.birthday
}
if (this.props.userRole === 'instructor'){
axios.post(`/instructors`, values)
.then((response)=> {
//delete this line
console.log(response);
})
.catch((error) => {
console.log(error + 'something went wrooooong');
});
this.props.history.push("/success-instructor");
}else{
axios.post(`/students`, values)
.then((response)=> {
//delete this line
console.log(response);
})
.catch((error) => {
console.log(error + 'something went wrooooong');
});
if (this.props.parent === "false"){
this.props.history.push("/success-student");
}else{
this.props.history.push("/success-parent");
}
}
}
//end of validation
render() {
return (
<div className="Registration">
<div className="container menu buttons">
<HeaderPage/>
</div>
<div className="page container narrow">
<div className="cover-content">
<Logo/>
<div className="container">
<h2 className="page-title">{this.props.title}</h2>
<a className="helpLink" href={this.props.helpUrl}>{this.props.helpText}</a>
<div className="main-content background-white">
<RegistrationForm
userRole={this.props.userRole}
onChange={this.setUserState}
onSave={this.saveUser}
errors={this.state.errors}
/>
<br/>
<br/>
<br/>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default withRouter(Registration);
Instead of
this.state.errors = {};
and
this.state.errors.lastName = "Last name must be at least 2 characters.";
use
this.setState({errors = {}});
this.setState({ errors: { lastName: "Last name must be at least 2 characters." } });
You need to avoid directly mutating the state.
The Warning itself answers the question. Please read the React Doc
carefully.
"warning Do not mutate state directly. Use setState()
react/no-direct-mutation-state."
Do not mutate state
Don't ever have code that directly changes state. Instead, create new object and change it. After you are done with changes update state with setState.
Instead of:
this.state.errors.someError1="e1";
this.state.errors.someError2="e2";
do this:
this.errorsObject=Object.assign({},this.state.errors,{someError1:"e1",someError2:"e2"};
and in the end:
this.setState({
errors:this.errorsObject
});
Object.assign lets us merge one object's properties into another one, replacing values of properties with matching names. We can use this to copy an object's values without altering the existing one.

Getting values from form input groups

I have a group of form inputs that are produced like so:
Please see UPDATE 2 below for full component.
So, if there are three columns then three of this group will be shown in the form. I am trying to extract the data from these inputs but I only need the ids. How can I extract the column id from the TextField?
Also, I need to get the data (i.e. the ids) per group so that they appear in an array:
transformations: [{columnId: 1, ruleId: 4}, {columnId: 2, ruleId: 2}, {columnId:3 , ruleId: 1}]
These are just example values, the main problem as I mentioned, is that I'm not sure how to extract the value of the columnId from the first input. I'm also struggling with getting the multiple sets of data.
Any help with this problem would be much appreciated.
Thanks for your time.
UPDATE:
handleRuleChange looks like this:
handleRuleChange = (e, index, value) => {
this.setState({
ruleId: value
})
}
UPDATE 2:
Here is the component:
import React from 'react'
import Relay from 'react-relay'
import { browserHistory } from 'react-router'
import SelectField from 'material-ui/SelectField'
import MenuItem from 'material-ui/MenuItem'
import TextField from 'material-ui/TextField'
import CreateTransformationSetMutation from '../mutations/CreateTransformationSetMutation'
class CreateTransformationSetDialog extends React.Component {
componentWillMount() {
this.props.setOnDialog({
onSubmit: this.onSubmit,
title: 'Create and Apply Transformation Set'
})
}
initial_state = {
targetTableName: '',
ruleId: 'UnVsZTo1',
}
state = this.initial_state
onSubmit = () => {
const onSuccess = (response) => {
console.log(response)
browserHistory.push('/table')
}
const onFailure = () => {}
Relay.Store.commitUpdate(
new CreateTransformationSetMutation(
{
viewer: this.props.viewer,
version: this.props.viewer.version,
targetTableName: this.state.targetTableName,
transformations: ///this is where I need to get the values///,
}
),
{ onSuccess: onSuccess }
)
}
handleTextChange = (e) => {
this.setState({
targetTableName: e.target.value
})
}
handleRuleChange = (e, index, value) => {
this.setState({
ruleId: value
})
}
render() {
return (
<div>
<TextField
floatingLabelText="Table Name"
value={this.state.targetTableName}
onChange={this.handleTextChange}
/>
<br />
{
this.props.viewer.version.columns.edges.map((edge) => edge.node).map((column) =>
<div key={column.id}>
<TextField
id={column.id}
floatingLabelText="Column"
value={column.name}
disabled={true}
style={{ margin: 12 }}
/>
<SelectField
floatingLabelText="Select a Rule"
value={this.state.ruleId}
onChange={this.handleRuleChange}
style={{ margin: 12 }}
>
{
this.props.viewer.allRules.edges.map(edge => edge.node).map(rule =>
<MenuItem
key={rule.id}
value={rule.id}
primaryText={rule.name}
/>
)
}
</SelectField>
</div>
)
}
</div>
)
}
}
export default Relay.createContainer(CreateTransformationSetDialog, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
${CreateTransformationSetMutation.getFragment('viewer')}
version(id: $modalEntityId) #include(if: $modalShow) {
${CreateTransformationSetMutation.getFragment('version')}
id
name
columns(first: 100) {
edges {
node {
id
name
}
}
}
}
allRules(first: 100) {
edges {
node {
id
name
}
}
}
}
`
},
initialVariables: {
modalEntityId: '',
modalName: '',
modalShow: true,
},
prepareVariables: ({ modalEntityId, modalName }) => {
return {
modalEntityId,
modalName,
modalShow: modalName === 'createTransformationSet'
}
}
})
It is using Relay but that isn't connected to the question, just need to extract the data from the inputs into the transformations array.
This can meet your requirement. Most of code will be understandable. feel free to ask for queries.
class CreateTransformationSetDialog extends React.Component {
componentWillMount() {
this.props.setOnDialog({
onSubmit: this.onSubmit,
title: 'Create and Apply Transformation Set'
})
}
initial_state = {
targetTableName: '',
transformations: [];
ruleId:'UnVsZTo1' //default values for all rules
}
state = this.initial_state
onSubmit = () => {
const onSuccess = (response) => {
console.log(response)
browserHistory.push('/table')
}
const onFailure = () => {}
Relay.Store.commitUpdate(
new CreateTransformationSetMutation(
{
viewer: this.props.viewer,
version: this.props.viewer.version,
targetTableName: this.state.targetTableName,
transformations: this.state.transformations,
}
),
{ onSuccess: onSuccess }
)
}
handleTextChange = (e) => {
this.setState({
targetTableName: e.target.value
})
}
handleRuleChange = (index, ruleId, columnId) => { //TODO: make use of index if needed
let transformations = this.state.transformations;
const isInStateWithIndex = transformations.findIndex((el) => el.columnId === columnId);
if(isInStateWithIndex > -1){
transformations[isInStateWithIndex].ruleId = ruleId; //changed rule
}else{
transformations.push({columnId: columnId, ruleId: ruleId}); //new column added to state.
}
this.setState({
transformations: transformations
}); //do with es6 spread operators to avoid immutability if any
}
render() {
return (
<div>
<TextField
floatingLabelText="Table Name"
value={this.state.targetTableName}
onChange={this.handleTextChange}
/>
<br />
{
this.props.viewer.version.columns.edges.map((edge) => edge.node).map((column) =>
<div key={column.id}>
<TextField
id={column.id}
floatingLabelText="Column"
value={column.name}
disabled={true}
style={{ margin: 12 }}
/>
<SelectField
floatingLabelText="Select a Rule"
value={this.state.ruleId}
onChange={(e, index, value) => this.handleRuleChange(index, value, column.id )}
style={{ margin: 12 }}
>
{
this.props.viewer.allRules.edges.map(edge => edge.node).map(rule =>
<MenuItem
key={rule.id}
value={rule.id}
primaryText={rule.name}
/>
)
}
</SelectField>
</div>
)
}
</div>
)
}
}
Maintaining the state for transformations in state with dynamically created columns.

React/Redux clear form elements values after submit

I have problems with clearing values from input and select form elements in react form after successful submit through axios library. Just want to mention that i do not use redux-form.
I don't know if I am on the right track here, this is my workflow by far: I wrote a form with react-bootstrap, give every input and select value through props and I access and update the state through these props. I have wrote actions and reducers for updating input values, and one action is dispatched in my component, but the second action and the reducer that is supposed to clear values after submit doesn't work as expected. This is the main problem, I'm not sure if I dispatch FORM_RESET action form in the right place, because I call it in the action that is responsible for posting data to server, and on success callback I dispatch FORM_RESET.
Below is the code relevant for this problem.
/* actionRegister.js */
let _registerUserFailure = (payload) => {
return {
type: types.SAVE_USER_FAILURE,
payload
};
};
let _registerUserSuccess = (payload) => {
return {
type: types.SAVE_USER_SUCCESS,
payload,
is_Active: 0,
isLoading:true
};
};
let _hideNotification = (payload) => {
return {
type: types.HIDE_NOTIFICATION,
payload: ''
};
};
//asynchronous helpers
export function registerUser({ //use redux-thunk for asynchronous dispatch
timezone,
password,
passwordConfirmation,
email,
name
}) {
return dispatch => {
axios.all([axios.post('/auth/signup', {
timezone,
password,
passwordConfirmation,
email,
name,
is_Active: 0
})
// axios.post('/send', {email})
])
.then(axios.spread(res => {
dispatch(_registerUserSuccess(res.data.message));
dispatch(formReset()); //here I dispatch clearing form data
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
}))
.catch(res => {
dispatch(_registerUserFailure(res.data.message)); //BE validation and passport error message
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
});
};
}
/* actionForm.js */
//synchronous action creators
export function formUpdate(name, value) {
return {
type: types.FORM_UPDATE_VALUE,
name, //shorthand from name:name introduced in ES2016
value
};
}
export function formReset() {
return {
type: types.FORM_RESET
};
}
/* reducerRegister.js */
const INITIAL_STATE = {
error:{},
is_Active:false,
isLoading:false
};
const reducerSignup = (state = INITIAL_STATE , action) => {
switch(action.type) {
case types.SAVE_USER_SUCCESS:
return { ...state, is_Active:false, isLoading: true, error: { register: action.payload }};
case types.SAVE_USER_FAILURE:
return { ...state, error: { register: action.payload }};
case types.HIDE_NOTIFICATION:
return { ...state , error:{} };
}
return state;
};
export default reducerSignup;
/* reducerForm.js */
const INITIAL_STATE = {
values: {}
};
const reducerUpdate = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.FORM_UPDATE_VALUE:
return Object.assign({}, state, {
values: Object.assign({}, state.values, {
[action.name]: action.value,
})
});
case types.FORM_RESET:
return INITIAL_STATE;
//here I need isLoading value from reducerRegister.js
}
return state;
};
export default reducerUpdate;
/* SignupForm.js */
import React, {Component} from 'react';
import {reduxForm} from 'redux-form';
import {connect} from 'react-redux';
import map from 'lodash/map';
import timezones from '../../data/timezones';
import styles from '../formElements/formElements.scss';
import {registerUser} from '../../actions/actionRegister';
import {formUpdate} from '../../actions/actionForm';
import FieldGroup from '../formElements/FieldGroup';
import { Form, FormControl, Col, Checkbox, Button, FormGroup } from 'react-bootstrap';
// {... props} passing large number of props wrap in object with spread notation
class SignupForm extends Component { //if component have state it needs to be class
constructor(props) {
super(props);
this.state = {
errors: { //this errors are irrelevant for now
name:'',
email: '',
password: '',
passwordConfirmation:'',
timezone:''
},
};
}
onChange = (event, index, value) => {
this.props.onChange(event.target.name, event.target.value);
};
onSave = (event) => {
event.preventDefault();
this.props.onSave(this.props.values);
}
render() {
let isLoading = this.props.isLoading;
return (
// this.props.handleSubmit is created by reduxForm()
// if the form is valid, it will call this.props.onSubmit
<Form onSubmit={this.onSave} horizontal>
<FieldGroup
id="formControlsName"
type="text"
label="Name"
name="name"
placeholder="Enter Name"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.name}
/>
<FieldGroup
id="formControlsEmail"
type="text"
label="Email"
name="email"
placeholder="Enter Email"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.email}
/>
<FieldGroup
id="formControlsPassword"
type="password"
label="Password"
name="password"
placeholder="Enter Password"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.password}
/>
<FieldGroup
id="formControlsPasswordConfirmation"
type="password"
label="Password Confirmation"
name="passwordConfirmation"
placeholder="Enter Password"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.passwordConfirmation}
/>
<FieldGroup
id="formControlsTimezone"
label="Time Zone"
name="timezone"
placeholder="Select Time Zone"
componentClass="select"
defaultValue="Select Your Timezone"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.timezone}
>
<option value="Select Your Timezone">Select Your Timezone</option>
{
map(timezones, (key, value) =>
<option key={key} value={key}>{value}</option>)
}
</FieldGroup>
<FormGroup>
<Col smOffset={4} sm={8}>
<Checkbox>Remember me</Checkbox>
</Col>
</FormGroup>
<FormGroup>
<Col smOffset={4} sm={8}>
<Button type="submit" disabled={isLoading}
onClick={!isLoading ? isLoading : null}
>
{ isLoading ? 'Creating...' : 'Create New Account'}
</Button>
</Col>
</FormGroup>
{this.props.errorMessage && this.props.errorMessage.register &&
<div className="error-container">{this.props.errorMessage.register}</div>}
</Form>
//this.setState({ disabled: true });
//this.props.errorMessage.register == this.props = {errorMessage :{ register: ''}}
);
}
}
function mapStateToProps(state) {
return {
errorMessage: state.signup.error,
isLoading: state.signup.isLoading,
values: state.form.values
};
}
function mapDispatchToProps(dispatch) {
return {
onSave: (values) => dispatch(registerUser(values)),
onChange: (name, value) => dispatch(formUpdate(name, value))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SignupForm)
;
There is no need to use redux-form :-) You're on the right path and you're calling FORM_RESET action in the right place.
Couple of things:
are you sure you are importing formReset in actionRegister.js?
in reducerForm I would suggest to still return new state here:
case types.FORM_RESET:
return { ...INITIAL_STATE }; // or Object.assign({}, INITIAL_STATE)
And btw. why are you setting isLoading: true on success? I would suggest to create 3 actions instead of 2:
SAVE_USER_START (which you dispatch before sending a request),
set isLoading to true,
SAVE_USER_SUCCESS - set isLoading to false
SAVE_USER_FAILURE - set isLoading to false
I would suggest to look into redux-form library. It provides configuration option to clear fields after submit out of the box.