is there any way to make this react component less verbose? - javascript

So, i wrote a test project to explore react, react-router and react-redux.
After i got everything working fine i laid my eyes again on Settings.jsx and i am wondering how could i make it less verbose and error prone:
import React, { Component } from "react";
import { connect } from "react-redux";
class Settings extends Component {
state = { name: this.props.settings.name };
render() {
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}/>
<button onClick={e => this.props.changeName(this.state.name)}>
Change
</button>
</div>
);
}
}
const mapState = state => ({ settings: state.settings });
const mapDispatch = dispatch => {
return {
changeName(name) {
dispatch({ type: "setName", name });
}
};
};
export default connect(
mapState,
mapDispatch
)(Settings);
My first idea was to convert it into a functional component, but it's said that they don't have state and i need the state to locally handle the input.

With #babel/plugin-proposal-decorators, connect can be used as a decorator:
import React, { Component } from "react";
import { connect } from "react-redux";
const mapState = state => ({ settings: state.settings });
const mapDispatch = dispatch => {
return {
changeName(name) {
dispatch({ type: "setName", name });
}
};
};
#connect(mapState, mapDispatch)
export default class Settings extends Component {
state = { name: this.props.settings.name };
render() {
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}/>
<button onClick={e => this.props.changeName(this.state.name)}>
Change
</button>
</div>
);
}
}
small, but imho nice simplification
also, you could use concise syntax with your mapDispatch:
const mapDispatch = dispatch => ({
changeName(name) {
dispatch({ type: "setName", name });
}
});

you can do this if you want to to add the typing text in store:
Settings.js
import React from "react";
import { changeName, typingName } from '../actions/settingsActions'
import { connect } from "react-redux";
const Settings = () => {
const { changeName, typingName, typedName, submittedName } = this.props
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={typedName}
onChange={e => typingName(e.target.value)}/>
<button onClick={changeName(submittedName)}>
Change
</button>
</div>
);
}
const mapState = state => ({
typedName: state.typedName,
submittedName: state.submittedName
});
const mapDispatchToProps = dispatch => ({
typingName: x => dispatch(typingName(x)),
changeName: x => dispatch(changeName(x))
})
export default connect(
mapState,
mapDispatch
)(Settings);
settingsActions.js
export const typingName = payload => ({
type: 'TYPING_NAME',
payload
});
export const changeName = payload => ({
type: 'CHANGE_NAME',
payload
});
settingsReducer.js
export const typingName = (state = [], action) => {
switch (action.type) {
case 'TYPING_NAME':
return [...state, action.payload];
default:
return state;
}
};
export const changeName = (state = '', action) => {
switch (action.type) {
case 'CHANGING_NAME':
return action.payload;
default:
return state;
}
};
You could maybe achieve something like this. But validating the typing state inside the component then sending the final result to the store as you did is a better idea I think, to avoid so much verbose.
Also you should of course create a constants file, but I guess you know already.

Related

Better way to store data in REDUX

