mockApi in componentdidmount not returning any values - reactjs

I'm trying to populate an array of object in my state when my component loads. I have the following files. What I want to happen is that my caseList state gets updated and after calling the loadCases method from my mockapi. The problem is it seems like I can't seem to get the values from my mockapi. I'm pretty new to react and redux so I might have missed a very key concept here.
CaseListPage.js
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import BulletImage from '../../images/icon_requestor.png';
import {IndexLink} from 'react-router';
import CaseListHeader from './CaseListHeader';
import * as caseActions from '../../actions/caseActions';
import {bindActionCreators} from 'redux';
import CaseItems from './CaseItems';
class CaseListPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
caseList: []
};
}
componentDidMount() {
this.props.actions.loadCases()
.then((response) => console.log(response));
}
render() {
return(
<div className="container-fluid">
<div className="row">
<div className="col-xs-6 form-header-container">
<img src={BulletImage} alt="bullets" className="bulletImage pull-left" width="40"/>
<span className="form-header-cl pull-left">LIST OF REQUESTS LOGGED</span>
</div>
<div className="col-xs-6" id="backDiv">
<IndexLink to="/">
<p className="form-header-back pull-right">
<i className="glyphicon glyphicon-chevron-left" aria-hidden="true"></i>
BACK
</p>
</IndexLink>
</div>
</div>
<CaseListHeader />
<div className="row case-list-items">
{/*<CaseItems items={this.state.caseList}/>*/}
<div id="caseListItem1" className="row collapse case-list-request">
<div className="col-xs-2">
<span> </span>
<span className="case-id-item">LEMUEL MALLARI</span>
</div>
<div className="col-xs-3">
<span> </span>
<span>Request ID: R10000001</span>
</div>
<div className="col-xs-2">
<span> </span>
<span>22/07/2018 01:00</span>
</div>
<div className="col-xs-3">
<span> </span>
<span>CPO: In Progress</span>
</div>
<div className="col-xs-1">
<button type="button" className="btn btn-case-item">RECALL</button>
</div>
<div className="col-xs-1">
<button type="button" className="btn btn-case-item">TRACK</button>
</div>
</div>
</div>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(caseActions, dispatch)
};
}
function mapStateToProps(state, ownProps) {
return {
cases: state.cases
};
}
CaseListPage.propTypes = {
actions: PropTypes.object.isRequired,
cases: PropTypes.arrayOf(PropTypes.object)
};
export default connect(mapStateToProps, mapDispatchToProps)(CaseListPage);
caseActions.js
import * as types from './actionTypes';
import caseApi from '../api/mockCaseAPI';
import {beginAjaxCall, ajaxCallError} from './ajaxStatusActions';
export function loadCasesSuccess(cases) {
return { type: types.LOAD_CASES_SUCCESS, cases };
}
export function loadCases() {
return function(dispatch) {
dispatch(beginAjaxCall());
return caseApi.getAllCases().then(cases => {
dispatch(loadCasesSuccess(cases));
}).catch(error => {
throw(error);
});
};
}
caseReducer.js
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function caseReducer(state = initialState.cases, action) {
switch (action.type) {
case types.LOAD_CASES_SUCCESS:
return action.cases;
default:
return state;
}
}
mockCaseApi.js:
import delay from './delay';
const cases = [
{
caseid: '709460',
requestname: 'iPhone Request',
lastmodified: '20/07/2018 05:34',
overallstatus: 'CPO: In Progress',
requests: ['#caseListItem1', '#caseListItem2', '#caseListItem3']
},
{
caseid: '709461',
requestname: 'iPad Request',
lastmodified: '22/07/2018 05:34',
overallstatus: 'Completed',
requests: ['#caseListItem3', '#caseListItem5', '#caseListItem6']
},
{
caseid: '709462',
requestname: 'iPhone Request',
lastmodified: '25/07/2018 05:34',
overallstatus: 'CPO: In Progress',
requests: ['#caseListItem7', '#caseListItem8', '#caseListItem9']
}
];
class CaseAPI {
static getAllCases() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(Object.assign([], cases));
}, delay);
});
}
}
export default CaseAPI;
configureStore.dev.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk, reduxImmutableStateInvariant())
);
}

I don't think you are using bindActionCreators correctly, from the redux docs:
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.
Instead write your mapDispatchToProps like:
import { loadCases } from '../../actions/caseActions';
...
...
componentDidMount() {
this.props.loadCases()
.then((response) => console.log(response));
}
...
function mapDispatchToProps(dispatch) {
return {
loadCases: () => dispatch(loadCases())
};
}

