Redux-form 6.0+ change all field values - javascript

I am trying to change multiple values in redux-form. I have them in one object so basically I want to override redux-form state values with my object values. One way to accomplish it is to run this.props.reset() followed by multiple this.props.change() events for each property. It works but it sends too many events and is slow. The second thing I tried is to run this.props.initialize(data,false) and this works but validation isn't rerun so I can easily submit the form without validation.
Is there a way to run one event to override form state with my object?

I am scared it is not possible. I had the same problem some time ago, and reading all the documentation in redux-form I got to conclude you have to use the action creators. Either change either autofill.
If you use initialize, you are initializing the values, it is meant to use for async initialization of data, therefore, it does not validate as you say.
Long ago in previous versions, they had a "defaultValue" concept. But they removed it. If you don't really need to have the last update, maybe it's worthy for you to check if that somehow would help you.
NOTE
I recommend you to follow this issue thread. They talk about it there.

It is possible. I achieved it in React using Redux via the Create-React-App file structure.
Using the stateProps/dispatchProps pattern.
You should already know about actions and reducers to use this.
Here is the project I originally started with https://medium.com/#notrab/getting-started-with-create-react-app-redux-react-router-redux-thunk-d6a19259f71f
I included that so you will know what I am talking about when I use terms like reducers and actions.
In you actions/index file
import makeAction from "./makeActionCreator";
const clearMultiplePropertiesInState = "clearMultiplePropertiesInState";
export default {
constants: {
clearMultiplePropertiesInState,
},
creators: {
clearMultiplePropertiesInState: makeAction(clearMultiplePropertiesInState
}
};
In your reducers/{your-reducer}.js
import actions from '../actions';
const { constants } = actions;
const INITIAL_STATE = {
name: "Dr Hibbert",
age: "40",
height: "180cm"
};
//#region const declarations
const { clearMultiplePropertiesInState } = constants;
//#endregion
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case clearMultiplePropertiesInState: {
var fields = action.data;
var theState = {
...state
};
for(var i = 0; i < fields.length; i++) {
theState[fields[i]] = "";
}
return theState;
}
default:
if (!action.type.includes('##')) {
console.log(`No action for: ${action.type} type`);
}
return state;
}
}
So the three items you want to clear are the state.name, state.age and state.height
import React from "react";
import { connect } from "react-redux";
import { Form, Icon, Button, Modal } from "semantic-ui-react";
import actions from "common/actions";
const { creators } = actions;
const MyComponent = ({ stateProps, dispatchProps }) => {
return (
<React.Fragment>
<Button
disabled={disableOkButton}
onClick={() => {
dispatchProps.clearMultiplePropertiesInState(["name", "age", "height"]);
}}
primary
labelPosition='right'
icon='checkmark'
content="Create Club"
loading={stateProps.modalOkButtonLoading}
/>
</React.Fragment>
);
}
function mapStatetoProps(state) {
return {
stateProps: {
name: state.name,
age: state.age,
height: state.height
}
};
}
function mapDispatchToProps(dispatch) {
return {
dispatchProps: {
clearMultiplePropertiesInState: (fieldNames) => {
dispatch(creators.clearMultiplePropertiesInState(fieldNames));
}
}
};
}
export default connect(mapStatetoProps, mapDispatchToProps)(MyComponent);
As I said you need to be well versed in using React with Redux to understand this but it is possible. This example shows I reset 3 values at the same time. So imaging passing new values as well...
I generally have a changeSinglePropInState action that I use (didnt include in code) which it passes the fieldName and the fieldValue it wants to change in state as I didnt want to create an action for every single item in my state.
Also if you can wrap your head around it, this changes one property of an object inside the state
case addItemToWishList: {
return {
...state,
buyer: {
...state.buyer,
wishlist: action.data
}
};
}

Related

In React / Redux, how to call the same fetch twice in componentDidMount, setting 2 state variables with results

