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

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);

Related

React - Unable to set state from the response on initial render

This is the response from redux store :
{
"newsletter": true,
"orderConfirmation": true,
"shippingInformation": true,
"orderEnquiryConfirmation": true,
}
This is the jsx file, where am trying to set state. The idea is setting the state from the response and add an onChange handle to each checkboxes.
But currently am receiving a correct response but I tried to set state in didUpdate, DidMount but no luck. I want to know the correct place to set state on initial render of the component.
import React from 'react';
import Component from '../../assets/js/app/component.jsx';
import { connect } from 'react-redux';
import * as actionCreators from '../../assets/js/app/some/actions';
import { bindActionCreators } from 'redux';
import Checkbox from '../checkbox/checkbox.jsx';
const mapStateToProps = (state, ownProps) => {
return {
...state.emailSubscriptions
}
}
const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators(actionCreators, dispatch)
}
}
#connect(mapStateToProps, mapDispatchToProps)
class EmailSubscriptions extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
this.props.actions.getEmailSubscriptions();
this.setState({ // Not setting state
notifications: [
newsletter = this.props.newsletter,
orderConfirmation = this.props.orderConfirmation,
shippingInformation = this.props.shippingInformation,
orderEnquiryConfirmation = this.props.orderEnquiryConfirmation
]
})
}
render() {
return (
<div>
Here I want to use loop through state to create checkboxes
{this.state.notifications&& this.state.notifications.map((item, index) => {
const checkboxProps = {
id: 'subscription' + index,
name: 'subscription',
checked: item.subscription ? true : false,
onChange: (e)=>{ return this.onChange(e, index)},
};
return <div key={index}>
<Checkbox {...checkboxProps} />
</div>
</div>
)
}
}
export default EmailSubscriptions;
I hope getEmailSubscriptions is an async action, so your setState won't update the state as you intended. add componentDidUpdate hook in your class component and your setState statement within an if statement that has an expression checking your props current and prev value.
You can do something like this.
componentDidMount() {
this.props.actions.getEmailSubscriptions();
}
componentDidUpdate(prevProps, prevState, snapshot){
if(this.props.<prop_name> != prevProps.<prop_name>){
this.setState({
notifications: [
newsletter = this.props.newsletter,
orderConfirmation = this.props.orderConfirmation,
shippingInformation = this.props.shippingInformation,
orderEnquiryConfirmation = this.props.orderEnquiryConfirmation
]
})
}
}

ReactJS not re-rendering after context changed

I have a basic app here and I'm trying to config React Context properly but it isn't working. My goal is to render PlayScreen with the content of currentStage inside the React Context. Game changes the context but App keeps rendering PlayScreen with the "welcome" string, instead of "won" or "lost".
Also, I know that gameContext.js is for autocompletion but I added "welcome" there to have a first default state. Somehow I couldn't find a way to set up that very first "welcome" context when App is rendered for the first time.
I tried feeding PlayScreen with the context itself and didn't work, and now I tried setting a state with it but it doesn't work either (even when using useEffect and having the context as a dependency).
So I have two questions, what am I doing wrong? and, my way to set up the "welcome" default state is wrong? If so, how can I do it? Thanks.
gameContext.js
import React from 'react';
const GameContext = React.createContext({
currentStage: 'welcome',
playerStage: (stage) => {},
});
export default GameContext;
GameProvider.jsx
import React, { useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';
import GameContext from './gameContext';
const defaultState = {
currentStage: '',
};
const gameReducer = (state, action) => {
if (action.type === 'STAGE') {
return {
currentStage: action.playerStage,
};
}
return defaultState;
};
const GameProvider = ({ children }) => {
const [gameState, dispatchGameAction] = useReducer(gameReducer, defaultState);
const playerStageHandler = (playerStage) => {
dispatchGameAction({
type: 'STAGE',
playerStage,
});
};
const gameContext = useMemo(
() => ({
currentStage: gameState.currentStage,
playerStage: playerStageHandler,
}),
[gameState.currentStage]
);
return (
<GameContext.Provider value={gameContext}>{children}</GameContext.Provider>
);
};
GameProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export default GameProvider;
App.jsx
import React, { useContext, useState, useEffect } from 'react';
import GameProvider from './store/GameProvider';
import GameContext from './store/gameContext';
import PlayScreen from './components/PlayScreen';
import Settings from './components/Settings';
import Game from './components/Game';
const App = () => {
const gameContext = useContext(GameContext);
const [stage, setStage] = useState(gameContext.currentStage);
useEffect(() => {
setStage(gameContext.currentStage);
}, [gameContext]);
const [currentScreen, setCurrentScreen] = useState({
playScreen: true,
settings: false,
game: false,
});
const changeScreenHandler = (newScreen) => {
switch (newScreen) {
case 'playScreen':
setCurrentScreen({
playScreen: true,
settings: false,
game: false,
});
break;
case 'settings':
setCurrentScreen({
playScreen: false,
settings: true,
game: false,
});
break;
case 'game':
setCurrentScreen({
playScreen: false,
settings: false,
game: true,
});
break;
default:
break;
}
};
return (
<GameProvider>
{currentScreen.playScreen && (
<PlayScreen stage={stage} onChangeScreen={changeScreenHandler} />
)}
{currentScreen.settings && (
<Settings onChangeScreen={changeScreenHandler} />
)}
{currentScreen.game && <Game onChangeScreen={changeScreenHandler} />}
</GameProvider>
);
};
export default App;
PlayScreen.jsx
import PropTypes from 'prop-types';
const PlayScreen = ({ stage, onChangeScreen }) => {
const clickHandler = () => {
onChangeScreen('settings');
};
return (
<div>
<h1>{stage}</h1>
<button type="button" onClick={clickHandler}>
Go
</button>
</div>
);
};
PlayScreen.propTypes = {
stage: PropTypes.string.isRequired,
onChangeScreen: PropTypes.func.isRequired,
};
export default PlayScreen;
Game.jsx
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import GameContext from '../store/gameContext';
const Game = ({ onChangeScreen }) => {
const gameContext = useContext(GameContext);
const wonHandler = () => {
onChangeScreen('playScreen');
gameContext.playerStage('won');
};
const lostHandler = () => {
onChangeScreen('playScreen');
gameContext.playerStage('lost');
};
return (
<div>
<h1>GAME RUNNING</h1>
<button type="button" onClick={wonHandler}>
won
</button>
<button type="button" onClick={lostHandler}>
lost
</button>
</div>
);
};
Game.propTypes = {
onChangeScreen: PropTypes.func.isRequired,
};
export default Game;
You are consuming the GameContext above the GameProvider. The context shouldn't be available because App isn't being provided the context by the GameProvider.
Try moving everything underneath GameProvider into its own component and consume the context there.

A question about structure of class component in React

As we know, the structure of a class component can be simplified as the following:
// Blank 1
class Books extends Component {
// Blank 2
render(){
// Blank 3
return()
}
export default Books;
So just for example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { updateFilters } from '../../../services/filters/actions';
import Checkbox from '../../Checkbox';
import GithubStarButton from '../../github/StarButton';
import './style.scss';
const availableSizes = ['XS', 'S', 'M', 'ML', 'L', 'XL', 'XXL'];
class Filter extends Component {
static propTypes = {
updateFilters: PropTypes.func.isRequired,
filters: PropTypes.array
};
componentWillMount() {
this.selectedCheckboxes = new Set();
}
toggleCheckbox = label => {
if (this.selectedCheckboxes.has(label)) {
this.selectedCheckboxes.delete(label);
} else {
this.selectedCheckboxes.add(label);
}
this.props.updateFilters(Array.from(this.selectedCheckboxes));
};
createCheckbox = label => (
<Checkbox
classes="filters-available-size"
label={label}
handleCheckboxChange={this.toggleCheckbox}
key={label}
/>
);
createCheckboxes = () => availableSizes.map(this.createCheckbox);
render() {
return (
<div className="filters">
<h4 className="title">Sizes:</h4>
{this.createCheckboxes()}
<GithubStarButton />
</div>
);
}
}
const mapStateToProps = state => ({
filters: state.filters.items
});
export default connect(
mapStateToProps,
{ updateFilters }
)(Filter);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchProducts } from '../../services/shelf/actions';
import { addProduct } from '../../services/cart/actions';
import Product from './Product';
import Filter from './Filter';
import ShelfHeader from './ShelfHeader';
import Clearfix from '../Clearfix';
import Spinner from '../Spinner';
import './style.scss';
class Shelf extends Component {
static propTypes = {
fetchProducts: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
addProduct: PropTypes.func.isRequired,
filters: PropTypes.array,
sort: PropTypes.string
};
state = {
loading: false
};
componentWillMount() {
const { filters, sort } = this.props;
this.handleFetchProducts(filters, sort);
}
componentWillReceiveProps(nextProps) {
const { filters: nextFilters, sort: nextSort } = nextProps;
if (nextFilters !== this.props.filters) {
this.handleFetchProducts(nextFilters, undefined);
}
if (nextSort !== this.props.sort) {
this.handleFetchProducts(undefined, nextSort);
}
}
handleFetchProducts = (
filters = this.props.filters,
sort = this.props.sort
) => {
this.setState({ loading: true });
this.props.fetchProducts(filters, sort, () => {
this.setState({ loading: false });
});
};
render() {
const { products } = this.props;
const p = products.map(p => {
return (
<Product product={p} addProduct={this.props.addProduct} key=
{p.id} />
);
});
return (
<React.Fragment>
{this.state.loading && <Spinner />}
<Filter />
<div className="shelf-container">
<ShelfHeader productsLength={products.length} />
{p}
<Clearfix />
</div>
<Clearfix />
</React.Fragment>
);
}
}
const mapStateToProps = state => ({
products: state.shelf.products,
filters: state.filters.items,
sort: state.sort.type
});
export default connect(
mapStateToProps,
{ fetchProducts, addProduct }
)(Shelf);
Except for state and life cycle methods, sometimes we define other types of attributes and functions in Blank 1, sometimes in Blank 2, sometimes in Blank 3. So I am wondering when we are going to define attributes and functions, which part should we choose? Is there a convention or something like that?
Block 1 is for defining variables and functions which are not depended on component ,these are general variables and functions which could be used in the component and can even be exported in another files.
Block 2 is for defining component specific variables and methods, define lifecycle methods.variables and methods defined in block 2 could be accessed using this keyword.
Block 3 is used when we want to execute certain piece of code,every time when render method is executed.Apart from initial render, render method is executed every time when setState is performed,so avoid writing code in block 3 as it's excessive.
Hope this helps,
Cheers !!

Redux Store and nested JSON from Axios API

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!

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}`);
}

Resources