I want to set the initial state in reducer.js from the existing props which I get from the Home.js
I tried to call home: this.props.homedata to set the initial state but it is not working.
import { COMMENT_SHOW, LIKE_SHOW, HOMEDATA_SHOW } from '../actions/types';
const INITIAL_STATE = {
wallid: '',
comment: '',
like: '',
home: this.props.homedata
};
export default (state = INITIAL_STATE, action) => {
........///////////
}
Reducer.js
When the app loads I want to get the same data in home which I have in homedata state
I don't think it's possible to pass prop to your reducer. What you can do is have a data set file, say data.js, and export your data. Import it into your reducer file, and set it as your initial data.
import { COMMENT_SHOW, LIKE_SHOW, HOMEDATA_SHOW } from '../actions/types';
import { homeData } from 'path/to/data';
const INITIAL_STATE = {
wallid: '',
comment: '',
like: '',
home: homedata,
};
export default (state = INITIAL_STATE, action) => {
........///////////
}
If you don't want the sample data modified as the app runs. Maybe you need it to reset your home data, you will need to clone the data before passing it to into your initial state.
Reducers don't have props. You probably picked it up from React.
A reducer should update the Redux state based on the dispatched actions. To update the state to be homedata, you can dispatch an action and return the data in the reducer as the new state.
// action to dispatch
{
type: "UPDATE_HOME",
payload: homedata
}
// Reducer.js
const INITIAL_STATE = {
wallid: '',
comment: '',
like: '',
home: {}
};
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case "UPDATE_HOME":
return {
...state,
home: action.payload
};
default:
return state;
}
}
Related
I'm building a game using react-redux and TypeScript. I've got TS and React down, but redux... less so.
When the player clicks the start game button, the main menu is hidden via manipulation of redux state and the "gameState" is generated. "gameState" contains all the relevant information for the game world and entities therein, and is several thousand lines of serializable JSON. This part is definitely working, the problem comes when I try to dispatch to update it. I can see from the Redux browser extension that the payload being sent to the reducer function updateGameState is correct, but after the dispatch has been completed it's as if it never happened.
My question is simple: what am I doing wrong?
The code for the previously-mentioned dispatch is:
let nGS = gameStateGenerator.create(scene)
dispatch(updateGameState(nGS))
The layout of this part of the redux logic is as shown below. The four children of multiverse, universes, species, connections, and players, should all be populated, but are not.
I'm using combined reducers, as follows. I've not used ES6 notation for the reducer object properties as part of my attempts to rule out causes (which hopefully speaks to my level of desperation).
store.ts (top level)
import { configureStore, ThunkAction, Action } from '#reduxjs/toolkit';
import gameState from '../features/gameState/gameState';
export const store = configureStore({
reducer: {
gameState: gameState
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
gameState.ts (first and so far only child of root)
import { combineReducers, createSlice, PayloadAction } from '#reduxjs/toolkit';
import { GameState } from '../../interfaces/GameState';
import flags, { initialState as flagsInitialState} from '../flags/flags';
import multiverse, { initialState as multiverseInitialState } from '../multiverse/multiverse';
export const initialState: GameState = {
multiverse: multiverseInitialState,
flags: flagsInitialState
};
export const gameState = createSlice({
name: 'gameState',
initialState,
reducers: {
updateGameState: (state: GameState, action: PayloadAction<GameState>) => {
return Object.assign({}, state, action.payload)
}
}
});
export const { updateGameState } = gameState.actions;
export default combineReducers({
flags: flags,
multiverse: multiverse
})
flags.ts
import { createSlice } from '#reduxjs/toolkit';
import { FlagsState } from '../../interfaces/Flags';
export const initialState: FlagsState = {
ui: {
showMainMenu: true,
showWelcome: true,
gameStarted: false,
gameLoading: false,
gameLoaded: false
}
};
export const flags = createSlice({
name: 'flags',
initialState,
reducers: {
startGame: (state: Required<FlagsState>) => {
return Object.assign({}, state, {
ui: {
...state.ui,
showMainMenu: false,
gameStarted: true,
gameLoading: true
}
})
},
gameLoaded: (state: Required<FlagsState>) => {
return Object.assign({}, state, {
ui: {
...state.ui,
gameLoaded: true
}
})
}
}
});
export const { startGame, gameLoaded } = flags.actions;
export const getState = (state: FlagsState) => state;
export default flags.reducer;
And finally multiverse.ts
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { Multiverse } from '../../interfaces/Multiverse';
import { Universe } from './../../interfaces/Universes';
export const initialState: Multiverse = {
universes: [],
species: [],
connections: [],
players: []
};
export const multiverse = createSlice({
name: 'multiverse',
initialState,
reducers: {
setUniverses: (state: Required<Multiverse>, action: PayloadAction<Universe[]>) => {
return Object.assign({}, state, { universes: action.payload })
}
}
});
export const { setUniverses } = multiverse.actions;
export const getState = (state: Multiverse) => state;
export default multiverse.reducer;
I'm think the trouble comes from your reducer, you use Object assign and return.
Redux-Toolkit uses Immer to change the state with no mutating like this :
(state, action) => state.value = action.payload
See the doc, https://redux-toolkit.js.org/usage/immer-reducers#immutable-updates-with-immer
So for you, you can do something like :
state.univers = {...state.univers, ... action.payload}
with no return.
I am trying to fetch some data in a react component using the useEffect hook. After the initial render, fetchItems() gets the items and updates the store. However, items is still an empty object even after the store updates.
I might be using useEffects wrong. How do you use Redux with useEffects? I want to set a loading state for the component, but since the component only dispatches an action to fetch items (instead of directly calling the API), it does not know when the data is fetched and the store is updated so it can pull it.
Can someone please help figure out how to make sure that items object is updated after the saga fetch and the subsequent store update?
import React, { useState, useEffect } from "react";
import { connect } from 'react-redux';
import { useParams } from "react-router-dom";
const ItemComponent = ({ item, fetchItem }) => {
const { itemId } = useParams();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true)
fetchItem(itemId)
setIsLoading(false)
}, []);
console.log(item) // gives empty object even after the fetch and store update
}
const mapStateToProps = (state) => {
return {
item: state.item
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchItem: (itemId) => { dispatch(fetchItemActionCreator(itemId)) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ItemComponent);
fetchItemActionCreator is an action creator that creates the action to be dispatched.
My reducer and saga work fine as I can see the store actions and updates in the console.
If I pass the items object into the dependency array for useEffect, then there will be an infinite loop and the page keeps re-rendering.
Reducer:
const itemReducer = (state={}, { type, payload }) => {
switch(type) {
case ITEM_GET_SUCCESS:
return {...state, ...payload}
default: return state
}
}
fetchItemActionCreator:
import { createAction } from '#reduxjs/toolkit';
export const fetchItemActionCreator = createAction(ITEM_GET_PENDING);
Thank you very much in advance!
I want to set a loading state for the component
/** Action */
const getItem = () => dispatch => {
dispatch({ type: 'GET_ITEM_START' });
axios
.get('your api end point')
.then(res => {
const item = res.data;
dispatch({
type: 'GET_ITEM_SUCCESS',
payload: {
item,
},
});
})
.catch(error => {
dispatch({
type: 'GET_ITEM_FAIL',
payload: error,
});
});
};
/** Reducer */
const INITIAL_STATE = {
item: null,
error: '',
loading: false,
};
const itemReducer = (state = INITIAL_STATE, { type, payload }) => {
switch (type) {
case 'GET_ITEM_START':
return { ...state, error: '', loading: true };
case 'GET_ITEM_SUCCESS':
return { ...state, ...payload, loading: false };
case 'GET_ITEM_FAIL':
return { ...state, error: payload, loading: false };
default:
return state;
}
};
Then your could handle Loading state in your component
const ItemComponent = ({ fetchItem, item, loading, error }) => {
/** ... */
/**
Check for loading and show a spinner or anything like that
*/
useEffect(() => {
fetchItem(itemId);
}, []);
if (loading) return <ActivityIndicator />;
if (item) return <View>{/* renderItem */}</View>;
return null;
};
Trying to get user input with action,all is working i get my console.logs about how inputVal changes,but when I try to print this in i get undefined in console
Should I use like mapDispatchToProps or I don't need this,since I'm passing actions as second param into mapStateToProps
actions:
export const inputChange = val => {
return {
type: INPUT_CHANGE,
payload: val
};
};
reducer:
import { INPUT_CHANGE } from './actionTypes';
const initialState = {
inputVal: ''
};
export default (state = initialState, action) => {
switch (action.type) {
case INPUT_CHANGE:
return {
...state,
inputVal: action.payload
};
default:
return state;
}
};
mainPage:
const mapStateToProps = state => {
console.log(state);
return state;
};
class MainPage extends Component {
onInput = e => {
this.props.inputChange(e.target.value);
console.log(this.props.inputChange(e.target.value));
};
render() {
console.log(this.props.inputVal);
return (
<div>
<input onChange={this.onInput}></input>
<p>{this.props.}</p>
</div>
);
}
}
export default connect(
mapStateToProps,
{
addToCart,
removeFromCart,
selectItem,
inputChange
}
)(MainPage);
combinedReducers:
import { combineReducers } from 'redux';
import AddItem from './addItem/reducer';
import InputReducer from './reducerInput';
export default combineReducers({
AddItem,
InputReducer
});
I've tried to this.props.inputVal.
Since you have combineReducers, you should use these keys to access in mapStateToProps.
From the redux docs:
The state produced by combineReducers() namespaces the states of each
reducer under their keys as passed to combineReducers()
You can control state key names by using different keys for the
reducers in the passed object. For example, you may call
combineReducers({ todos: myTodosReducer, counter: myCounterReducer })
for the state shape to be { todos, counter }.
So your mapStateToProps must be like:
const mapStateToProps = state => {
console.log(state);
return {
inputVal: state.InputReducer.inputVal
}
};
A minimal working code sandbox:
https://codesandbox.io/s/cold-meadow-pxtu3
I'm currently developing an app with React Native. The state of the app is quite complex, but managable due to Redux and Normalizr. I now have to implement a functionality for the user to filter items.
In order for the user to filter items, I enriched the server response in the Normalizr schema:
export const subCategorySchema = new schema.Entity(
"subCategories",
{},
{
idAttribute: "uuid",
processStrategy: entity => {
const newEntity = Object.assign({}, { name: entity.name, uuid: entity.uuid, chosen: false });
return newEntity;
}
}
);
The corresponding reducer now looks like this:
const initialState = {};
const subCategoriesReducer = (state = initialState, action) => {
if (action.payload && action.payload.entities) {
return {
...state,
...action.payload.entities.subCategories
};
} else {
return state;
}
};
These the subcategories now get displayed in the UI using this SwitchListItem component, which gets it's items through a selector:
import React, { Component } from "react";
import { Switch, Text, View } from "react-native";
import PropTypes from "prop-types";
import styles, { onColor } from "./styles";
export default class SwitchListItem extends Component {
static propTypes = {
item: PropTypes.object
};
render() {
const { name, chosen } = this.props.item;
return (
<View style={styles.container}>
<Text style={styles.switchListText}>{name}</Text>
<Switch style={styles.switch} value={chosen} onTintColor={onColor} />
</View>
);
}
}
I'm now about to implement the <Switch /> component's onValueChange() function, which is where my question arose:
What is the best way to toggle a boolean value in a normalized state tree?
I came up with two solutions, which I will describe below. Please let me know if you think any one of these is good. If not I would love to get advice on what I could do better :)
Solution 1: Extending the reducer:
My first solution for the problem was to extend the reducer to listen to TOGGLE_ITEM actions. This would look something like this:
const subCategoriesReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
if (action.payload.item.uuid in state) return { ...state, ...action.payload.item };
}
if (action.payload && action.payload.entities) {
return {
...state,
...action.payload.entities.subCategories
};
} else {
return state;
}
};
This is my preferred solution as it does not need a lot of code.
Solution 2: Enriching the selector that passes the items to the SwitchList:
The other solution would be to enrich the objects while being passed to the list using a selector with it's key for the state. Then I could create an action that uses this key to update the state like this:
const toggleItem = (item, stateKey) => ({
type: TOGGLE_ITEM,
payload: {entities: { [stateKey]: item } }
})
I would love to read an answer, preferably opinionated, if you have a lot of experience with Redux. Also, if you think my way of enriching the data in the normalizr is bad and you can come up with a better way, please let me know! Thank you very much for any advice!
I did it in a completely different way.
I created an array that holds the uuids of the toggled items. Therefore I only need to look, whether the item is in the toggled array.
Just like this:
const initialState = {};
export const byId = (state = initialState, action) => {
if (action.payload && action.payload.entities && action.payload.entities.itemClassifications) {
return {
...state,
...action.payload.entities.itemClassifications
};
} else {
return state;
}
};
export const chosen = (state = [], action) => {
if (action.type === TOGGLE_ITEM && action.meta === ITEM_CLASSIFICATION) {
if (state.includes(action.payload.uuid)) {
return state.filter(uuid => uuid !== action.payload.uuid);
} else {
return [...state, action.payload.uuid];
}
} else {
return state;
}
};
const itemClassificationsReducer = combineReducers({
byId,
chosen
});
export default itemClassificationsReducer;
export const getAllItemClassificationsSelector = state =>
Object.values(state.itemClassifications.byId);
export const getAllItemClassificationsNormalizedSelector = state => state.itemClassifications.byId;
export const getChosenItemClassificationsSelector = state => state.itemClassifications.chosen;
export const enrichAllItemClassificationsSelector = createSelector(
getAllItemClassificationsSelector,
itemClassifications =>
itemClassifications.map(val => ({ ...val, stateKey: ITEM_CLASSIFICATION }))
);
export const getItemClassificationsFilterActiveSelector = createSelector(
getChosenItemClassificationsSelector,
itemClassifications => itemClassifications.length > 0
);
I have the following reducer
import { EntityState, createEntityAdapter } from '#ngrx/entity';
import { createFeatureSelector } from '#ngrx/store';
export const pageAdapter = createEntityAdapter<Page>({
//selectId: (collection: Collection) => collection.id,
});
export interface State extends EntityState<Page> {
}
const defaultPage = {
ids: ['kHnryus'],
entities: {
'kHnryus': {
id: '83nryus',
title: 'How to create a blog with Angular4 Ngrx',
description: 'How to create a blog with Angular4 Ngrx',
}
},
success_create: false
}
export const initialState: State = pageAdapter.getInitialState();
// Reducer
export function pageReducer(
state: State = initialState,
action: actions.PageActions) {
switch (action.type) {
case actions.ADD_ALL: {
return pageAdapter.addAll(action.pages, state);
};
case actions.SUCCESS: {
return {success_create: true}
};
default:
return state;
}
}
// Create the default selectors
export const getPageState = createFeatureSelector<State>('page');
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = pageAdapter.getSelectors(getPageState);
I want to get the boolean variable success_create of the state in my component.
Basically , I want that if there is SUCCESS, I should be able to get a success_create true in the component class. I have no idea as to how to do this and even if it's possible.
If it is, please how can I achieve this?
First make sure to remove defaultPage constant as you are not maintaining it within your reducers and you are already using #ngrx/entity for that. The success_create should be defined as follow:
export interface State extends EntityState<Page> {
success_create: boolean;
}
export const adapter: EntityAdapter<Item> = createEntityAdapter<Page>({
selectId: (page: Page) => page.id,
sortComparer: false
});
export const initialState: State = adapter.getInitialState({
success_create: false,
});
Then, after your default selectors add a third one that uses getPageState selector and gets one step deeper into your state:
export const getSuccessCreate = createSelector(
getPageState,
(state: State) => state.success_create
);
Then make your component/service listening to it directly like:
this.created$ = this.store.pipe(select(fromPages.getSuccessCreate));
note: the pipe is optional here and if used then select should be imported from #ngrx/store as it is done in the official ngrx demo app. pipe allow you to work with lettable rxjs operators. you can read more about it here.