My app contains main component, which render data, received from store, at list. Child component allow to select an options. And after user made a choice (one of options at dropdown list), object in store should be updated accordingly. Further, when I refresh the page, I expect updated list to be render.
Main component (TableMain):
import OperationSelect from "./operationSelect";
const mapStateToProps = (store) => {
return {itemsProp: store.fetch.items}
};
class TableMain extends React.Component {
// constructor
getOperationItems = function () {
this.props.itemsProp.map((item, index) => {
return (
<li>key={index} item={item}</li>
);
});
};
render() {
return <div>
{this.getOperationItems()}
<OperationSelect />
</div>;
}
}
export default connect(mapStateToProps)(TableMain)
Initial state:
export default {
items: [
{
'Date': null,
'Operation': 'revenue',
}
]
}
Update-action:
export function selectOperation(payload) {
return {
type: 'SELECT',
payload: payload,
};
}
I omit get-action, because its work well.
Update reducer:
import initialState from '../constants/initialState';
export default function update(state = initialState, action) {
switch (action.type) {
case 'SELECT':
return {
...state,
[action.payload.key]: action.payload.value
};
default:
return state;
}
}
Combine-reducers:
import {combineReducers} from 'redux';
import fetch from '../reducers/fetchReducer';
import update from '../reducers/updateReducer';
const rootReducer = combineReducers({fetch, update});
export default rootReducer;
And child-component for select-operation providing:
const mapDispatchToProps = (dispatch) => {
return {
selectOperation: (input) => dispatch({type: 'SELECT', payload: input})
}
};
const mapStateToProps = (store) => {
return {itemsProp: store.fetch.items}
};
class OperationSelect extends React.Component {
// constructor
handleChange(event) {
this.props.selectOperation({
key: 'Operation',
value: event.target.value
});
console.log(`items = ${JSON.stringify(this.props.itemsProp)}`);
};
render() {
return (
<label>
<select onChange={this.handleChange}>
<option selected="select value"></option>
<option value="value1">Option1</option>
<option value="value2">Option2</option>
</select>
</label>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(OperationSelect).
Container component exists, but omitted.
When I simply load a page (without any use choice) everything is well. App fetch from store initial-state data: {'Date': null,'Operation': 'revenue'}.
When I'm trying to select an option in dropdown list (for example, "value1"), I expect, updated data will be received from redux-store. For example - {'Date': null,'Operation': 'value1'}. But contrary to expectations I receive from store old an value - {'Date': null,'Operation': 'revenue'}.
In Browser-Console I see the following message:
index.js:1437 Unexpected key "items" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: "fetch", "update". Unexpected keys will be ignored.
Please, advise me, how could I update object-state in redux-store from Select dropdown-list?
You can try using "redux-persist" in your application. This package delays the app UI rendering until your persisted state has been retrieved and saved to redux after page refresh. You can read the documentation at https://www.npmjs.com/package/redux-persist
Alternatively, you can configure the dropdown in a way that it doesn't refresh the page, just refresh the affected values on the page and it will give more cleaner UI experience.
Related
I don't understand why React not update my object. In another component through the dispatch I update the state. In this (in code below) code in mapStateToProps categories are changing (console log show one more category). But component not rerender, although in component in useEffect I use props.categories. Event console.log in element does not run
const LeftSidebar = (props: any) => {
console.log('not render after props.categories changed')
useEffect(() => {
props.dispatch(getCategories())
}, [props.categories]);
const addCategoryHandler = (categoryId: number) => {
props.history.push('/category/create/' + categoryId)
};
return (
<div className='left-sidebar'>
<Logo/>
<MenuSidebar categories={props.categories} onClickAddCategory={addCategoryHandler}/>
</div>
);
};
function mapStateToProps(state: State) {
const categories = state.category && state.category.list;
console.log('this categories changes, but LeftSidebar not changing')
console.log(categories)
return { categories };
}
export default connect(mapStateToProps)(LeftSidebar);
I thought if i update state, react update components dependent on this state. How should it work? how should it work? It may be useful, the item that adds the category is not a parent or child, it is a neighbor
My reducer
import {CATEGORIES_GET, CATEGORY_CREATE} from "../actions/types";
export default function (state={}, action: any) {
switch (action.type) {
case CATEGORIES_GET:
return {...state, list: action.payload};
case CATEGORY_CREATE:
return {...state, list: action.payload};
default: return state;
}
}
Thanks for solving problem. All problem was in inmutable data. I used fixtures, and not copied properly array
import {CATEGORIES_GET, CATEGORY_CREATE} from "./types";
import {categoryMenuItems as items} from "../../fixtureData";
import {NewCategory} from "../../types";
let categoryMenuItems = items; // My mistake, I used not immutable value. Not use fixtures for state))
let id = 33;
export function getCategories() {
return {
type: CATEGORIES_GET,
payload: categoryMenuItems
}
}
export function createCategory(newCategory: NewCategory) {
id++
const category = {
title: newCategory.name,
id: id
};
// MISTAKE I use same array, not cloned like let clonedCategoryMenuItems = [...categoryMenuItems]
categoryMenuItems.push(category);
return {
type: CATEGORY_CREATE,
payload: categoryMenuItems
}
}
Not use fixtures for state, use real api :)
Maybe your state not is inmutable. In your reducer use spread operator to add new items
{
list: [
...state.list,
addedCategory
]
}
Instead of
state.list.push(addedCategory)
I'm new using Redux, and I'm trying to integrate React with Redux. What I want is to put all my actions in one reducer. Actually my reducer looks like this:
import {GET_ALL_CONNECTIONS, DELETE_CONNECTION, POST_CONNECTION} from '../actions';
const initialState = {
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case GET_ALL_CONNECTIONS:
return payload
case POST_CONNECTION:
return {...state, ...payload}
case DELETE_CONNECTION:
return {...state, ...payload}
default:
return state
}
}
The problem is when I call the action corresponding to the GET_ALL_CONNECTIONS type:
export const getAllConnections = () => {
return async (dispatch) =>{
const response = await Conexiones.get('/db/myConnections');
dispatch({type: GET_ALL_CONNECTIONS, payload: response.data});
}
}
When I call this function in a React component is supposed to get multiple connections from an API, and save the array of object resultant of the API call in the state.
The problem is when I want to save the connections array in the state, to later map that state and generate options with each one of the connection inside a select element. When I render the component it throws me the next error:
TypeError: this.props.conexiones.map is not a function
The file where I combine all reducers looks like this:
import {combineReducers} from 'redux';
import {reducer as formReducer } from 'redux-form';
import postUser from './postUser';
import postConnection from './postConnection';
import getAllConnections from './getAllConnections';
import ConnectionsReducer from './ConnectionsReducer';
export default combineReducers({
newUser: postUser,
form: formReducer,
conexiones: ConnectionsReducer
});
And the component where I do the call looks like this:
import React, { Component } from 'react';
import { Grid, Container, Select, Button, withStyles, FormControl, InputLabel, MenuItem } from '#material-ui/core';
import {connect} from 'react-redux';
import {reduxForm, Field} from 'redux-form';
import {deleteConnection, getAllConnections} from '../actions';
const styles = theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
});
class BorrarConexion extends Component {
componentDidMount() {
this.props.getAllConnections();
}
handleSubmit = ({conexionId}) => {
this.props.deleteConnection(conexionId);
}
renderConexiones = () => {
return this.props.conexiones.map(conexion =>{
return (<MenuItem key={conexion.id} value={conexion.id}>{conexion.connectionUrl}</MenuItem>);
});
}
renderSelectField = ({input,label,meta: { touched, error },children,...custom}) =>{
return (
<FormControl>
<InputLabel>Seleccione la URL que desea eliminar</InputLabel>
<Select {...input} {...custom}>
{this.renderConexiones()}
</Select>
</FormControl>
)
}
render() {
return (
<Container>
<Grid container direction="column">
<Field name="conexionId" component={this.renderSelectField} label="Favorite Color"/>
<Button onClick={this.props.handleSubmit(this.handleSubmit)}>Eliminar</Button>
</Grid>
</Container>
);
}
}
const mapStateToProps = (state) => {
return {conexiones: state.conexiones}
}
const BorraConexionEstilizado = withStyles(styles)(BorrarConexion);
const formWrapped = reduxForm({form: 'delete_connection'})(BorraConexionEstilizado);
export default connect(mapStateToProps, {getAllConnections, deleteConnection})(formWrapped);
When I do this with a separate reducer called getAllConnections and replace the conexiones: ConnectionsReducers with conexiones: getAllConnections it works. The getAllConnections reducer looks like this:
export default (state = [], { type, payload }) => {
switch (type) {
case 'GET_ALL_CONNECTIONS':
return payload
default:
return state
}
}
I want to know how to do this work with one reducer receiving all my actions instead of a individual reducer for each action.
This issue is related to the fact that you are likely returning different structures from your reducer. It looks like the reducer is meant to handle objects as state but for this one case you return an array. Objects do not have a map function. You need to determine the structure of your state for this reducer and stick with it, you cannot change from array to object and back again, that is undeterministic behavior that redux is not built for.
I do not know the implementation details of your APIs but this is the main area in need of updating
const initialState = []
export default (state = initialState, { type, payload }) => {
switch (type) {
case GET_ALL_CONNECTIONS:
return payload //hoping that your api gave an array here
case POST_CONNECTION:
//return {...state, ...payload} //this is clearly returning an object
return [...state, payload] //now it returns an array with a new item
case DELETE_CONNECTION:
//return {...state, ...payload} //this is clearly returning an object
return state.filter(item => item.id !== payload.id) //now it returns a filtered array
default:
return state
}
}
The following code should recieve states as arrays and return updated states as arrays.
You have to set initial state... right now, your state is an empty object
I solved this issue by surrounding the conexion:state.conexion in the mapStateToProps method with Object.values() like this:
conexion: Object.values(state.conexion)
I am developing a Web application using React JS + Redux. I am new to React. What I am doing now is trying to set the state on one page and then retrieve the state in another page after redirection.
I have a Component called EventListComponent that displays the list of events. Inside that component, I change the state of a reducer calling an event.
This is the reducer function I call.
import * as EditEventActions from '../actions/edit.event.actions';
export default function (state = { }, action){
switch (action.type)
{
case EditEventActions.EVENT_SET_EDITING_EVENT:
return { ...state, event: action.payload }
default:
return state;
}
}
I fire the actions before redirecting to another page like this.
this.props.setEditingEvent(event);
this.props.history.push({
pathname : '/event/'+ event.id +'/edit'
});
In the new page, I render the component called, EditEventComponent.
This is the definition of the EditEventComponent
export class EditEventComponent extends React.Component{
constructor(props)
{
super(props);
alert(this.props.event.id)//cannot retrieve event here
}
render(){
return (
<h4>This is the Edit Event component</h4>
);
}
}
function mapStateToProps(state)
{
return {
event: state.editEvent.event
};
}
function matchDispatchToProps(dispatch)
{
return bindActionCreators({
}, dispatch);
}
const enhance = compose(withWidth(), withStyles(themeStyles, { withTheme: true }), connect(mapStateToProps, matchDispatchToProps))
export default enhance(EditEventComponent);
As you can see, inside the EditEventComponent I am trying to retrieve the event field of the state which is set in the previous page. But I cannot retrieve it.
My questions are
Is the state (of the redux store) reset after redirecting to the new page?
What is wrong with my code?
If what I am doing is not the right approach, what would be the best way to pass an object from one page to another in React Redux?
Here is my action
export const EVENT_SET_EDITING_EVENT = "(EVENT) SET EDITING EVENT";
export const setEditingEvent = (data) => ({
type: EVENT_SET_EDITING_EVENT,
payload: data
});
Here is the reducer
import * as EditEventActions from '../actions/edit.event.actions';
export default function (state = { }, action){
switch (action.type)
{
case EditEventActions.EVENT_SET_EDITING_EVENT:
return { ...state, event: action.payload }
default:
return state;
}
}
I expose the EventListComponent in this way as well.
const enhance = compose(withWidth(), withStyles(themeStyles, { withTheme: true }), connect(mapStateToProps, matchDispatchToProps))
export default enhance(EventListComponent);
You are probably not setting the type correctly in setEditingEvent action and the reducer returns the initial state since it doesn't hit EVENT_SET_EDITING_EVENT
I'm trying currently to pass the app.state contained to the Redux store in a React Component.
So far, this problem is still a deep mystery...
------> HERE THE GITHUB REPOSITORY OF MY CODE <------
Hope it will help to figure out what is wrong.
Abstract :
My problem is basically about mapStateToProps, is about link a Component to the state store, AFAIK the rest work very fine, but Something seems shortcut my this.props in React's Component, because either I use connect() or delete the mapStateToProps method, my Component stil display the initial state ..!
Redux resists me like an end-level's boss...
STATE OF PLAY
The provider with a store of react-redux: OK
Connect function pass to the props: OK
mapDispatchToProps works fine! So why the state fails to update the props since the connection seems well established?
I know my action is well mapped since when I delete the mapDispatch in the connect composition, the component then fails to trigger the corresponding action.
When console.log, the mapState receive effectively the store update but the Component stay blocked on initial state (tested with a "checkState" button on the component which returns the "store.getState().propertyTargeted"
HINTS :
when I delete the mapStateToProps in connect, my React.Component continue to receive the initialState,
so maybe there is an another source that overwrites my mapStateToProps, I seek for it currently
my this.props.state variable is called in the Component's constructor, maybe the constructor doesn't receive the store.updateState or something like that ? Another track to follow.
Here my combineReducer.js :
import { combineReducers } from "redux";
import {post} from "./status"
import {entry}from "./updateState";
// only one reducer active
const appReducer = combineReducers({
entry,
post
})
export default appReducer
Here my container.js :
const mapStateToProps = (state) => {
return { word: state.entry.word }
}
const mapDispatchToProps = {
postFile: postFileAction
}
const PostFileContainer = connect(mapStateToProps, mapDispatchToProps)(Component) ;
My postFile.js :
export const postFile = (word, base64Data) => dispatch => {
console.log("postFile httpRequest reached")
dispatch({
type: 'POST_WORD',
status: request
});
Axios.post("http://localhost:7500/api/files", {
"word": word,
"data": base64Data
}, {
"Content-Type": "multipart/form-data"
})
.then(res =>
dispatch({
type: 'POST_WORD',
status: success,
res
}))
.catch(err => {
dispatch({
type: 'POST_WORD',
status: error,
err
})
});
}
Here in my store.initialState :
initial state: {
"post": {},
"entry": {
"word": "initialWord"
}
}
the UPDATE_STATE_POSTWORD is provide by an other React component therefore dispatched to the store before that the bugging component trigger it own action with a updated word's entry.
Here my UPDATE_STATE_POSTWORD action snippet :
export const updateWord = word => {
return {
type: UPDATE_STATE_POSTWORD,
word
};
}
/// reducers.js part ///
postReducer.js :
export const post = (state ={}, action) => {
console.log("postStatus reached - reducer")
switch (action.status) {
case request:
console.log("Request start")
return state
case success:
switch (action.type) {
case POST_FILE:
console.log("request succeed: ", action.res)
var _id = action.res._id
// var word= action.res.word
return (Object.assign({}, state, {
_id
}))
case POST_WORD:
console.log("request succeed: ", action.res)
return (Object.assign({}, state, {
_id: ""
}))
default :
console.log(`default state on success case in
postStatusReducer`)
return state
}
case error:
console.log("request error: ", action.err)
return state
default:
return state
}
}
entryReducer.js :
const initialState = { word : "initialWord" }
export const updateStateReducer = (state= initialState, action) =>
{
switch (action.type) {
case UPDATE_STATE_POSTWORD:
var word = action.word
return (Object.assign({}, state, {
word
}))
default:
return state
}
}
Thanks
If you are using react-thunk, your action fn would receive dispatch and getState functions as arguments.
Running getState would give you actual state of the application. Recuired data would be passed to reducer and so on.
In your example RecordingAPI receives props that comes from redux only while initializing - in constructor.
You can fix your component by adding componentWillReceiveProps method
class RecordingAPI extends React.Component {
constructor(props) {
super(props);
...
this.state = {
word : this.props.word,
state: this.props
};
}
// new method that listens to props
componentWillReceiveProps (props) {
this.setState({
word: this.props.word,
state: this.props
});
}
checkState(e){
e.persist();
e.preventDefault();
e.stopPropagation();
console.dir(this.state.word)
console.dir(this.state.state)
}
render() {
...
return (
<div>
<button onClick={(e) => this.checkState(e)}> CheckState </button>
</div>
);
}
}
My current work-around is to import the store directly in my React Component then subscribe to the changes as it :
import {store} from "../App"
store.subscribe(() => {
// When state will be updated
// we will update local component state and force component to rerender
// with new data.
this.setState({
word: store.getState().entry.word // new entry.words at each update in the statge of the React.Component
});
});
ANSWER :
Assigning the store.state value to the Component's state constructor, the Component failed to update the state. So, referring to the store.state using this.props outside any assignment to the Component.state.property works like a charm*.
The trap is that storing a props in the props.constructor.state of the children works when you work only with React.js but this mechanism doesn't works for React-Redux then you have to stay the props outside any assignment in the props.constructor.state
I am just making a simple app to learn async with redux. I have gotten everything working, now I just want to display the actual state onto the web-page. Now, how do I actually access the store's state in the render method?
Here is my code (everything is in one page because I'm just learning):
const initialState = {
fetching: false,
fetched: false,
items: [],
error: null
}
const reducer = (state=initialState, action) => {
switch (action.type) {
case "REQUEST_PENDING": {
return {...state, fetching: true};
}
case "REQUEST_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
items: action.payload
}
}
case "REQUEST_REJECTED": {
return {...state, fetching: false, error: action.payload}
}
default:
return state;
}
};
const middleware = applyMiddleware(promise(), thunk, logger());
const store = createStore(reducer, middleware);
store.dispatch({
type: "REQUEST",
payload: fetch('http://localhost:8000/list').then((res)=>res.json())
});
store.dispatch({
type: "REQUEST",
payload: fetch('http://localhost:8000/list').then((res)=>res.json())
});
render(
<Provider store={store}>
<div>
{ this.props.items.map((item) => <p> {item.title} </p> )}
</div>
</Provider>,
document.getElementById('app')
);
So, in the render method of the state I want to list out all the item.title from the store.
Thanks
You should create separate component, which will be listening to state changes and updating on every state change:
import store from '../reducers/store';
class Items extends Component {
constructor(props) {
super(props);
this.state = {
items: [],
};
store.subscribe(() => {
// When state will be updated(in our case, when items will be fetched),
// we will update local component state and force component to rerender
// with new data.
this.setState({
items: store.getState().items;
});
});
}
render() {
return (
<div>
{this.state.items.map((item) => <p> {item.title} </p> )}
</div>
);
}
};
render(<Items />, document.getElementById('app'));
Import connect from react-redux and use it to connect the component with the state connect(mapStates,mapDispatch)(component)
import React from "react";
import { connect } from "react-redux";
const MyComponent = (props) => {
return (
<div>
<h1>{props.title}</h1>
</div>
);
}
}
Finally you need to map the states to the props to access them with this.props
const mapStateToProps = state => {
return {
title: state.title
};
};
export default connect(mapStateToProps)(MyComponent);
Only the states that you map will be accessible via props
Check out this answer: https://stackoverflow.com/a/36214059/4040563
For further reading : https://medium.com/#atomarranger/redux-mapstatetoprops-and-mapdispatchtoprops-shorthand-67d6cd78f132
All of the answers are from pre-hook era. You should use useSelector-hook to get the state from redux.
In your redux-reducer file or somewhere where you can import it easily:
import { useSelector } from 'react-redux'
export function useEmployees() {
return useSelector((state) => state.employees)
}
In your application code:
const { employees } = useEmployees()
More information on redux-hooks: https://react-redux.js.org/api/hooks to accomplish this goal.
You need to use Store.getState() to get current state of your Store.
For more information about getState() watch this short video.
You want to do more than just getState. You want to react to changes in the store.
If you aren't using react-redux, you can do this:
function rerender() {
const state = store.getState();
render(
<div>
{ state.items.map((item) => <p> {item.title} </p> )}
</div>,
document.getElementById('app')
);
}
// subscribe to store
store.subscribe(rerender);
// do initial render
rerender();
// dispatch more actions and view will update
But better is to use react-redux. In this case you use the Provider like you mentioned, but then use connect to connect your component to the store.
If you want to do some high-powered debugging, you can subscribe to every change of the state and pause the app to see what's going on in detail as follows.
store.js
store.subscribe( () => {
console.log('state\n', store.getState());
debugger;
});
Place that in the file where you do createStore.
To copy the state object from the console to the clipboard, follow these steps:
Right-click an object in Chrome's console and select Store as Global Variable from the context menu. It will return something like temp1 as the variable name.
Chrome also has a copy() method, so copy(temp1) in the console should copy that object to your clipboard.
https://stackoverflow.com/a/25140576
https://scottwhittaker.net/chrome-devtools/2016/02/29/chrome-devtools-copy-object.html
You can view the object in a json viewer like this one: http://jsonviewer.stack.hu/
You can compare two json objects here: http://www.jsondiff.com/
HACK SOLUTION: Example from my REAL project! Save Redux store objects to external JSON file.
STEP-1 import useStore first from react-redux and then getState() function is used to access store state.
STEP-2 area is the name of my slice in Redux store and areaName is state in that slice.
STEP-3 FiletoSave variable is used to export JSON file with data from store.
import { useStore } from "react-redux";
const exportJsonFileFromStore = () => {
const store = useStore();
const FileSaver = require('file-saver');
function exportData() {
const filename = 'filter_settings';
let prepareObject = { // It is used to make a JSON object
areaName:store.getState().area.areaName ,
}
const fileToSave = new Blob([JSON.stringify(prepareObject)], {
type: 'application/json'
});
// this will save file
FileSaver.saveAs(fileToSave, filename);
}
return (
<button onClick={(event: any) => exportData()}>Click me to download!</button>
)
}
import { ReactReduxContext } from 'react-redux';
var store = useContext(ReactReduxContext).store.getState();
console.log(store);
https://react-redux.js.org/using-react-redux/accessing-store#using-reactreduxcontext-directly