Related

React - Redux dispatcher not mapping

I have a several react components in react-redux application. Most of them are working find, but the below "TagItemWidget" does not appear to bind the state or dispatchers to props. I have confirmed that the dispatch function works and fires the reducer. The same function and state can be bound on other components. I have done a trace and observed that the bind function is firing. However, in both "console.log" outputs, props is empty. componentDidMount and render appear to be called only once, when the page loads - never again. What gives?
UPDATE: If I move my <TagItemWidget /> into the spot where <TagItemButton /> is, it populates the dispatchers. Is there a problem with my TagItemButton?
TagItemWidget:
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { tags_list } from "../../actions/tags";
export class TagItemWidget extends React.Component {
static propTypes = {
cases: PropTypes.array.isRequired,
activeCase: PropTypes.string
};
constructor(props) {
super(props);
}
componentDidMount() {
console.log(this)
}
render() {
console.log(this)
return (
<Fragment>
<div key={Math.random} >
{this.props.activeCase}
</div>
</Fragment>
)
}
}
const mapStateToProps = (state) => ({
cases: state.tags.tags,
activeCase: state.cases.activeCase
});
export default connect(mapStateToProps, { tags_list })(TagItemWidget);
The including component, TagItemButton:
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { TagItemWidget } from './TagItemWidget';
export class TagItemButton extends Component {
render() {
return (
<Fragment>
<a href="#" className="list-group-item list-group-item-action" id="controls_tagitem"
data-toggle="modal" data-target="#tagItemModal">
Tag Item
</a>
<div className="modal fade" id="tagItemModal" tabIndex="-1"
role="dialog" aria-labelledby="tagItemModalLabel"
aria-hidden="true">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title"
id="tagItemModalLabel">
Tag Item
</h5>
<button type="button" className="close"
data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<TagItemWidget />
</div>
</div>
</div>
</div>
</Fragment>
)
}
}
export default TagItemButton;
actions/tags.js
import { TAGS_LIST } from "./types";
import { createMessage, returnErrors } from "./messages";
export const tags_list = ( case_id ) => dispatch => {
if ( case_id != null ) {
console.log("dispatging TAGS_LIST")
axios
.get("/OMNI_api/api/tag/listbycase/?case_id="+case_id)
.then(response => {
dispatch
({
type: TAGS_LIST,
payload: response.data
})
})
}
}
If you are using redux-thunk. Which I think you are.
dispatch needs to be spread from the thunk like so
export const tags_list = ( case_id ) => ({ dispatch }) => {
You need to bindActionCreators
either before passing them to the connect function
const mapDispatch = (dispatch) => bindActionCreators({ tag_list }, dispatch);
export default connect(mapStateToProps, mapDispatch)(TagItemWidget);
inside the constructor
https://redux.js.org/api/bindactioncreators

How do I reorder/sort Firebase data in React App?

So I'm flying by the seat of my pants here, and I need help reordering a data set. Here's the base of my react component. This is a personal project that I'm doing to then reuse at work for an actual project. The base of this came from a YouTube series about React + Firebase: https://www.youtube.com/playlist?list=PL4cUxeGkcC9iWstfXntcj8f-dFZ4UtlN3
Ideally, I'd like a set of buttons that will sort the data from asc or desc. And maybe dependent on some other things.
import React, { Component } from 'react';
import ShopList from '../shops/ShopList.js';
import { Helmet } from 'react-helmet';
import { connect } from 'react-redux';
import { firestoreConnect } from 'react-redux-firebase';
import { compose } from 'redux';
class Dashboard extends Component {
reOrder = (e) => {
e.preventDefault();
console.log("button works!");
}
render() {
const { shops } = this.props;
return(
<div className="dashboard container">
<Helmet>
<title>Dashboard | Indianapolis Coffee Guide</title>
</Helmet>
<div className="row">
<div className="col-sm-6">
<button className="btn btn-primary" onClick={this.reOrder}>Reorder</button>
</div>
</div>
<div className="row">
<ShopList shops={shops} />
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
shops: state.firestore.ordered.coffeeShops,
auth: state.firebase.auth
}
}
export default compose(
connect(mapStateToProps),
firestoreConnect([
{ collection: 'coffeeShops', orderBy: ['shopName', 'asc']}
])
)(Dashboard)
The data is being ordered right now at the end with the orderBy, but I need to be able to update that...
Thanks for your help!
I am not sure it would work, because I have never used react-redux-firebase, but it seems to me you could create a redux action that sets the ordering direction in redux store. Like this:
import React, { Component } from 'react';
import ShopList from '../shops/ShopList.js';
import { Helmet } from 'react-helmet';
import { connect } from 'react-redux';
import { firestoreConnect } from 'react-redux-firebase';
import { compose } from 'redux';
class Dashboard extends Component {
reOrder = (e) => {
e.preventDefault();
console.log("button works!");
const orderDirection = this.props.orderDirection === 'asc' ? 'desc' : 'asc';
this.props.dispatch(ReOderAction(orderDirection));
}
render() {
const { shops } = this.props;
return(
<div className="dashboard container">
<Helmet>
<title>Dashboard | Indianapolis Coffee Guide</title>
</Helmet>
<div className="row">
<div className="col-sm-6">
<button className="btn btn-primary" onClick={this.reOrder}>Reorder</button>
</div>
</div>
<div className="row">
<ShopList shops={shops} />
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
shops: state.firestore.ordered.coffeeShops,
auth: state.firebase.auth,
orderDirection: state.anything.orderDirection,
}
}
export default compose(
connect(mapStateToProps),
firestoreConnect([
{ collection: 'coffeeShops', orderBy: ['shopName', orderDirection]}
])
)(Dashboard)
Please, let me know if it worked...

React Redux TypeError: Cannot read property 'map' of undefined

I'm very beginner at React/Redux coding. I've been lost in containers and components of react. Before Redux, I could fetch JSON data easily. Because of state complexity, I decided to learn Redux. Lack of time made me to ask this question.
I can't understand why my props aren't filled with reducer.
(I'm trying to fetch an json array which name is "events".)
Here is my code:
./actions/eventAction.js
import C from './actionType';
export function fetchEvents() {
return function (dispatch) {
dispatch(requestEvent());
return fetch('http://localhost:3000/json/data.json')
.then(handleErrors)
.then(
response => response.json(),
error => console.log('An error occurred.', error),
)
.then((json) => {
dispatch(receivedEvents(json));
},
);
};
}
export const requestEvent = () => ({
type: C.REQUEST_EVENT
});
export const receivedEvents = json => ({
type: C.RECEIVED_EVENTS,
payload: json.events
});
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
./Component/Event.js
import React from 'react'
import PropTypes from 'prop-types'
export const Event = ({ title, description, category, tags, img, onClick}) => (
<div className="card eq-event-card mb-5">
<img className="img-speech-bubble card-img-top eq-event-img" src={img} alt="Card image cap"></img>
<div className="card-body shadow-lg">
<div className="container d-flex flex flex-column mb-5">
<div className="fab-button justify-content-center">
<i className="fas fa-plus my-3" />
</div>
<div className="eq-event-header container d-flex flex-row justify-content-between">
<div className="eq-event-title col-md-9">
<p className="card-title h3 text-right">
{title}
</p>
</div>
<div className="eq-event-cat text-center col-md-3" >
<p className="h5">{category})</p>
</div>
</div>
<div className="container">
<div className="row">
<div className="eq-event-desc col-md-8 col-sm-12">
<p className="text-justify card-text text-muted">
{description}
</p>
</div>
<div className="eq-event-tag col-md-4 col-sm-12 ">
<ul className="text-justify">
<li className="text-muted">{tags}</li>
</ul>
</div>
</div>
</div>
</div>
<div className="d-flex justify-content-center">
<button onClick={onClick} href="#" className="more-button btn btn-primary">اطلاعات بیشتر <i className="fas fa-arrow-left" /></button>
</div>
</div>
</div>
)
Event.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
tags: PropTypes.arrayOf(PropTypes.string),
onClick: PropTypes.func.isRequired,
img: PropTypes.string.isRequired
}
export default Event
./Components/EventList.js
import React from 'react'
import PropTypes from 'prop-types'
import Event from './Event'
export const EventList = ({events}) => (
events.map((event, index) => (
<Event key={index} {...event} />
))
)
EventList.propTypes = {
events: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
tags: PropTypes.arrayOf(PropTypes.string),
img: PropTypes.string.isRequired
}).isRequired
).isRequired,
}
export default EventList
./containers/EventListHandler
import { connect } from 'react-redux'
import {fetchEvents, receivedEvents} from '../actions/eventAction'
import EventList from '../components/EventList'
import {C} from '../actions/actionType'
const getEvents = (events, actionType) => {
switch(actionType) {
case C.RECEIVED_EVENTS:
return events
default:
throw new Error('Errorororor!')
}
}
const mapStateToProps = state => {
return {
events: getEvents(state.events, C.RECEIVED_EVENTS )
}
}
const mapDispatchToProps = dispatch => {
return ({
fetchEvents: () => {dispatch(receivedEvents)}
})
}
const ShowEventList = connect(
mapStateToProps,
mapDispatchToProps
)(EventList)
export default ShowEventList
./reducers/eventReducer.js
import {C} from '../actions/actionType';
export default (state = [], action) => {
switch (action.type){
case C.RECEIVED_EVENTS:
return [
...state,
Object.assign({}, action.payload)
];
default:
return state;
}
};
./reducers/index.js
import { combineReducers } from 'redux';
import EventReducer from './eventReducer';
export const rootReducer = combineReducers({
events: EventReducer
});
export default rootReducer;
and error message:
Warning: Failed prop type: The prop events is marked as required in
EventList, but its value is undefined.
in EventList
UPDATE:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Place, Time} from './plan';
import {Event} from './containers/EventListHnadler'
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import rootReducer from './reducers/index'
const store = configureStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<Event />
</Provider>, document.getElementById('event-entry'));
ReactDOM.render(<Place />, document.getElementById('select-place'))
ReactDOM.render(<Time />, document.getElementById('select-time'))
serviceWorker.register();
i am really not sure where you are using this ShowEventList
but where ever it is use if condition
render(){
<div>
{
events && events.length>0 &&
<ShowEventList events={this.events} />
}
</div>
}
this will make sure that your EventList components always receives the props