The title is wordy, however a short / simple example will go a long ways in explaining my question. I have the following start to a component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGames } from '../../path-to-action';
class TeamsApp extends Component {
constructor(props) {
super(props);
this.state = {
oldGames: [],
newGames: []
};
}
componentDidMount() {
this.props.dispatch(fetchGames('1617'));
this.setState({ oldGames: this.props.teamGameData });
this.props.dispatch(fetchGames('1718'));
this.setState({ newGames: this.props.teamGameData });
}
...
...
}
function mapStateToProps(reduxState) {
return {
teamGameData: reduxState.GamesReducer.sportsData
};
}
export default connect(mapStateToProps)(TeamsApp);
I would like the action / reducer that corresponds with fetchGames() and gamesReducer to be called twice when the component mounts. This action / reducer grabs some sports data, and I am trying to grab data for two separate seasons (the '1617' season and the '1718' season). The fetchGames() is built correctly to handle the season parameter.
With the current setup, the states aren't being set, and my linter is throwing an error Do not use setState in componentDidMount.
Can I pass a callback to this.props.dispatch that takes the results of the fetchGames() (the teamGameData prop), and sets the oldGames / newGames states equal to this object?
Any help with this is appreciated!
Edit: if i simply remove the this.setState()'s, then my teamGameData prop simply gets overridden with the second this.props.dispatch() call...
Edit 2: I'm not 100% sure at all if having the 2 state variables (oldGames, newGames) is the best approach. I just need to call this.props.dispatch(fetchGames('seasonid')) twice when the component loads, and have the results as two separate objects that the rest of the component can use.
Edit 3: I have the following part of my action:
export const fetchSportsDataSuccess = (sportsData, season) => ({
type: FETCH_NBA_TEAM_GAME_SUCCESS,
payload: { sportsData, season }
});
and the following case in my reducer:
case FETCH_NBA_TEAM_GAME_SUCCESS:
console.log('payload', action.payload);
return {
...state,
loading: false,
sportsData: action.payload.sportsData
};
and the console.log() looks like this now:
payload
{ sportsData: Array(2624), season: "1718" }
but i am not sure how to use the season ID to create a key in the return with this season's data....
Edit 4: found solution to edit 3 - Use a variable as an object key in reducer - thanks all for help on this, should be able to take it from here!
Copying data from the redux store to one's component state is an anti-pattern
Instead, you should modify your redux store, for example using an object to store data, so you'll be able to store datas for multiples seasons :
sportsData: {
'1617': { ... },
'1718': { ... },
}
This way you'll be able to fetch both seasons in the same time :
componentDidMount() {
const seasons = ['1718', '1617'];
const promises = seasons.map(fetchGames);
Promise.all(promises).catch(…);
}
And connect them both :
// you can use props here too
const mapStateToProps = (reduxState, props) => ({
// hardcoded like you did
oldGames: reduxState.GamesReducer.sportsData['1617'],
// or using some props value, why not
newGames: reduxState.GamesReducer.sportsData[props.newSeason],
};
Or connect the store as usual and go for the keys:
const mapStateToProps = (reduxState, props) => ({
games: reduxState.GamesReducer.sportsData,
};
…
render() {
const oldGame = this.props.games[1718];
const newGame = this.props.games[1718];
…
}
Redux is you single source of truth, always find a way to put everything you need in Redux instead of copying data in components

images being duplicated when navigating between pages in redux / react application

I am creating a react / redux application for learning purposes. I am attempting to pull the images from contentful through their api. I have set up an action, reducer and component which displays the image fine, but when navigating between pages the images are duplicated. Everytime I return to the same page the image is duplicated + 1 so if I visit the page five times the image will exist 5 times on that page.
It would be great if anyone could give me some pointers in how to debug this or even a solution to the issue.
action
export function fetchAsset(id) {
const request = axios.get(`${API_BASE_URL}/spaces/${API_SPACE_ID}/assets/${id}?access_token=${API_TOKEN}`);
return {
type: FETCH_ASSET,
payload: request
};
}
reducer
import { FETCH_ASSET } from '../actions/index';
const EMPTY_ARRAY = []
export default function(state = EMPTY_ARRAY, action) {
switch(action.type) {
case FETCH_ASSET:
return [ ...state, action.payload.data];
default:
return state;
}
}
asset component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchAsset } from '../actions/index';
import styled, { css } from 'styled-components';
const RespImg = styled.img`
width: 100%;
`
class Asset extends Component {
componentWillMount() {
this.props.fetchAsset(this.props.assetId)
}
shouldComponentUpdate(nextProps, nextState) {
return true;
}
renderAsset() {
var assetArray = this.props.assets;
console.log(assetArray + ' this.props')
return assetArray.map((asset, index) => {
if (asset.sys.id == this.props.assetId) {
return (
<RespImg src={asset.fields.file.url} alt={asset.fields.file.fileName} key={index}/>
);
}
});
}
render() {
return (
<div className="asset">
{this.renderAsset()}
</div>
);
}
}
function mapStateToProps(state) {
return {
assets: state.assets
};
}
export default connect(mapStateToProps, { fetchAsset })(Asset)
Adding the component to the page
<Asset assetId={work.fields.featuredImage.sys.id} assetKey={index} />
I believe the problem here is that you fetch this image several times, that is why it is all good during the first render.
You keep your images in the array, and maybe you download the same asset, and add it to the array, even though it might exist there already. For such entities, the technique called normalizing can be used, so your state will look like:
state = {
[id]: Asset
};
Using this technique, you can get needed asset by id (you have it probably from the URL parameter).
Arrays in reducers are usually used for collections – for example, if you want to fetch all your assets. You can normalize response, and keep entities by id in one reducer, and result of collection requests in another one – so you'll have an array with ids, and an object with all possible Assets.
One more thing – #Dyo recommended you to put something into key, like id or url, and it is a good advice. However, if you open your console, you'll probably see something about elements with the same key. Basically, react does not render elements with the same key, so probably, your array of the same entities was rendered, but react rendered only one – all others were discarded.

Send metadata within an action to the reducer in redux

I have a component which builds onto the Select component from Ant Design https://ant.design/components/select/
<SomeComponent
onSelect = { this.props.handleSelect }
onDeselect = { this.props.handleDeselect }
selectionList = { valuesList }
value = { values }/>
onSelect triggeres the action this.props.handleSelect
export function handleSelect(value) {
return dispatch => {
dispatch(actionCreator(HANDLE_SELECT, value));
}
}
That actions goes into the reducer
case HANDLE_SELECT: {
const newValues = value_select(state, action);
return {
...state,
find: {
...state.a,
values: newValues
}
}
}
Finally, value_select is called to do all the magic
export const value_select = function(state, action) {
...
const newData = {
XYZ: action.payload
}
return newData
}
This brings me to my question.
Is it possible to send further metadata with the action? Imagine I use the component <SomeComponent.../> several times. I would not know which of the rendered components triggered the action when the onSelect is fired.
If I want to process the information in value_select = function(state, action) {... later, I want to know which component caused the action to process my data properly. I need to set XYZ in value_select() dynamically, depending on which <SomeComponent.../> caused the action. action.payload only gives me what is saved in value in <SomeComponent.../>, nothing more.
Is there a way to send some more information with the onSelect or is that bad practice and I would need an action for each component <SomeComponent.../> anyway?
Absolutely. It's your action and your reducer, you can attach any information you want to it.
The most common approach for structuring an action is the Flux Standard Action approach, which expects your actions to look like {type, payload, meta, error} but it's really up to you what you put into your actions.
For some more ideas, you might want to read through the Structuring Reducers - Reusing Reducer Logic section of the Redux docs.

Run reducer after state is updated by another reducer

Let's say I've got an app with two reducers - tables and footer combined using combineReducers().
When I click on some button two actions are being dispatched - one after another: "REFRESH_TABLES" and "REFRESH_FOOTER".
tables reducer is listening for the first action and it modifies the state of tables. The second action triggers footer reducer. The thing is it needs current state of tables in order to do it's thing.
My implementation looks something like below.
Button component:
import React from 'react';
const refreshButton = React.createClass({
refresh () {
this.props.refreshTables();
this.props.refreshFooter(this.props.tables);
},
render() {
return (
<button onClick={this.refresh}>Refresh</button>
)
}
});
export default refreshButton;
ActionCreators:
export function refreshTables() {
return {
type: REFRESH_TABLES
}
}
export function refreshFooter(tables) {
return {
type: REFRESH_FOOTER,
tables
}
}
The problem is that the props didn't update at this point so the state of tables that footer reducer gets is also not updated yet and it contains the data form before the tables reducer run.
So how do I get a fresh state to the reducer when multiple actions are dispatched one after another from the view?
Seems you need to handle the actions async so you can use a custom middleware like redux-thuk to do something like this:
actions.js
function refreshTables() {
return {
type: REFRESH_TABLES
}
}
function refreshFooter(tables) {
return {
type: REFRESH_FOOTER,
tables
}
}
export function refresh() {
return function (dispatch, getState) {
dispatch(refreshTables())
.then(() => dispatch(refreshFooter(getState().tables)))
}
}
component
const refreshButton = React.createClass({
refresh () {
this.props.refresh();
},
{/* ... */}
});
Although splitting it asynchronous may help, the issue may be in the fact that you are using combineReducers. You should not have to rely on the tables from props, you want to use the source of truth which is state.
You need to look at rewriting the root reducer so you have access to all of state. I have done so by writing it like this.
const rootReducer = (state, action) => ({
tables: tableReducer(state.tables, action, state),
footer: footerReducer(state.footer, action, state)
});
With that you now have access to full state in both reducers so you shouldn't have to pass it around from props.
Your reducer could then looks like this.
const footerReducer = (state, action, { tables }) => {
...
};
That way you are not actually pulling in all parts of state as it starts to grow and only access what you need.

Nested smart components in redux

I tried to make a reusable component in redux.
The idea behind this is that I am creating a smart combobox and place it several times inside an other component or smart component.
Lets assume the only job from this combobox is to display countries, allow to add new countries and tell the parent what country is selected.
The parent dont have to pass the available countries down to the combobox only the onValueChanged event so the parent knows what country is selected.
This results in the following structure (The items are not really countries to keep it simple but you should get the idea behind it):
//Constants (appConstants.ts)
export const SmartCombobox = {
ADD_ITEM: 'SMART_COMBOBOX/ADD_ITEM'
}
//Action creator (smartComboboxAction.ts)
import { SmartCombobox } from '../constants/appConstants';
export function AddItem() {
return {
type: SmartCombobox.ADD_ITEM
};
}
//Reducer (smartCombobox.ts)
import { SmartCombobox } from '../constants/appConstants';
const initialState = ['Item 1']
export default function items(state = initialState, action) {
switch (action.type) {
case SmartCombobox.ADD_ITEM:
let items = ['Item' + Math.random().toString()]
return state.concat(items);
default:
return state;
}
}
//Container (smartCombobox.ts)
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { default as SmartCombobox } from '../components/combobox';
import * as ComboboxActions from '../actions/smartComboboxAction';
function mapStateToProps(state) {
return {
items: state.items
};
}
function mapDispatchToProps(dispatch) {
return {
comboboxActions: bindActionCreators(<any>ComboboxActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SmartCombobox);
Then I am able to use it like this inside my component or smart component.
When I add a new item, every component that includes my smartCombobox would be synced and has the exact amout of items.
//Component (combobox.tsx)
import * as React from 'react';
interface SmartComboboxProps {
items?: Array<any>,
comboboxActions?: any,
onValueChanged: Function
}
export default class SmartCombobox extends React.Component<SmartComboboxProps, any> {
onValueChanged(event:any) {
let selectedValue = event.target.value;
const { onValueChanged } = this.props;
onValueChanged(selectedValue);
}
componentDidMount() {
// Call value changed for first selected item
this.props.onValueChanged(this.props.items[0]);
}
render() {
const { comboboxActions } = this.props;
let options = this.props.items.map(function (o) {
return <option key={o} value={o}>{o}</option>
});
return (
<div>
<select style={{ width: "200px" }} name="SmartCombobox" onChange={ this.onValueChanged.bind(this) } >
{ options }
</select>
<button onClick={ comboboxActions.AddItem }>Add item</button>
</div>
);
}
}
Final result (Image)
Is this the correct approach for reusable components?
Or are there maybe any pitfalls I might forgot?
There was also the idea that the combobox should be connected directly to an api because the app shouldn't know whats happening in here.
But this would break the idea of flux because I would need a state inside this component etc.
I was against that idea...
Is this the correct approach for reusable components?
Or are there maybe any pitfalls I might forgot
This approach is good.
There was also the idea that the combobox should be connected directly to an api because the app shouldn't know whats happening in here.
You are right here. The source of truth (or rather truth setter) only needs to be one and therefore cannot be the component.

Categories