react-redux action to reducer relationship - react-redux

So I am learning React-Redux. I am trying to find a way to load a value from the database first on load instead of just starting from 0. I have written an action that will hit an endpoint on my node file and pull from my mongo database. This works. However the action never seems to reach the reducer to actually update the store. Can someone explain to me the right way to make sure this action is store aware.
Here is the code for the action. Note the console.log with the number prints out what I want. I just never see the logs in the reducer that it was even ever reached.
export function setFooClicks(){
console.log("in the set foo clicks action")
var number = 0;
//return function(dispatch){
//console.log("in the return function")
return axios.get('/clicks').then(result => {
number = result.data
console.log("The number of clicks is", number)
//return number
return{
type: types.SETFOOCLICKS,
totalFoo: result.data
}
}).catch((error) => {
return console.log(error);
})
//}
}
I am trying to grab it in the top level container at the moment so here is the code for that.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Foo from '../components/Foo'
import { incrementFoo, setFooClicks } from '../actions'
class FooContainer extends Component {
componentDidMount(){
setFooClicks();
}
render() {
return (
<div>
<Foo incrementFooAction={() => this.props.incrementFoo()} totalFoo={this.props.totalFoo}/>
</div>
)
}
}
function mapStateToProps(state) {
return {
totalFoo: state.foo.totalFoo
}
}
export default connect(mapStateToProps, { incrementFoo,setFooClicks })(FooContainer)
The normal incrementFoo action works but trying to add the setFooClicks action into the component as well doesn't. Lastly here is the code to the reducer funciton. All I did was add a case to the switch block.
export default function incrementFoo(state = initialState, action) {
console.log("I am in the foo reducer")
console.log(action.type)
switch (action.type) {
case INCREMENT:
console.log(state.totalFoo);
return {
...state,
totalFoo: state.totalFoo + 1
}
case SETFOOCLICKS:
console.log("in the SETFOOCLICKS reducer")
return{
...state,
totalFoo: action.totalFoo
}
default:
return state
}
}

Related

Return EventEmitter as Observable in Nest.js

EventEmitter in Nestjs is wrapper around EventEmitter2 module. I whant that Server-Sent Events return Observable with EE.
import { Controller, Post, Body, Sse } from '#nestjs/common';
import { fromEvent } from 'rxjs';
import { EventEmitter2 } from '#nestjs/event-emitter';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
#Controller('orders')
export class OrdersController {
constructor(private ordersService: OrdersService,
private eventEmitter2: EventEmitter2) {}
#Post()
createOrder(#Body() createOrderDto: CreateOrderDto) {
// save `Order` in Mongo
const newOrder = this.ordersService.save(createOrderDto);
// emit event with new order
this.eventEmitter2.emit('order.created', newOrder);
return newOrder;
}
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
}
But after subscribtion to this source from browser i've getting only errors
this.eventSource = new EventSource('http://localhost:3000/api/v1/orders/newOrders');
this.eventSource.addEventListener('open', (o) => {
console.log("The connection has been established.");
});
this.eventSource.addEventListener('error', (e) => {
console.log("Some erorro has happened");
console.log(e);
});
this.eventSource.addEventListener('message', (m) => {
const newOder = JSON.parse(m.data);
console.log(newOder);
});
It's quite likely that you forgot to format the event in the right way.
For SSE to work internally, each chunk needs to be a string of such format: data: <your_message>\n\n - whitespaces do matter here. See MDN reference.
With Nest.js, you don't need to create such message manually - you just need to return a JSON in the right structure.
So in your example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
would have to be adjusted to, for example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created')
.pipe(map((_) => ({ data: { newOrder } })));
}
the structure { data: { newOrder } } is key here. This will be later translated by Nest.js to earlier mentioned data: ${newOrder}\n\n

why this Component only works on start but not when pressing button