How to add 'Delete Post' functionality on list items with React & Redux?

I'm currently taking the Modern React with Redux course from Udemy.
On posts_show component I can get the id from react-router's params object from the uri as shown below.
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {fetchPost, deletePost } from "../actions/index";
import { Link } from 'react-router-dom';
class PostsShow extends Component {
componentDidMount() {
// if (!this.props.post) { if we don't have it go and grab that thing}
const { id } = this.props.match.params;
this.props.fetchPost(id);
}
onDeleteClick() {
const { id } = this.props.match.params;
this.props.deletePost(id, () => {
this.props.history.push('/');
});
}
render() {
const { post } = this.props;
if(!post) { // BEFORE THE DATA IS LOADED, WE SHOULD RETURN (RENDER) ANOTHER THING.
return <div>Loading...</div>
}
return (
<div>
<Link to="/" className="btn btn-primary">Back to Index</Link>
<button
className="btn btn-danger pull-xs-right"
onClick={this.onDeleteClick.bind(this)}
>
Delete Post
</button>
<h3>{post.title}</h3>
<h6>Categories: {post.categories}</h6>
<p>{post.content}</p>
</div>
);
}
}
function mapStateToProps({ posts }, ownProps) { // (application state, ownProps)
return { post: posts[ownProps.match.params.id] };
}
export default connect(mapStateToProps, { fetchPost, deletePost })(PostsShow);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Here is the action creator:
export function deletePost(id, callback) {
const request = axios.delete(`${ROOT_URL}/posts/${id}${API_KEY}`)
.then(() => callback());
return {
type: DELETE_POST,
payload: id
}
}
and here is the reducer:
export default function(state = {}, action) {
switch (action.type) {
case DELETE_POST:
return _.omit(state, action.payload);
Now I would like to add the same functionality for the posts_index component.
I'd like to add Delete Post buttons for each individual list item. I assume that it's ok to use the same action_creator and reducer for the task, however I can't reach the id property of the individual list item and pass it to the onDeleteClick function on the posts_index component.
I would appreciate if anyone can help me to resolve this issue.
Thanks in advance.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {fetchPosts, deletePost} from "../actions/index";
import _ from 'lodash';
import { Link } from 'react-router-dom';
class PostsIndex extends React.Component {
componentDidMount() {
this.props.fetchPosts();
}
onDeleteClick() {
console.log(this.props.posts);
alert('clicked');
}
renderPosts() {
return _.map(this.props.posts, post => {
return <li className="list-group-item" key={post.id}>
<Link to={`/posts/${post.id}`}>
{post.title}
</Link>
<button
className="btn btn-danger pull-xs-right"
onClick={this.onDeleteClick.bind(this)}
>Delete {post.id}
</button>
</li>
})
}
render() {
// console.log(this.props.posts);
return (
<div>
<div className="text-xs-right">
<Link className="btn btn-primary" to="/posts/new">
Add a Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
return { posts: state.posts };
}
export default connect(mapStateToProps, { fetchPosts, deletePost })(PostsIndex);
Change the onClick to
onClick={() => this.onDeleteClick(post.id)}
Ok, I got it, thank you for your help!
onDeleteClick(id) {
this.props.deletePost(id, () => {
this.props.history.push('/');
});
}
renderPosts() {
return _.map(this.props.posts, post => {
return <li className="list-group-item" key={post.id}>
<Link to={`/posts/${post.id}`}>
{post.title}
</Link>
<button
className="btn btn-danger pull-xs-right"
onClick={() => this.onDeleteClick(post.id)}
>Delete {post.id}
</button>
</li>
})
}

How to hookup input boxes to Reactjs / Redux?

I not 100% sure if I am doing this right as per the redux design.
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import 'materialize-css/sass/materialize.scss';
import NavigationContainer from './NavigationContainer';
import AddStorageModal from './AddStorageModal.js'
import {loadAddStorageModal, createNewStorage} from '../actions/StorageActions.js'
import '../styles/main.scss';
class App extends React.Component {
render() {
return (
<div>
<NavigationContainer />
<AddStorageModal {...this.props} />
</div>
)
}
}
function mapStateToProps(state) {
return {
storages: state.storages
};
}
function matchDispatchToProps(dispatch){
return bindActionCreators({loadAddStorageModal: loadAddStorageModal, createNewStorage: createNewStorage}, dispatch);
}
export default connect(mapStateToProps, matchDispatchToProps)(App);
StorageActions
export function fetchStorage() {
return function(dispatch) {
var payload = [
{
id: 1,
name: "Fridge2"
},
{
id: 2,
name: "Closet2"
},
{
id: 3,
name: "Car2"
}
];
dispatch({type: "Fetch_Storage", payload: payload});
}
}
export function loadAddStorageModal(load) {
return function(dispatch) {
dispatch({type: "Load_Add_Storage_Modal", payload: load});
}
}
export function createNewStorage(storage) {
return function(dispatch) {
dispatch({type: "New_Storage_Created", payload: storage});
}
}
Reducer
export default function reducer(state = {
fetchedStorages: [],
openAddStorageModal: false
}, action) {
switch (action.type) {
case "Fetch_Storage": {
return {
fetchedStorages: action.payload
}
}
case "Load_Add_Storage_Modal": {
return {
openAddStorageModal: action.payload,
fetchedStorages: state.fetchedStorages
}
}
case "New_Storage_Created": {
return {
openAddStorageModal: false,
}
}
}
return state;
}
AddStorageModal
import React from 'react';
import 'materialize-css/sass/materialize.scss';
import 'materialize-css/js/materialize.js';
import 'font-awesome/scss/font-awesome.scss';
import '../styles/main.scss';
export default class AddStorageModal extends React.Component {
constructor() {
super();
this.state = {storageName: ""}
}
handleChange(event) {
this.setState({storageName: event.target.value});
}
render() {
if (this.props.storages.openAddStorageModal) {
$('#add-new-storage-modal').openModal({ dismissible: false });
}
return (
<div id="add-new-storage-modal" className="modal" >
<div className="modal-content">
<h6>Enter your new Storage (Freezer, Pantry, etc.) </h6>
<div className="row">
<form>
<div className="input-field col s12 m12 l12 ">
<input id="storage_name" type="text" className="validate" value={this.state.storageName} onChange={this.handleChange} />
<label htmlFor="storage_name">Storage Name</label>
</div>
<br />
<h4 className="center">OR</h4>
<h6>Enter in the sharing key you were given.</h6>
<div className="input-field col s12 m12 l12 ">
<input id="sharing_key" type="text" className="validate" />
<label htmlFor="sharing_key">Sharking Key</label>
</div>
</form>
</div>
</div>
<div className="modal-footer">
Add
<a href="#!" className="modal-action modal-close waves-effect waves-green btn-flat" onClick={() => this.props.loadAddStorageModal(false) }>Cancel</a>
</div>
</div>
)
}
}
I get
Uncaught TypeError: Cannot read property 'setState' of undefined
So I am not sure if this just means I am doing redux wrong or if I just made some general error.
You can't pass a generic function reference, you need to keep the reference to this. You have 2 options:
bind this to the function, like #nuway said in his answer.
Use an arrow function, which also keeps the this reference: onChange={ (event) => this.handleChange(event) }
you need to bind to this for the handleChange handler, otherwise this inside handleChange funtion won't be the react component but rather the input element itself.
onChange={this.handleChange.bind(this)}

Resources