Language used : javascript with react / redux
My project : I have a multiple step form. At every step,when a user write something or check someting i'm using redux to store the state. I have one reducer but I'm creating an action for every step of the form
What i would like to do : I would like to have only one action to update the state step by step.
What i'm doing now (working fine) :
my page who contain each step
const Form = () => {
return (
<div className="page">
<form>
{
{
1: <StepOne />,
2: <StepTwo />,
3: <StepThree />,
}[buttonDatas.pageNumber]
}
</form>
</div>
);
};
export default Form;
here one example of a step component (stepOne)
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addName } from '../../../actions/form.action.js';
import { isEmpty } from '../../../middlewares/verification.js';
export const StepOne = () => {
const dispatch = useDispatch();
const usersList = useSelector((state) => state.userReducer);
const [userName, setUserName] = useState();
useEffect(() => {
dispatch(addName(userName));
}, [dispatch, userName]);
return (
<div>
<label>Select the user name</label>
<select
name="name"
onChange={(e) => {
const userSelected = e.target.value;
setUserName(userSelected);
}}
defaultValue={'default'}
>
<option value="default" hidden disabled>
Select a user
</option>
{!isEmpty(usersList[0]) &&
usersList.map((user) => {
return (
<option key={user.id}>
{user.fullName}
</option>
);
})}
</select>
</div>
);
};
here my reducer :
import {
ADD_NAME,
ADD_PHONE,
ADD_ADDRESS,
} from '../actions/form.action';
const initialState = {
userInfo: {
name: '',
phone : '',
},
address: ''
};
export default function formReducer(state = initialState, actions) {
switch (actions.type) {
case ADD_NAME:
state = {
...state,
userInfo: {
name: actions.payload,
},
};
return state;
case ADD_PHONE:
state = {
...state,
userInfo: {
phone: actions.payload,
},
};
return state;
case ADD_ADDRESS:
state = {
...state,
address: actions.payload,
},
};
return state;
default:
return state;
}
}
Is there a better way to write it ?
You can create one object that includes all the necessary property values throughout your multi-step form wizard layout and maintain only one action to save the data in the Redux store. Instead of making each action item for a single property of the identical form.
hereby am giving you a reference which will help you to organise your code based on your requirement.
I will recommend you to go through below two links:
Redux Form multi-step wizard form
Video Tutorial of creating a multi-step form using react Hooks.
With the indications of nimish, i've used redux toolkit and react-hook-form and it's working fine.
What i've change :
Create a slice file (for my reducer and action ) with redux toolkit
import { createSlice } from '#reduxjs/toolkit';
const formSlice = createSlice({
name: 'form',
initialState: {
userInfo: {
name: '',
phone: ''
},
address: ''
},
reducers: {
selectUserName: (state, action) => {
state.userInfo.name = action.payload;
},
addUserPhone: (state, action) => {
state.userInfo.phone = action.payload;
},
addAddress: (state, action) => {
state.address = action.payload;
},
},
});
export const reducer = formSlice.reducer;
export const {
selectUserName,
addUserPhone,
addAddress,
} = formSlice.actions;
use it in my userInfo component
import React from 'react';
import { userName } from './userName';
import { userPhone } from './userPhone';
import { useDispatch, useSelector } from 'react-redux';
import {
selectUserName,
addUserPhone,
} from '../../../reducer/form.slice';
import { useForm } from 'react-hook-form';
export const UserInfo = () => {
const dispatch = useDispatch();
const state = useSelector((state) => state.reducer);
const { register, handleSubmit } = useForm({
defaultValues: {},
});
const onChange = (data) => {
dispatch(selectUserName(data.name);
dispatch(addUserPhone(data.phone));
};
return (
<div>
<h2 className="title"> Step One : Information User</h2>
<form onChange={handleSubmit(onChange)} className="form">
<userName register={register} />
<userPhone register={register} />
</form>
//to see your result
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
);
};
in the child comp
import React from 'react';
import { useSelector } from 'react-redux';
import { isEmpty } from '../../../middlewares/verification.js';
export const UserName = ({ register }) => {
const userList = useSelector((state) => state.userReducer);
return (
<div className="form_group">
<label>Select the user name</label>
<select
className="select"
name="name"
{...register('userName')}
defaultValue={'default'}
>
<option value="default" hidden disabled>
Select a user
</option>
{!isEmpty(userList[0]) &&
userList.map((user) => {
return (
<option value={user.fullName} key={user.mail}>
{user.fullName}
</option>
);
})}
</select>
</div>
);
};
thank you.

TypeError: updateElement is not a function

I am trying to update an element from an array by adding an object as a property like shown in this picture
When a user clicks on a single node button, a modal appears the user fills the form and then it is addes as a property for this node.
But for some reason I get this type error that says that the updateElement is not a function.
BTW, I am using Redux & react-flow-renderer libraries.
Reducer
import * as types from '../actions/types';
const initialState = {
elements: []
};
const flow = (state = initialState, action) => {
switch (action.type) {
case types.UPDATE_ELEMENT:
return {
...state,
elements: state.elements.map((e) => {
if (e.id === action.payload.id) {
e = {
...e,
options: action.payload.options,
};
}
return e;
}),
};
default:
return state;
}
};
export default flow;
Action
import { UPDATE_ELEMENT } from './types';
export const updateElement = (data) => (dispatch) => {
dispatch({
type: UPDATE_ELEMENT,
payload: data,
});
};
Node modal
import React, { useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { updateElement } from '../../../../redux/actions/flow';
const VPCNodeModal = (props, { updateElement }) => {
const [formData, setFormData] = useState({
instance: '',
});
// options
const { instance } = formData;
const onFormChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmitForm = () => {
const update = {
id: selectedElement.id,
options: formData,
};
updateElement(update);
};
return (
<>
<Modal {...props}>
<form
onSubmit={(e) => {
e.preventDefault();
onSubmitForm();
}}
>
<label>
<span> Instance name:</span>
<input
type='text'
name='instance'
value={instance}
onChange={onFormChange}
/>
</label>
<button type='submit'>Submit</button>
</form>
</Modal>
</>
);
};
VPCNodeModal.propTypes = {
updateElement: PropTypes.func.isRequired
};
export default connect(null, { updateElement })(VPCNodeModal);
Issue is while receiving the props.
change
const VPCNodeModal = (props, { updateElement }) => {
to
const VPCNodeModal = (props) => {
const { updateElement } = props;
updateElement is a props was passes in VPCNodeModal. So you should update like this with spread operator
const VPCNodeModal = ({ updateElement, ...props }) => {

Updating redux state onClick

I have a component that displays data from the state. I'm using redux for state. I want to be able to click a button and filter the state. But I'm stuck on dispatching the action from the button.
Right now I have a button that is supposed to dispatch the action but it's not being called. I'm not sure if the mapsToDispatchProps is wrong or it's something else.
Here is the actions
import { GET_POLLS, SHOW_APPROVAL } from './types';
const URL = 'https://projects.fivethirtyeight.com/polls/polls.json';
export const getPolls = () => dispatch => {
return fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
}
export const getApproval = () => ({ type: SHOW_APPROVAL })
reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return (payload.type === "trump-approval")
default:
return state
}
}
export default pollReducer;
types
export const GET_POLLS = 'GET_POLLS';
export const POLLS_LOADING = 'POLLS_LOADING';
export const SHOW_ALL = 'SHOW_ALL';
export const SHOW_APPROVAL = 'SHOW_APPROVAL';
list that displays data
import React, { Component } from 'react'
import { PollCard } from '../Components/PollCard'
// import FilterLink from './FilterLink'
import * as moment from 'moment';
import { connect } from 'react-redux'
import { getPolls, getApproval } from '../actions/index';
class PollList extends Component {
componentDidMount() {
this.props.getPolls();
}
render() {
console.log("rendering list")
const { polls } = this.props
const range = 30
var dateRange = moment().subtract(range, 'days').calendar();
var filteredPolls = polls.filter(e => Date.parse(e.endDate) >= Date.parse(dateRange)).reverse()
return (
<React.Fragment>
<button onClick={getApproval}>
Get Approval
</button>
{console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
polls: state.polls
});
const mapDispatchToProps = {
getApproval
};
export default connect(
mapStateToProps,
mapDispatchToProps,
{ getPolls, getApproval }
)(PollList);
// export default PollList;
Your mapDispatchToProps() appears to be configured incorrectly. You need to define a function that returns an object, defining a key-value pair for each action you want to make available as a prop in your component.
const mapDispatchToProps = (dispatch) => {
return {
getApproval: () => {
dispatch(getApproval())
},
getPolls: () => {
dispatch(getPolls())
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProp)(PollList);
Now getPolls is available as prop and you can use it in componentDidMount()
componentDidMount() {
this.props.getPolls();
}
You should also create an onClick handler for your getApproval action
handleClick = () => {
this.props.getApproval()
}
And then connect it to your onClick event-listener
<React.Fragment>
<button onClick={this.handleClick}>
Get Approval
</button>
console.log("get approval", getApproval)}
{
filteredPolls && filteredPolls.map((poll) => (
<div key={poll.id}>
<PollCard poll={poll} />
{/* {(poll.type)} */}
</div>
))
}
</React.Fragment>
Action File
export const getPolls = () => dispatch => {
fetch(URL)
.then(res => res.json())
.then(polls => {
dispatch({ type: GET_POLLS, payload: polls })
})
.catch(errors => {
dispatch({ type: "GET_ERRORS", payload: errors.response.data })
})
}
Reducer
import {
GET_POLLS,
SHOW_APPROVAL
} from '../actions/types';
const pollReducer = (state = [], { type, payload }) => {
switch (type) {
case GET_POLLS:
return payload
case SHOW_APPROVAL:
return state.filter((poll) => {
return poll.type === "trump-approval"
})
case "GET_ERRORS":
return payload
default:
return state
}
}
export default pollReducer;
You are not calling the action function.
// Either destructure it
const { polls, getApproval } = this.props;
<button onClick={getApproval}>
Get Approval
</button>
// Or use this.props.function
<button onClick={this.props.getApproval}>
Get Approval
</button>
// You don't need this
const mapDispatchToProps = {
getApproval
};
// You don't need this
const mapStateToProps = state => {
return {polls: state.polls};
};
export default connect(
mapStateToProps,
// Doing this is easier, cleaner & faster
{ getPolls, getApproval }
)(PollList);
Here you are doing it correctly;
componentDidMount() {
this.props.getPolls();
}

Passing multiple actions to mapDispatchToProps in Redux

I have 3 actions I am trying to use in this component. One logs me out of Firebase Google Auth (working correctly), and the other two are simply changing a piece of state to a certain string which I am going to use later to determine what component to render.
The commented out mapDispatchToProps works fine and it's how I'm used to writing it, the one using the logout method is the syntax I can't figure out. How can I refactor the below so that setRoutines and setExercises work?
The component:
import React from 'react';
import { connect } from "react-redux";
import { firebaseLogout } from '../Auth/Auth.actions';
import { setRoutines, setExercises } from './Profile.actions';
const Profile = ({logout, setRoutines, setExercises}) => (
<React.Fragment>
<button onClick={setRoutines}>My Routines</button>
<button onClick={setExercises}>My Exercises</button>
<button onClick={logout}>Logout</button>
</React.Fragment>
);
const mapDispatchToProps = (dispatch) => ({
logout: () => dispatch(firebaseLogout()),
setRoutines,
setExercises,
});
// const mapDispatchToProps = {
// setRoutines,
// setExercises
// };
export default connect(
undefined,
mapDispatchToProps
)(Profile);
My actions file:
export const setRoutines = () => ({
type: "SET_ROUTINES",
payload: "routines"
});
export const setExercises = () => ({
type: "SET_EXERCISES",
payload: "exercises"
});
export const logout = () => ({
type: 'LOGOUT'
});
export const firebaseLogout = () => {
return () => {
return firebase.auth().signOut();
}
};
My reducer file:
export default (state = {view:'routines'}, action) => {
switch (action.type) {
case 'SET_ROUTINES':
return {
...state,
view: action.payload
};
case 'SET_EXERCISES':
return {
...state,
view: action.payload
};
case 'LOGOUT':
return {};
default:
return state;
}
};
By modifying the mapDispatchToProps to the below mention format should help in creating a bound action creator that automatically dispatches.
const mapDispatchToProps = (dispatch) => ({
logout: () => dispatch(firebaseLogout()),
boundRoutines: () => dispatch(setRoutines()),
boundExercises: () => dispatch(setExercises()),
});
After creating a bound action creator we can call the creator as follows.
const Profile = ({logout, boundRoutines, boundExercises}) => (
<React.Fragment>
<button onClick={boundRoutines}>My Routines</button>
<button onClick={boundExercises}>My Exercises</button>
<button onClick={logout}>Logout</button>
</React.Fragment>
);

Action doesn't update the store

|I have the following component based on this:
**WarningModal.js**
import React from 'react';
import ReactDOM from 'react-dom';
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Alert, No} from './pure/Icons/Icons';
import Button from './pure/Button/Button';
import Modal from './pure/Modal/Modal';
import {setWarning} from '../actions/app/appActions';
import configureStore from '../store/configureStore';
const store = configureStore();
export const WarningModal = (props) => {
const {message, withCleanup} = props;
const [
title,
text,
leave,
cancel
] = message.split('|');
const handleOnClick = () => {
props.setWarning(false);
withCleanup(true);
}
return(
<Modal>
<header>{title}</header>
<p>{text}</p>
<Alert />
<div className="modal__buttons-wrapper modal__buttons-wrapper--center">
<button
onClick={() => withCleanup(false)}
className="button modal__close-button button--icon button--icon-only button--text-link"
>
<No />
</button>
<Button id="leave-warning-button" className="button--transparent-bg" onClick={() => handleOnClick()}>{leave}</Button>
<Button id="cancel-warning-button" onClick={() => withCleanup(false)}>{cancel}</Button>
</div>
</Modal>
);
}
WarningModal.propTypes = {
withCleanup: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
setWarning: PropTypes.func.isRequired
};
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const WarningModalContainer = connect(mapStateToProps, {
setWarning
})(WarningModal);
export default (message, callback) => {
const modal = document.createElement('div');
document.body.appendChild(modal);
const withCleanup = (answer) => {
ReactDOM.unmountComponentAtNode(modal);
document.body.removeChild(modal);
callback(answer);
};
ReactDOM.render(
<Provider store={store}>
<WarningModalContainer
message={message}
withCleanup={withCleanup}
/>
</Provider>,
modal
);
};
the issue I have is that 'setWarning' doesn't update the state, it does get called as I have a debugger inside the action and the reducer but the actual property doesn't not change to 'false' when:
props.setWarning(false);
gets called.
I use the following to trigger the custom modal:
const togglePromptCondition =
location.hash === '#access-templates' || location.hash === '#security-groups'
? promptCondition
: isFormDirty || isWarning;
<Prompt message={promptMessage} when={togglePromptCondition} />
To test this even further I have added 2 buttons in the application to toggle 'isWarning' (the state property I am talking about) and it works as expected.
I think that although WarningModal is connected in actual fact it isn't.
REDUCER
...
case SET_WARNING:
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
ACTION
...
export const setWarning = status => {
console.log('action called')
return {
type: SET_WARNING,
payload: status
}
};
...
UPDATE
After having to incorporates the following:
const mapStateToProps = state => {
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
I am now getting:
Maybe this could help?
You have to dispatch the actions in the action creator and the type of the action to dispatch should be always string.
Try this
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
console.log(dispatch)
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
const WarningModalContainer = connect(mapStateToProps, mapDispatchToProps)(WarningModal);
REDUCER
...
case 'SET_WARNING':
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...

Categories

Resources