I'm seeing this strange error.
I'm writing an app which uses the graph api to retrieve event details from facebook.
The event has a couple of attributes from which:
- owner which is an object containing owner id, owner name, and other attributes
- cover which is an object representing the event cover image details.
I save events in a mongo database, here is what my event model looks like:
const EventSchema = new Schema({
title: String,
name: String,
_id: {
type: String,
unique: true,
default: shortid.generate,
},
start_time: Date,
end_time: Date,
description: String,
owner: {},
cover: {},
venue: {},
privacy: String,
timezone: String,
location: String,
createdAt: { type: Date, default: Date.now },
event_type: {},
});
I have an express route which sends back a given event by id:
router.get('/:id', (req, res) => {
Event.findById(req.params.id).exec((error, events) => {
if (error){
res.json(error);
}
res.json(events);
})
});
My component architecture goes like this:
-EventPage component which contains an EventDetails component.
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import axios from 'axios';
import EventDetails from './eventDetails';
class EventPage extends React.Component {
constructor(props) {
super(props);
this.state = {
event: {},
};
}
componentWillMount() {
axios.get(`/api/events/${this.props.params.id}`)
.then((eventResponse) => {
this.setState({
event: eventResponse.data
})
}).catch((err) => {
console.log(JSON.stringify(err));
})
}
render() {
return (
<div className="row">
<EventDetails event={this.state.event} />
</div>
)
}
}
EventPage.propTypes = {
};
export default EventPage;
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import _ from 'lodash';
class EventDetails extends React.Component {
constructor(props) {
super(props);
}
render() {
const { name, description, start_time, end_time, owner } = this.props.event;
return(
<div className='row'>
<h1>{name}</h1>
<p>{description}</p>
<p>{JSON.stringify(this.props.event)}</p>
<p>{this.props.event.owner}</p>
<pre>
</pre>
</div>
)
}
}
EventDetails.propTypes = {
};
export default EventDetails;
Trying to display the event owner's name results in this error:
{"name":"Invariant Violation","framesToPop":1}
The error comes from the axios error handler in the EventPage component.
Anyone sees what I've done wrong here?
Thanks for your help
I had probably the same problem with {"name":"Invariant Violation","framesToPop":1}.
I've passed a javascript object instead of an array and it worked for me.
Message.find({}).sort({'date': -1}).limit(50).exec().then( (doc, err) => {
console.log('found');
const messages = [];
doc.map( (item) => {
messages.push({data: item});
});
callback(err, {items: messages});
});
Related
I have a response from my mongodb database as the following
{_id: '61ca4273e7cc1da1f3dbc9a3', title: 'Hero Syndrome', slug: 'hero-syndrome', category: 'Game', release_date: null, … }
I'm using Redux to fetch the data.
When I do console.log(game) which is the Object I provided, the console return the Object indeed. But when I'm trying to access the children such as title or slug it doesn't work.
I used to have this error Objects are not valid as a React child .. but fixed it somehow randomly in the code.
Any idea how to access title for example ?
What I tried : {title}, title, {game.title} and none of them work
What I did to get data from Redux :
GameComponent.propTypes = {
game: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
game: state.game,
});
And at the top of the component
function GameComponent({
game: { game, loading, title },
}) { ....
I tried to convert the object to string and to array in order for React to read it but I failed.
Code :
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getGameByToken } from '../actions/game';
import GameOverview from './GameOverview';
function GameComponent({ game: { game, loading, title }, getGameByToken, auth }) {
useEffect(() => {
getGameByToken(token);
}, [getGameByToken, token]);
return <>
//this doesn't work
Title : {title}
</>;
}
GameComponent.propTypes = {
getGameByToken: PropTypes.func.isRequired,
game: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
game: state.game,
auth: state.auth,
});
export default connect(mapStateToProps, { getGameByToken })(GameComponent);
Response from database in redux :
import axios from 'axios';
import { setAlert } from './alert';
import { GET_GAME, GAME_ERROR } from './types';
// GET GAMES BY TOKEN
export const getgameByToken = (token) => async (dispatch) => {
try {
const res = await axios.get('/api/games/' + token);
dispatch({ type: GET_GAME, payload: res.data });
} catch (err) {
dispatch({
type: GAME_ERROR,
payload: {
msg: err.response.msg,
status: err.response.status,
},
});
}
};
From Redux Dev Tools :
EDIT: If I rename game: state.game, to game: state.game.game, I actually got the value ! But when refreshing It goes back saying TypeError: Cannot read properties of null (reading 'title')
I have created a React App and I am using .Net Core in the backend, the list of data from backend is successfully received, but in react while using Map it only shows one item from the list.I ma using MObX for state management.
My Code is :
import React, { useContext, useEffect } from 'react'
import { RootStoreContext } from '../../app/stores/rootStore';
import { observer } from 'mobx-react-lite';
import { Segment, Item, Icon, Button } from 'semantic-ui-react';
import { format } from 'date-fns';
import { Link } from 'react-router-dom';
const BookList: React.FC = () => {
const rootStore = useContext(RootStoreContext);
const { loadBooks, getAvailableBooks } = rootStore.bookStore;
useEffect(() => {
loadBooks();
}, [loadBooks]);
return (
<div>
{getAvailableBooks.map(books => (
<Segment.Group key={books.bookName}>
<Segment>
<Item.Group>
<Item>
<Item.Image size='tiny' circular src='/assets/user.png' />
<Item.Content>
<Item.Header as='a'>{books.bookName}</Item.Header>
</Item.Content>
</Item>
</Item.Group>
</Segment>
</Segment.Group>
))}
</div>
)
}
export default observer(BookList);
My BookStore is :
import { observable, action, computed, runInAction } from "mobx";
import agent from "../api/agent";
import { RootStore } from "./rootStore";
import { IBooks } from "../models/books";
export default class BookStore {
rootStore: RootStore;
constructor(rootStore: RootStore) {
this.rootStore = rootStore;
}
#observable bookRegistry = new Map();
#observable book: IBooks | null = null;
#observable loadingInitial = false;
#computed get getAvailableBooks() {
return Array.from(this.bookRegistry.values());
}
#action loadBooks = async () => {
this.loadingInitial = true;
try {
const books = await agent.Books.list();
runInAction("loading books", () => {
books.forEach((books) => {
books.issuedOn = new Date(books.issuedOn);
this.bookRegistry.set(books.id, books);
});
this.loadingInitial = false;
});
} catch (error) {
runInAction("load books error", () => {
this.loadingInitial = false;
});
}
};
}
and API is called from agent.ts
import axios, { AxiosResponse } from "axios";
import { history } from "../..";
import { toast } from "react-toastify";
import { IBooks } from "../models/books";
axios.defaults.baseURL = "https://localhost:44396/api";
const requests = {
get: (url: string) => axios.get(url).then(sleep(1000)).then(responseBody),
post: (url: string, body: {}) =>
axios.post(url, body).then(sleep(1000)).then(responseBody),
put: (url: string, body: {}) =>
axios.put(url, body).then(sleep(1000)).then(responseBody),
del: (url: string) => axios.delete(url).then(sleep(1000)).then(responseBody),
};
const Books = {
list: (): Promise<IBooks[]> => requests.get("/Book/GetBookList"),
};
export default {
User
};
export interface IBooks {
id: number;
bookname: string;
issuedOn: Date;
isReturned: boolean;
isRequested: boolean;
isAvailable: boolean;
isTaken: boolean;
name: string;
}
The response from API
from the screenshot of your API response, it seems that each "book" object does not have an id property. This might explain why you only see one element rendered, because in your loadBooks action, each time you try to do this.bookRegistry.set(books.id, books), you're using undefined as the key, and then on the next iteration you overwrite the value stored at that key.
I did a tutorial and am able to display all of my Articles objects but I cannot figure out how to modify the code in order to grab one specific object and store it in the state. I have tried a lot of different things but I keep getting 'TypeError: Cannot read property 'name' of undefined'.
One note, the id that I am looking for is stored in 'this.props.match.params.id' but I don't really know what this means or how to use it. Thanks
ArticleShow.js
import React, { Component } from "react";
import { Container } from "reactstrap";
import { connect } from "react-redux";
import { getArticle } from "../actions/articleActions";
import PropTypes from "prop-types";
class articleShow extends Component {
componentDidMount() {
this.props.getArticle();
}
render() {
const { article } = this.props.article;
return (
<Container>
{article.name}
<br />
{article.author}
<br />
{article.body.split("\r").map((c) => {
return <p> {c} </p>;
})}
<br />
</Container>
);
}
}
ArticleShow.propTypes = {
getArticle: PropTypes.func.isRequired,
article: PropTypes.object.isRequired,
};
const mapStateToProps = (state, props) => ({
article: state.article,
});
export default connect(mapStateToProps, { getArticle })(ArticleShow);
articleActions.js
import axios from "axios";
import {
GET_ARTICLES,
GET_ARTICLE,
} from "./types";
export const getArticles = () => (dispatch) => {
dispatch(setArticlesLoading());
axios.get("/api/articles").then((res) =>
dispatch({
type: GET_ARTICLES,
payload: res.data,
})
);
};
export const getArticle = (id) => (dispatch) => {
dispatch(setArticlesLoading());
axios.get(`/api/articles/${id}`).then((res) =>
dispatch({
type: GET_ARTICLE,
payload: res.data,
})
);
};
articleReducer.js
import {
GET_ARTICLES,
GET_ARTICLE,
} from "../actions/types";
const intialState = {
articles: [],
loading: false,
};
export default function (state = intialState, action) {
switch (action.type) {
case GET_ARTICLES:
return {
...state,
articles: action.payload,
loading: false,
};
case GET_ARTICLE:
return {
...state,
article: action.payload,
loading: false,
};
default:
return state;
}
}
routes/api/articles.js
const express = require("express");
const router = express.Router();
// Article Model
const Article = require("../../models/Article");
router.get("/", (req, res) => {
Article.find()
.sort({ date: -1 })
.then((articles) => res.json(articles));
});
router.get("/:id", (req, res) => {
Article.findById(req.params.id).then((article) => res.json(article));
});
module.exports = router;
models/Article.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// create schema
const ArticleSchema = new Schema({
name: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
body: {
type: String,
required: true,
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = Article = mongoose.model("article", ArticleSchema);
store.js
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
First, if possible I would suggest you to reconsider how you are storing your data in articles node. In articles array, it would save you lots of complications if you store articles as object instead of array with article id as key if we are updating/deleting/accessing these articles
Secondly, Article should be child component of Articles which would ensure a particular article would always exists when an Article component loads
articles: {
5f0b628f172467147fbed0c2: {
"name":"Article 4",
"author":"Carol Henderson"
}
}
In that scenario, your switch block would look like this:
case GET_ARTICLES:
return {
...state,
articles: action.payload.reduce((accObj, curObj) => {...accObj, [curObj._id]: curObj}, {}),
loading: false,
};
case GET_ARTICLE:
return {
...state,
articles: {
[action.payload._id]: action.payload,
},
loading: false,
};
But still if you keep it in your current shape due to some use case, you could try this:
case GET_ARTICLE:
// find the article and merge more details
const article = state.articles.find((art) => art._id === action.payload._id);
article = {...article, ...action.payload};
// since state has reference of article via references, your state has now new values.
return {
...state,
loading: false,
};
I haven't tested the code. But it should give you an idea how to go about it
Hello, I am new to redux and I am struggling with a problem. I am trying to access and map over the comments within my post array. However, I am not sure how to do this. So far, I've tried changing the actions and reducers in order to solve this issue. I think the problem is within the react and redux. I can't tell if my mapStateToProps is working correctly. Also, the state is being fetched from my express server and it seems to be working properly as you can see in the picture.
My getPost action:
export const getPost = (group_id, post_id) => async dispatch => {
try {
const res = await axios.get(`/api/groups/${group_id}/${post_id}`);
dispatch({
type: GET_POST,
payload: res.data
});
} catch (error) {
dispatch({
type: POST_ERROR,
payload: { msg: error.response.statusText, status: error.response.status }
});
}
};
The initial state:
const initialState = {
groups: [],
group: [],
loading: true,
error: {}
};
The reducer:
case GET_POST:
return {
...state,
post: payload,
loading: false
};
Where I'm trying to map over the comments:
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getPost } from '../../../redux/actions/group';
const Post = ({ getPost, post, match }) => {
useEffect(() => {
getPost(match.params.group_id, match.params.post_id);
}, [getPost, match.params.group_id, match.params.post_id]);
// I want to map over the comments here
return (
{post.comments.map(comment => ({ comment }))}
);
};
Post.propTypes = {
getPost: PropTypes.func.isRequired,
group: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPost })(Post);
You can access nested object with some tricks using redux, we have use this way in our prod env for some time.
First the reducer (you can make this reducer even more complex)
const LocalStorageReducer = createReducer<Store['localStorage']>(
new LocalStorage(),
{
saveLocalStorageItem(state: LocalStorage, action: any) {
return {...state, [action.payload.item]: action.payload.value}; // <= here
},
}
);
For Actions
export const actions = {
saveLocalStorageItem: (payload: InputAction) => ({type: 'saveLocalStorageItem', payload}),
};
For the type InputAction
export class InputAction {
item: string;
value: string | Array<string> | null | boolean;
constructor() {
this.item = '';
this.value = null;
}
}
For the handler in component
this.props.saveLocalStorage({ item: 'loading', value: false });
In this way you can go one way done to the nested redux store.
For complex (4-5 levels) and multiple (> 2 times) data structure, there are other ways, but in most situations, it's good enough.
I am trying to get my head around React, Redux, Saga and Typescript. I managed to follow an example to wire up a simple page that displayed data in a table. I am now trying to create a login form. I have followed various different examples. I think the actions, reducer, saga and types parts are OK but I am really struggling to connect the actual page up to work.
Below is what I have so far and I am looking for some guidance on how I can get this to work.
Typescript Errors
The following typescript errors are displaying on my index.ts page:
<form onSubmit={handleSubmit(this.submit)}> //[ts] Expected 5 arguments, but got 1
<button action="submit">LOGIN</button> //[ts] Property 'action' does not exist on type ....
const formed = reduxForm({form: 'signup'})(connected) //[ts] Argument of type 'ComponentClass<Pick<AllProps, "errors" | "handleSubmit ....
Problems:
Clearly there are lots and I have been trying to work through them without much success but here is where I think I need help.
- How can I wire up the onSubmit of the form to call my registerRequest action.
- How do I correctly connect the ReduxForm and export at the end of the index.tsx
pages/register/index.tsx
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Dispatch } from 'redux';
import { reduxForm, Field } from 'redux-form'
import { ApplicationState, ConnectedReduxProps } from '../../store'
import { Message, Error } from '../../store/register/types'
import { registerRequest } from '../../store/register/actions'
// Separate state props + dispatch props to their own interfaces.
interface PropsFromState {
email: string,
password: string,
requesting: boolean,
successful: boolean,
message: Message[],
errors: Error[],
}
// We can use `typeof` here to map our dispatch types to the props, like so.
interface PropsFromDispatch {
registerRequest: typeof registerRequest,
handleSubmit: typeof PropTypes.func,
signupRequest: typeof PropTypes.func,
}
// Combine both state + dispatch props - as well as any props we want to pass - in a union type.
type AllProps = PropsFromState & PropsFromDispatch & ConnectedReduxProps
class RegisterIndexPage extends React.Component<AllProps> {
public componentDidMount() { }
// Redux Form will call this function with the values of our
// Form fields `email` and `password` when the form is submitted
// this will in turn call the action
submit = (email: string, password: string) => {
// we could just do signupRequest here with the static proptypes
// but ESLint doesn't like that very much...
this.props.registerRequest(email, password)
}
public render() {
// grab what we need from props. The handleSubmit from ReduxForm
// and the pieces of state from the global state.
const { handleSubmit } = this.props
return (
<div className="signup">
{/* Use the Submit handler with our own submit handler*/}
<form onSubmit={handleSubmit(this.submit)}>
<h1>Register</h1>
<label htmlFor="email">Email</label>
{
/*
Our Redux Form Field components that bind email and password
to our Redux state's form -> register piece of state.
*/}
<Field
name="email"
type="text"
id="email"
className="email"
label="Email"
component="input"
/>
<label htmlFor="password">Password</label>
<Field
name="password"
type="password"
id="password"
className="password"
label="Password"
component="input"
/>
<button action="submit">LOGIN</button>
</form>
</div>
)
}
}
// It's usually good practice to only include one context at a time in a connected component.
// Although if necessary, you can always include multiple contexts. Just make sure to
// separate them from each other to prevent prop conflicts.
const mapStateToProps = ({ register }: ApplicationState) => ({
email: register.email,
password: register.password,
requesting: register.requesting,
successful: register.successful,
message: register.message,
error: register.error,
})
// patchToProps is especially useful for constraining our actions to the connected component.
// You can access these via `this.props`.
const mapDispatchToProps = (dispatch: Dispatch, { register }: ApplicationState) => ({
registerRequest: () => dispatch(registerRequest(register.email, register.password))
})
// Now let's connect our component!
// With redux v4's improved typings, we can finally omit generics here.
const connected = connect(
mapStateToProps,
mapDispatchToProps
)(RegisterIndexPage)
// Connect our connected component to Redux Form. It will namespace
// the form we use in this component as `signup`.
const formed = reduxForm({
form: 'signup'
})(connected)
// Export our well formed component!
export default formed
register/types.ts
export interface RegisterUser {
email: string
password: string
}
export interface Message {
body: string
time: Date
}
export interface Error {
body: string
time: Date
}
export const enum RegisterActionTypes {
SIGNUP_REQUESTING = '##register/SIGNUP_REQUESTING',
SIGNUP_SUCCESS = '##register/SIGNUP_SUCCESS',
SIGNUP_ERROR = '##register/SIGNUP_ERROR',
}
export interface RegisterState {
readonly email: string
readonly password: string
readonly requesting: boolean,
readonly successful: boolean,
readonly message: Message[],
readonly error: Error[],
}
register/sagas.ts
import { call, put, takeLatest } from 'redux-saga/effects'
import { RegisterActionTypes } from './types'
import { handleApiErrors } from '../../utils/handleAPIErrors'
const signupUrl = `${process.env.TEST_AUTH_API}/api/Clients`
function signupApi(email: string, password: string) {
return fetch(signupUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
})
.then(handleApiErrors)
.then(response => response.json())
.then(json => json)
.catch((error) => { throw error })
}
function* signupFlow(action: any) {
try {
const { email, password } = action
const response = yield call(signupApi, email, password)
yield put({ type: RegisterActionTypes.SIGNUP_SUCCESS, response })
} catch (error) {
yield put({ type: RegisterActionTypes.SIGNUP_ERROR, error })
}
}
function* signupWatcher() {
yield takeLatest(RegisterActionTypes.SIGNUP_REQUESTING, signupFlow)
}
export default signupWatcher
register/reducer.ts
import { Reducer } from 'redux'
import { RegisterState, RegisterActionTypes } from './types'
const initialState: RegisterState = {
email: "",
password: "",
requesting: false,
successful: false,
message: [],
error: [],
}
const reducer: Reducer<RegisterState> = (state = initialState, action) => {
switch (action.type) {
case RegisterActionTypes.SIGNUP_REQUESTING: {
return {
email: action.response.email,
password: action.response.password,
requesting: true,
successful: false,
message: [{ body: 'Signing up...', time: new Date() }],
error: [],
}
}
case RegisterActionTypes.SIGNUP_SUCCESS: {
return {
email: action.response.email,
password: action.response.password,
error: [],
message: [{
body: `Successfully created account for ${action.response.email}`,
time: new Date(),
}],
requesting: false,
successful: true,
}
}
case RegisterActionTypes.SIGNUP_ERROR: {
return {
email: action.response.email,
password: action.response.password,
error: state.error.concat([{
body: action.error.toString(),
time: new Date(),
}]),
message: [],
requesting: false,
successful: false,
}
}
default: {
return state
}
}
}
export { reducer as registerReducer }
register/actions.ts
import { action } from 'typesafe-actions'
import { RegisterActionTypes } from './types'
export const registerRequest = (email: string, password: string) => action(RegisterActionTypes.SIGNUP_REQUESTING, { email, password })
export default registerRequest