Redux Store and nested JSON from Axios API - reactjs

I tried every possible variation of this code, but I don't really manage to get whatever the API fetched into my data store. I am absolutely stuck and would appreciate some help.
I think I just don't get the essential part of this construct and I would really like to understand how it works properly.
The data looks like this - it's basically a simple JSON (from a django restframework API) with some nested elements:
EDIT 2 (changed JSON to screenshot of axios API/ Redux action)
My Redux action - works perfectly fine. console.log pulls exactly the data from above (with correct inputs) :
// ./action/plan.js
import axios from 'axios';
export function fetchBudgets(){
return function(dispatch){
axios.get("/api/budgets/")
.then((response) => {
console.log(response)
dispatch({ type: "FETCH_BUDGETS", budgets: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_DATA_REJECTED", budgets: err})
})
}
}
So until now, everything seems fine. The problems starts with the reducer - as I am not sure how to model the reducer to use the nested data.
My reducer:
// ./reducer/plan.js
const initialState = {}
export default function budgets(state=initialState, action) {
switch (action.type) {
case 'FETCH_BUDGETS':
console.log(action)
return {
...state,
id: action.budgets.id,
value_jan: action.budgets.value_jan,
value_feb: action.budgets.value_feb,
value_mar: action.budgets.value_mar,
value_apr: action.budgets.value_apr,
value_may: action.budgets.value_may,
value_jun: action.budgets.value_jun,
value_jul: action.budgets.value_jul,
value_aug: action.budgets.value_aug,
value_sep: action.budgets.value_sep,
value_oct: action.budgets.value_oct,
value_nov: action.budgets.value_nov,
value_dec: action.budgets.value_dec,
p_version: action.budgets.p_version,
entry_time: action.budgets.entry_time,
campaign: {
...state.campaign, ...action.budgets.campaign
},
segment: {
...state.segment, ...action.budgets.segment
},
touch_point: {
...state.touch_point, ...action.budgets.touch_point
},
year: {
...state.year, ...action.budgets.year
},
user: {
...state.user, ...action.budgets.user
}
}
default:
return state
}
}
I already cannot display data in here - so this.props.fetchBudgets() doesn't seem to fetch any data.
My .jsx App
//./container/PlanContainer.jsx
import React, { Component } from 'react';
import {connect} from 'react-redux';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
import 'jquery';
import 'popper.js'
import 'bootstrap';
import 'underscore'
import _ from 'lodash'
import {plan} from "../actions";
const columns = [
{ dataField: 'id', text: 'ID', hidden: true},
{ dataField: 'year', text: 'Year', editable: false},
{ dataField: 'segment', text: 'Segment', editable: false},
{ dataField: 'campaign.name',text: 'Campaign', editable: false},
{ dataField: 'touch_point',text: 'Touchpoint', editable: false},
{ dataField: 'value_jan',text: 'Jan'},
{ dataField: 'value_feb',text: 'Feb'},
{ dataField: 'value_mar',text: 'Mar'},
{ dataField: 'value_apr',text: 'Apr'},
{ dataField: 'value_may',text: 'May'},
{ dataField: 'value_jun',text: 'Jun'},
{ dataField: 'value_jul',text: 'Jul'},
{ dataField: 'value_aug',text: 'Aug'},
{ dataField: 'value_sep',text: 'Sep'},
{ dataField: 'value_oct',text: 'Oct'},
{ dataField: 'value_nov',text: 'Nov'},
{ dataField: 'value_dec',text: 'Dec'},
{ dataField: 'user',text: 'User'},
];
const RemoteCellEdit = (props) => {
const { columns, data, keyField } = props
const cellEdit = {
mode: 'click',
errorMessage: props.errorMessage,
blurToSave: true
};
return (
<div>
<BootstrapTable
remote={ { cellEdit: true } }
keyField = { keyField }
data={ data }
columns={ columns }
/>
</div>
);
};
class PlanContainer extends React.Component {
componentDidMount() {
this.props.fetchBudgets();
console.log(this.props.fetchBudgets())
}
render() {
return (
<div>
<RemoteCellEdit
data={ this.props.budgets }
columns = { columns }
keyField = 'id'
/>
</div>
);
}
}
const mapStateToProps = state => {
return {
budgets: state.budgets,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchBudgets: () => {
dispatch(plan.fetchBudgets());
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PlanContainer);
Finally, my store - according to the console.log nothing is beeing passed:
// .Planning.jsx
import React from "react"
import { hot } from 'react-hot-loader'
import { render } from "react-dom"
import {
createStore,
compose,
applyMiddleware,
combineReducers,
} from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"
import PlanContainer from "./containers/PlanContainer"
import reducerApp from "./reducers";
import Sidebar from "./components/Sidebar"
import axios from 'axios';
import axiosMiddleware from 'redux-axios-middleware';
let store = createStore(reducerApp, applyMiddleware(thunk, axiosMiddleware(axios)));
console.log(store)
class Planning extends React.Component {
render() {
return (
<Sidebar>
<Provider store={store}>
<PlanContainer />
</Provider>
</Sidebar>
)
}
}
render(<Planning />, document.getElementById('Planning'))
Again, I would appreciate as I've been stuck on this issue for quite some time and I really want to understand how to do this properly.
Edit:
Here's a screenshot of my browser: 1st element is the store, second in the .jsx app, 3rd of the action (that looks perfectly fine) and 4th of the action in the reducer.

PlanContainer is messed up. Here's how:
componentDidMount() {
this.budgets = this.props.fetchBudgets();
}
this.budgets is pointing to the value returned by this.props.fetchBudgets() which, in this case, is a Promise, and not the actual data.
state = {
data: this.budgets
};
state now holds the promise, not the data.
render() {
return (
<div>
<RemoteCellEdit
data={ this.state.data }
...
}
So data here is not the actual data but the promise.
The confusion is happening because you are mixing redux state with react state. Use one or the other, not both (there are expcetions to this but not in this particular scenario).
There are some more issues with PlanContainer which are not clear as to whether they are real issues, or just a result of code ommission in OP.
See annotations below:
class PlanContainer extends React.Component {
componentDidMount() {
this.props.fetchBudgets();
}
constructor(props) {
... // removed for brevity, use the same code as you have right now
}
render() {
return (
<div>
<RemoteCellEdit
data={ this.props.budgets}
columns = { this.columns }
keyField = 'id'
errorMessage={ /* should come from props.data or similar - it's not in state */ }
/>
<tbody>
{this.props.budgets} /* not sure what this is for - I assumed RemoteCellEdit is the one rendering the data */
</tbody>
</div>
);
}
}
Fixing these should set you on the correct course. Good luck!

Related

How do i manage custom props like 'isInnerPage' within router reducer?

I use connected-react-router and have some types of pages: main, inner, auth, admin. Depends of current page type I render certain components. Now it works in this way: there are 4 props in my config reducer with bool values: isMainPage, isInnerPage, isAdminPage, isAuthPage, and only one of them could be true in a time. They changes any time I change location (by executing certain action in componentDidMount). So, I want to deal with connected-react-router and, if possible, pass these props from config reducer to the router. Then, if possible, I want to execute an action that will define current page type and set it and then I would get this val in components. Is it all possible? Sorry for this explanation - I'm just studying.
I would provide some code but I don't know which part could be helpful - ask for one, please
config reducer:
import {
SET_VIEW_MODE,
SET_AUTH_PAGE,
SET_MAIN_PAGE,
SET_INNER_PAGE,
SET_ADMIN_PAGE,
...
} from '../constants';
...
case SET_AUTH_PAGE:
return {
...state,
isAuthPage: true,
isMainPage: false,
isInnerPage: false,
isAdminPage: false
};
case SET_MAIN_PAGE:
return {
...state,
isAuthPage: false,
isMainPage: true,
isInnerPage: false,
isAdminPage: false
};
case SET_INNER_PAGE:
return {
...state,
isAuthPage: false,
isMainPage: false,
isInnerPage: true,
isAdminPage: false
};
case SET_ADMIN_PAGE:
return {
...state,
isAuthPage: false,
isMainPage: false,
isInnerPage: false,
isAdminPage: true
};
config actions:
...imports;
export const actionSetViewMode = () => ({ type: SET_VIEW_MODE });
export const actionSetAuthPage = () => ({ type: SET_AUTH_PAGE });
export const actionSetMainPage = () => ({ type: SET_MAIN_PAGE });
export const actionSetInnerPage = () => ({ type: SET_INNER_PAGE });
expample of component that sets any type:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actionSetMainPage } from '../actions/configActions';
...
class MainPage extends Component {
componentDidMount() {
this.props.actionSetMainPage();
}
render() {
return (
<>
...
</>
)
}
}
export default connect(null, {
actionSetMainPage,
})(MainPage);
example of component that renders any depends of page type:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import Subaction from '../components/header/Subaction';
import Back from '../components/header/Back';
import Menu from '../components/header/Menu';
import Title from '../components/header/Title';
import Logo from '../components/header/Logo';
import Notification from '../components/header/Notification';
class Header extends Component {
render() {
const { isAdaptive, isAuthPage, isMainPage, isInnerPage, title } = this.props.config;
return (
!isAuthPage &&
<header>
<div className="h-inner">
...
{
isMainPage &&
<Logo></Logo>
}
<Notification></Notification>
...
</div>
</header>
)
}
}
export default connect(state => ({
config: state.config
}), {})(Header);

Create redux reducer to change state nested object value

I am new to React-Redux and i am struggling with a proof-of-concept project (list of movies) in order to learn the basics of redux. I am quite good at React, but Redux is another deal...
So, in my repo and specifically in Smart Component MoviesList.jsx (https://bitbucket.org/vforvaios/react-movie-app/src/05a6241eff3a1896eca91bb1800e8e017f8b569a/src/MoviesList.jsx?at=feature%2Fadd_redux&fileviewer=file-view-default) i am wondering how to dispatch the correct action to change the rating (increment, decrement) in each SingleMovieItem (https://bitbucket.org/vforvaios/react-movie-app/src/05a6241eff3a1896eca91bb1800e8e017f8b569a/src/SingleMovieItem.jsx?at=feature%2Fadd_redux&fileviewer=file-view-default) and what would be the correct reducer to achieve that. Could anyone help me with this?
In other words, to show a real example of what i mean...
Lets say that there is a main App and we make use of the
<Provider store={store}>
<App />
</Provider>
And then the App contains the following:
import React, { Component } from 'react';
import MoviesList from './MoviesList'
class App extends Component {
render() {
return (
<div>
<MoviesList />
</div>
)
}
}
export default App;
And then, the MovieList.jsd contains the following:
import React, { Component } from 'react';
import { addRating } from './actions';
import SingleMovieItem from './SingleMovieItem';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
allMovies: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: (id) => dispatch(addRating(id))
};
};
class MovieList extends Component {
constructor(props) {
super(props);
}
handleIncrement = id => {
}
render() {
return (
<div>
<ul className="moviesList">
{this.props.allMovies.movies.map(movie => {
return (
<SingleMovieItem
key={movie.id}
movie={movie}
onIncrement={this.handleIncrement(movie.id)} />
);
})}
</ul>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MovieList);
What would be the reducer to increment rating of each singlemovie
if initialState is
const initialState = {
movies: [
{
id: "1",
title: "Harry Potter 1",
url: "harry-1.jpg",
rating: "2"
},
{
id: "2",
title: "Harry Potter 2",
url: "harry-2.jpg",
rating: "3"
}
]
};
Thanks in advance.

Tracker.autorun not working inside componentDidMount of react

Tracker.autorun not working inside componentDidMount of react when I specify the projection (fields) for output. But the same works when I dont have any projection on mongo query.
This works:
Meteor.subscribe('quotes');
this.quotesTracker = Tracker.autorun(() => {
const quotes = Quotes.find(
{instrument_token: 12374274},
{
sort: {timestamp: 1},
limit: 5000
}
).fetch();
This doesnt work
Meteor.subscribe('quotes');
this.quotesTracker =Tracker.autorun(() => {
const quotes = Quotes.find(
{instrument_token: 12374274},
{
fields: {
last_price: 1,
timestamp: 1,
},
sort: {timestamp: 1},
limit: 5000
}
).fetch();
What am I missing here?
I don't think Meteor's tracker works well with ReactJS, as their mechanism of re-render is different.
You might want to use this package.
https://github.com/meteor/react-packages/tree/devel/packages/react-meteor-data
You can use it like so.
import { Meteor } from 'meteor/meteor';
import { mount } from 'react-mounter';
import { withTracker } from 'meteor/react-meteor-data';
import { IndexPage } from "./index-page";
FlowRouter.route('/', {
action: () => {
mount(IndexPageContainer, {});
}
});
export const IndexPageContainer = withTracker(() => {
Meteor.subscribe('whatever');
return {
Meteor: {
collection: {
whatever: Whatever.find().fetch()
},
user: Meteor.user(),
userId: Meteor.userId(),
status: Meteor.status(),
loggingIn: Meteor.loggingIn()
}
};
})(IndexPage);
Where IndexPage is your actual component.
You can then access the db by this.props.Meteor.collection.whatever.find()

Redux-Observable and Rxjs not capturing the first event but captures the second event

I've been working on a chat-bot app that's written in Typescript and uses Redux-Observable with rxjs. I tried to send a click event from the Shell.tsx component to my redux store which first gets intercepted by rxjs Epic and then gets sent off to redux store. But my redux store's return state does not effect the change that's supposed to be made on a click event, however it does return the expected result on a second click.
This is part of my Shell.tsx that contains the relevant component methods that fires off the click event as an action to the store:
import * as React from 'react';
import { ChatState, FormatState } from './Store';
import { User } from 'botframework-directlinejs';
import { classList } from './Chat';
import { Dispatch, connect } from 'react-redux';
import { Strings } from './Strings';
import { createStore, ChatActions, sendMessage } from './Store';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/observable/merge';
interface Props {
inputText: string,
strings: Strings,
isActive: boolean,
onChangeText: (inputText: string) => void,
sendMessage: (inputText: string) => void
checkActive: (isChatActive: boolean) => void
}
private handleChatClick(isChatActive) {
this.store.dispatch({type: 'Chat_Activate', isChatActive: true})
setTimeout(() => {
this.store.subscribe(() => {
this.isActive = this.store.getState().shell.isChatActive
})
console.log(this.isActive)
}, 3000)
if (this.isActive) {
this.forceUpdate()
}
// this.props.checkActive(true)
}
render() {
//other code
return (
<div className={ className }>
<div className="wc-textbox">
{
console.log('chat rendered')
}
<input
type="text"
className="wc-shellinput"
ref={ input => this.textInput = input }
value={ this.props.inputText }
onChange={ _ => this.props.onChangeText(this.textInput.value) }
onKeyPress={ e => this.onKeyPress(e) }
placeholder={ placeholder }
aria-label={ this.props.inputText ? null : placeholder }
aria-live="polite"
// onFocus={ this.props.handleChatClick}
onClick={() => {
this.handleChatClick(true)
}}
/>
</div>
</div>
);
}
export const Shell = connect(
(state, ownProps) => {
return {
inputText: state.shell.input,
strings: state.format.strings,
isActive: state.shell.isChatActive,
// only used to create helper functions below
locale: state.format.locale,
user: state.connection.user
}
}
, {
// passed down to ShellContainer
onChangeText: (input: string) => ({ type: 'Update_Input', input, source: "text" } as ChatActions),
// only used to create helper functions below
sendMessage
}, (stateProps: any, dispatchProps: any, ownProps: any): Props => ({
// from stateProps
inputText: stateProps.inputText,
strings: stateProps.strings,
isActive: stateProps.isActive,
// from dispatchProps
onChangeText: dispatchProps.onChangeText,
checkActive: dispatchProps.checkActive,
// helper functions
sendMessage: (text: string) => dispatchProps.sendMessage(text, stateProps.user, stateProps.locale),
}), {
withRef: true
}
)(ShellContainer);
This is my the part of my Store.ts code:
export interface ShellState {
sendTyping: boolean
input: string,
isChatActive: boolean,
isPinging: boolean
}
export const setChatToActive = (isChatActive: boolean) => ({
type: 'Chat_Activate',
isChatActive: isChatActive,
} as ChatActions);
export const ping = (isPinging: boolean) => ({
type: 'Is_Pinging',
isPinging: isPinging
} as ChatActions)
export type ShellAction = {
type: 'Update_Input',
input: string
source: "text"
} | {
type: 'Card_Action_Clicked'
} | {
type: 'Set_Send_Typing',
sendTyping: boolean
} | {
type: 'Send_Message',
activity: Activity
} | {
type: 'Chat_Activate',
isChatActive: boolean
} | {
type: 'Is_Pinging',
isPinging: boolean
}
export const shell: Reducer<ShellState> = (
state: ShellState = {
input: '',
sendTyping: false,
isChatActive: false,
isPinging: false
},
action: ShellAction
) => {
console.log(state)
switch (action.type) {
case 'Update_Input':
return {
... state,
input: action.input
};
case 'Send_Message':
return {
... state,
input: ''
};
case 'Chat_Activate':
const newState = {
...state,
isChatActive: action.isChatActive
}
return newState
case 'Set_Send_Typing':
return {
... state,
sendTyping: action.sendTyping
};
case 'Card_Action_Clicked':
return {
... state
};
case 'Is_Pinging':
const newPing = {
... state,
isPinging: action.isPinging
}
return newPing;
default:
return state;
}
}
// 2. Epics
//************************************************************************
//Import modules
import { applyMiddleware } from 'redux';
import { Epic } from 'redux-observable';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/merge';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/mapTo';
import 'rxjs/add/operator/throttleTime';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/observable/bindCallback';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/of';
//************************************************************************
//Asynchronously send messages
const sendMessageEpic: Epic<ChatActions, ChatState> = (action$, store) =>
action$.ofType('Send_Message')
.map(action => {
const state = store.getState();
const clientActivityId = state.history.clientActivityBase + (state.history.clientActivityCounter - 1);
return ({ type: 'Send_Message_Try', clientActivityId } as HistoryAction);
});
const setChatToActiveEpic: Epic<ChatActions, ChatState> = (action$, store) =>
action$.ofType('Chat_Activate')
.mapTo({type: 'Chat_Activate', isChatActive: true} as ChatActions)
.takeUntil(
action$.ofType('Chat_Activate')
)
const pingEpic: Epic<ChatActions, ChatState> = (action$, store) =>
action$.ofType('Is_Pinging')
.mapTo({type: 'Is_Pinging', isPinging: true} as ChatActions)
.takeUntil(
action$.ofType('Is_Pinging')
)
// 3. Now we put it all together into a store with middleware
import { Store, createStore as reduxCreateStore, combineReducers } from 'redux';
import { combineEpics, createEpicMiddleware } from 'redux-observable';
export const createStore = () =>
reduxCreateStore(
combineReducers<ChatState>({
shell,
format,
size,
connection,
history
}),
applyMiddleware(createEpicMiddleware(combineEpics(
updateSelectedActivityEpic,
sendMessageEpic,
trySendMessageEpic,
retrySendMessageEpic,
showTypingEpic,
sendTypingEpic,
setChatToActiveEpic,
pingEpic
)))
);
export type ChatStore = Store<ChatState>;
In a nutshell I want to produce a console log of true when I click on the input element in my Shell.tsx. But the output is always false when I click on the input for the first time, works when I click it again.
I don't see anything immediately wrong in your code that would cause state to not be changed the first time that the action is dispatched. The logging is a little confusing though, so it might be causing you to think the code is behaving differently than it is?
From your screenshot, I can see that the first console.log statement is from store.ts line 84 (if you look all the way to the right, you can see that), and the second console.log is coming from the component.
In your store.ts file, you have a console.log statement at the top of the reducer. Because this logging is at the top of the reducer, it will always display the previous state, not the updated state.
export const shell: Reducer<ShellState> = (
state: ShellState = {
input: '',
sendTyping: false,
isChatActive: false,
isPinging: false
},
action: ShellAction
) => {
console.log(state)
Another thing that may be confusing you is that you are listening to store changes AFTER you've changed the store.
// this updates the store
this.store.dispatch({type: 'Chat_Activate', isChatActive: true})
// 3 seconds later, you're listening for store changes, but it's already changed
setTimeout(() => {
this.store.subscribe(() => {
this.isActive = this.store.getState().shell.isChatActive
})
// then you console.log this.isActive which might be false because that's the initial state in the reducer
console.log(this.isActive)
}, 3000)
You should subscribe to the store BEFORE you dispatch the action, if you want to see it change.
this.store.subscribe(() => {
this.isActive = this.store.getState().shell.isChatActive;
});
this.store.dispatch({type: 'Chat_Activate', isChatActive: true});
Alternatively, you can use Connected components from React-Redux. They will listen for store changes and update components automatically for you, so that you don't have to subscribe to the store for changes yourself.
React Redux github: https://github.com/reactjs/react-redux
Intro Blog Post: https://www.sohamkamani.com/blog/2017/03/31/react-redux-connect-explained/
If you want to see the stores value update immediately with console.log, you could do something like
private handleChatClick(isChatActive) {
console.log(`before click/chat active event: ${this.store.getState().shell.isChatActive}`);
this.store.dispatch({type: 'Chat_Activate', isChatActive: true})
console.log(`after click/chat active event: ${this.store.getState().shell.isChatActive}`);
}

Dispatch redux action when clicking cell in react-table

How can I dispatch the action returned by the 'editUser' action creator when the client clicks on the Edit link for a given user in the Users table?
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import ReactTable from 'react-table';
import { editUser } from '../actions/users'
class Users extends Component {
render() {
return (
<div>
<ReactTable className="-striped "
data={this.props.users}
columns={columns}
/>
</div>
);
}
}
const columns = [{
id: 1,
header: 'Namn',
}, {
id: 2,
header: '',
accessor: 'id',
render: (row) => (Edit ),
}
];
function mapStateToProps(state) {
return {
users: state.users.users,
}
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({ editUser }, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(Users);
My action creator:
export function editUser(user) {
return {
type: 'EDIT_USER',
user
}
}
how so I looked at this was that the data property needed to have the editUser action, so I added this to the data property.
const { users, editUser } = this.props
const newUsers = users.map(r => {
return (
{...r, editUser}
)
})
then instead of data={this.props.users} use data={ newUsers }
that way, inside the render method of columns, row.editUser is available as an action.
Take care of the click event
<ReactTable
getTdProps={(state, rowInfo, column, instance) => {
return {
onClick: e => {
editUser(rowInfo.userId)
}
}
}}
/>
You should probably tweak your action a bit too, usually you just pass the minimal data needed. In this case, user id.

Resources