I learn React-Redux and need help understanding why this Component only works on start but not when I press the button.
When debug start the breakpoints in the picture break execution but when I press the button I get this error showed in the picture.
When breakpoints hit I hoower over the {toasts.map(toast => { and the Array size is zero. But when I press button the breakpoints does not even hit
Any ide?
UPDATE
I have this configureStore.js
import { combineReducers } from "redux";
import { createStore, applyMiddleware, compose } from "redux";
import { forbiddenWordsMiddleware } from "../middleware";
import ToastsReducer from '../reducers/ToastsReducer';
import RootReducer from '../reducers/RootReducer';
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const reducers = {
toastsReducer: ToastsReducer,
rootReducer: RootReducer
};
const reduce = combineReducers({
...reducers,
});
const store = createStore(
reduce,
storeEnhancers(applyMiddleware(forbiddenWordsMiddleware))
);
export default store;
RootReducer.js
import { ADD_ARTICLE } from "../constants/action-types";
import { FOUND_BAD_WORD } from "../constants/action-types";
const initialState = {
articles: []
};
export default function reducer(state = initialState, action) {
if (action.type === ADD_ARTICLE) {
return Object.assign({}, state, {
articles: state.articles.concat(action.payload)
});
}
if (action.type === FOUND_BAD_WORD) {
//return Object.assign({}, state, {
// articles: state.articles.concat(action.payload)
// });
}
return state;
}
ToastsReducer.js
import { ADD_TOAST, REMOVE_TOAST } from "../constants/action-types";
const initialState = {
toastList: []
};
export default function toasts(state = initialState, action) {
const { payload, type } = action;
switch (type) {
case ADD_TOAST:
return [payload, state.toastList];
case REMOVE_TOAST:
return state.toastList.filter(toast => toast.id !== payload);
default:
return state;
}
}
UPDATE
Picture showing RootReducer.jsx and Toasts.jsx when I press button two times,
Toast.js
import PropTypes from "prop-types";
import React, { Component } from "react";
class Toast extends Component {
render() {
return (
<li className="toast" style={{ backgroundColor: this.props.color }}>
<p className="toast__content">
{this.props.text}
</p>
<button className="toast__dismiss" onClick={this.props.onDismissClick}>
x
</button>
</li>
);
}
shouldComponentUpdate() {
return false;
}
}
Toast.propTypes = {
color: PropTypes.string.isRequired,
onDismissClick: PropTypes.func.isRequired,
text: PropTypes.string.isRequired
};
export default Toast;
Please share your reducer code. Most likely, you have not set an initial state for toastList in the reducer or there is an error with toastsReducer.toastList.
Try the following:
Change line 34 to toasts: state.toastsReducer
Comment the lines from 10 to 19 and insert the following to make sure toasts is an array.
console.log(toasts);
console.log(toasts.toastList);
return null;
If both are undefined, then the value returned by the reducer is not right.
In ToastsReducer.js:
Change the following:
case ADD_TOAST:
return [ ...state.toastList, payload]; //<--- Here
When you do return[payload,state.toastList], it appends another array to the toastList.
Run the following to see:
toastList = ['abc'];
// Right way to add an item to an array.
toastList = [...toastList, 'def'];
console.log(toastList);
console.log('-----');
// Adds an array to the array. Incorrect way.
toastList = [toastList, 'ghi'];
console.log(toastList);
---UPDATE---
Change your ADD_TOAST case to:
return { toastList: [...state.toastList, payload] };
and you should be good to go.
Just do check your toasts array contains data,
{toasts && toasts.length > 0 ? toasts.map(toast => {...}) : null}

React state update in ajax success

I'm having problems with setting my state when a ajax call is successfully run. I want to update the state when the ajax process is completed.
The text in the div stays on "Busy" instead of "Done", while in the browser Network Tab, I see the status changing from "pending" to status "200".
import React, { Component } from "react";
import * as ReactDOM from "react-dom";
import { extend } from "lodash";
export class StoreToCheck extends React.Component{
constructor(props){
super(props);
this.state = { ListWithISBN :[],
getISBNS : false };
this.ajaxSuccess = this.ajaxSuccess.bind(this);
}
getISBNSList(){
if(!this.state.getISBNS){
var store_name;
// loop through array to fill store_name variable
var ajaxSuccess = this.ajaxSuccess;
if(store_name != ''){
apex.server.process(
'GET_EBOOKS_FROM_STORE',
{
success:function (data){
// when succesfull update state getISBNS
ajaxSuccess
}
}
);
}
}
}
ajaxSuccess(){
this.setState({"getISBNS":true});
}
componentDidMount(){
this.getISBNSList();
}
render(){
return(
<div>
{this.state.getISBNS ? "Done" : "Busy"}
</div>
)
}
}
You need to call ajaxSuccess method, also instead of storing the correct function reference, you can bind it inplace
getISBNSList(){
if(!this.state.getISBNS){
var store_name;
// loop through array to fill store_name variable
if(store_name != ''){
apex.server.process(
'GET_EBOOKS_FROM_STORE',
{
success: (data) => { // bind here with arrow function
// when succesfull update state getISBNS
this.ajaxSuccess() // call the function
}
}
);
}
}
}

Load data before createStore

I’ve created some React files where one initializes a Redux store. However, I really need to load some data from a json file before store is initialized.
I’ve tried to import a script loading the json structure then assigning it to the createStore initialState value. But createStore runs before the data is loaded and assigned.
Is there any simple way to say “dont do anything before my axios call is done”???
Action types
actiontypes.js
export const LOAD_DATA_REQUEST='LOAD_DATA_REQUEST';
export const LOAD_DATA_SUCCESS='LOAD_DATA_SUCCESS';
export const LOAD_DATA_ERROR='LOAD_DATA_ERROR';
Actions
actions.js
import * as Actions from './actiontypes';
function load() {
return { type: Actions.LOAD_DATA_REQUEST };
}
function success(res) {
return { type: Actions.LOAD_DATA_SUCCESS, payload: res };
}
function error(ex) {
return { type: Actions.LOAD_DATA_ERROR, payload: ex };
}
export function loadData(url) {
return (dispatch) => {
dispatch(load());
axios.get(url).then((res) => {
dispatch(success(res));
}).catch((ex) => {
dispatch(error(ex));
});
};
}
use this in reducers that requires
import * as Actions from './actiontypes';
const newState = Object.assign({}, state);
switch (action.type) {
case Actions.LOAD_DATA_REQUEST:
{
//maybe you load
newState.loading = true;
return newState;
}
case Actions.LOAD_DATA_SUCCESS:
{
const res = action.payload;
//do what you need for this reducer
return newState;
}
case Actions.LOAD_DATA_ERROR:{
/// maybe you will want to show some error message in some reducer?
return newState;
}
}
You just need the first screen of your application on componentWillMount() call the loadData() action
I hope this can help you

react-redux initial ajax data and generate childrens

I am new to react-redux.I have to says I read a lot of example project, many use webpack and couple a lot of package together without detailed introduction. I also read official example several times, but I still can not understand it well, specially in how to get initial data, and show it in the dom and communicate with ajax(not like jquery.ajax, use ajax in redux seems very complex, everyone's code has different approach and different style make it much hard to understand)
I decide to build a file manager webui to learn react-redux.
To begin, I just want it work, so no ajax:
containers/App.js:
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {getFileList} from '../actions/NodeActions'
import Footer from '../components/Footer';
import TreeNode from '../containers/TreeNode';
import Home from '../containers/Home';
export default class App extends Component {
componentDidMount() {
let nodes = getFileList();
this.setState({
nodes: nodes
});
}
render() {
const { actions } = this.props;
const { nodes } = this.state;
return (
<div className="main-app-container">
<Home />
<div className="main-app-nav">Simple Redux Boilerplate</div>
{nodes.map(node =>
<TreeNode key={node.name} node={node} {...actions} />
)}
<Footer />
</div>
);
}
}
function mapStateToProps(state) {
return {
test: state.test
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(getFileList, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
actions/NodeActions.js:
import { OPEN_NODE, CLOSE_NODE } from '../constants/ActionTypes';
export function openNode() {
return {
type: OPEN_NODE
};
}
export function closeNode() {
return {
type: CLOSE_NODE
};
}
class NodeModel {
constructor(name, path, type, right) {
this.name = name;
this.path = path;
this.type = type;
this.right = right;
}
}
const testNodes = [
new NodeModel('t1','t1', 'd', '777'),
new NodeModel('t2','t2', 'd', '447'),
new NodeModel('t3','t3', 'd', '667'),
]
export function getFileList() {
return {
nodes: testNodes
}
}
export function ansyncGetFileList() {
return dispatch => {
setTimeout(() => {
dispatch(getFileList());
}, 1000);
};
}
reducers/index.js
import { combineReducers } from 'redux';
import opener from './TreeNodeReducer'
const rootReducer = combineReducers({
opener
});
export default rootReducer;
reducers/TreeNodeReducer.js
import { OPEN_NODE, CLOSE_NODE } from '../constants/ActionTypes';
const initialState = [
{
open: false
}
]
export default function opener(state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
return true;
case CLOSE_NODE:
return false;
default:
return state;
}
}
reducers/index.js
import { combineReducers } from 'redux';
import opener from './TreeNodeReducer'
const rootReducer = combineReducers({
opener
});
export default rootReducer;
store/store.js(a copy from a redux demo):
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers';
import createLogger from 'redux-logger';
import thunk from 'redux-thunk';
import DevTools from '../containers/DevTools';
const logger = createLogger();
const finalCreateStore = compose(
// Middleware you want to use in development:
applyMiddleware(logger, thunk),
// Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument()
)(createStore);
module.exports = function configureStore(initialState) {
const store = finalCreateStore(rootReducer, initialState);
// Hot reload reducers (requires Webpack or Browserify HMR to be enabled)
if (module.hot) {
module.hot.accept('../reducers', () =>
store.replaceReducer(require('../reducers'))
);
}
return store;
};
chrome console says:Uncaught TypeError: Cannot read property 'nodes' of null at App render() {
I don't know the es6 well, due to react-redux strange syntax make me read the es6 doc, but I am not sure my code is right.
Tring:
I think maybe can not create testNodes with new instance in the list, so I change testNodes to plain json:
const testNodes = [
{name:'t1',type:'t1'},
{name:'t2',type:'t2'},
{name:'t3',type:'t3'},
]
Still same error
maybe action can not get the global testNodes? I move testNodes into getFileList, not work too.
I have no idea.
After solve this, I would try to replace getFileList content to a ajax call.
PS:My react-route also have strange problem, chrome show blank page and no error when I wrap App with route, just feel react-redux is so hard for newbee...this is just some complain...
Simply
you don't need to bindActionCreators yourself
you need to use this.props.getFileList
you don't need to manage it with component's state
for eg.
import {ansyncGetFileList} from '../actions/NodeActions'
componentWillMount() {
// this will update the nodes on state
this.props.getFileList();
}
render() {
// will be re-rendered once store updated
const {nodes} = this.props;
// use nodes
}
function mapStateToProps(state) {
return {
nodes: state.nodes
};
}
export default connect(
mapStateToProps,
{ getFileList: ansyncGetFileList }
)(App);
Great Example
Update based on the question update and comment
since your state tree doesn't have a map for nodes you'll need to have it in the state's root or opener sub tree.
for async operation you'll have to modify your thunk action creator
for eg.
export function ansyncGetFileList() {
return dispatch => {
setTimeout(() => {
dispatch({ type: 'NODES_SUCCESS', nodes: getFileList()}); // might need to export the type as constant
}, 1000);
};
}
handle the NODES_SUCCESS action type in reducer
const initialState = {
nodes: []
};
export default function nodes(state = initialState, action) {
switch (action.type) {
// ...
case 'NODES_SUCCESS':
let nodes = state.nodes.slice();
return nodes.concat(action.nodes);
// ...
}
}
use nodes reducer to manage nodes sub tree
for eg.
import { combineReducers } from 'redux';
import opener from './TreeNodeReducer'
import nodes from './nodes'
const rootReducer = combineReducers({
opener, nodes
});
export default rootReducer;
use mapStateToProps as above to get the nodes
regarding mapDispatchToProps
The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn’t aware of Redux, and you don’t want to pass dispatch or the Redux store to it.
Since you already have the access to dispatch you can call it directly. Passing a map is a shorthand version of it. video

Resources