I am trying to show message 'hello brozz..' from the reducer to a component render() method.ComponentDidMount() get the this.props.message as please see the image. And my reducer is like
export const geod = (state ={}, action) => {
console.log('inside reducer');
switch(action.type){
case 'INITIAL_MESSAGE':
return [
...state, {
message:'hello brozz..'
}
]
case 'CLICK_MESSAGE':
return [
...state, {
message: ''
}
]
default:
return state;
}
};
But I can not use this.props.message as <div><div>{this.props.message}</div></div> inside the render method. I am getting the error
Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons
My component is,
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { initialMessageAction, clickButtonAction } from './redux.js';
import { connect } from 'react-redux';
import { Message } from './messageComponent';
class App extends Component {
componentWillMount() {
console.log('component will mount');
let data = this.props.initialMessageAction();
}
componentDidUpdate() {
console.log('comp Did Update')
console.log(this.props.message)
}
render() {
return (
<div className="App">
<div className="App-header">
</div>
<p className="App-intro">
react-redux-data-flow
</p>
<div>{this.props.message}</div>
<div>{this.props? <div>hi ..<button onClick={ () => this.props.clickButtonAction() }>hide message</button></div>: ''}</div>
</div>
);
}
}
const mapStateToProps = (state, ownProperty) => ({
message:state.geod,
});
const mapDispatchToProps = {
initialMessageAction,
clickButtonAction
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
const initialState = {message: null};
export const geod = (state = initialState, action) => {
console.log('inside reducer');
switch(action.type){
case 'INITIAL_MESSAGE':
return Object.assign({}, state, {message:'hello brozz..'});
case 'CLICK_MESSAGE':
return Object.assign({}, state, {message: ''});
default:
return state;
}
};
And in stateMap { message: state.geod.message }.
As initial state You use an empty object and you see it in error.
Related
I'm trying to edit an object and replace it in array using React and Redux like this:
case EDIT_LANGUAGE:
let languages = [...state.languageSkills];
languages[languages.findIndex(el => el.id === action.payload.id)] = action.payload;
return {
...state,
languageSkills: languages
};
'languages' array looks find before return statement, but state is not re-rendered. I guess I'm mutating state somehow. Other actions (delete, get, set) are working fine. Any tips?
EDIT. This is relevant part of the component that should render
import { setLanguages, getLanguages } from '../../actions';
import {connect} from 'react-redux';
import {bindActionCreators} from "redux"
import React, { Component } from 'react';
class UserProfile extends Component {
constructor(props) {
super(props);
}
render() {
const languageSkillItems = this.props.languageSkills.map((languageSkill) => {
return (
<LanguageSkillItem key={languageSkill.id} item={languageSkill} />
)
});
return (
<div className="profile">
<Language languageSkillItems={languageSkillItems} />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
languageSkills: state.languageSkills
};
};
const mapDispatchToProps = dispatch => {
return {
...bindActionCreators({ setLanguages, getLanguages }, dispatch)
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserProfile
);
You need to create a new array reference, easiest way is just to use map, like so:
case EDIT_LANGUAGE:
const languageSkills = state.languageSkills.map(el => {
if(el.id === action.payload.id) {
return action.payload;
}
return el;
});
return {
...state,
languageSkills
};
I can't understand why my Redux props are showing empty on the React component. This is the screenshot of the Redux state:
This is the component that visualizes. It is a child of a parent component that fetches the data and runs a for loop through the array of elements.
import React, { Component } from 'react'
import { connect } from "react-redux";
import PropTypes from "prop-types";
class ResourceCard extends Component {
render() {
const { resource } = this.props;
return (
<div className="resource-card">
<div className="resource-card-header">
<span className="res-span-header">{resource.header}</span>
</div>
<div className="resource-card-image">
<img src={resource.image} alt={resource.header} width='300px' />
</div>
<div className="resource-card-desc">
<span className="res-span-desc">{resource.description}</span>
</div>
<div className="resource-card-link">
<span className="res-span">{resource.website}</span>
</div>
</div>
)
}
}
ResourceCard.propTypes = {
resource: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
resource: state.resource
});
export default connect(
mapStateToProps,
{}
)(ResourceCard);
While the css is being rendered properly, and there are no errors in the console, the content itself is not there:
What could possibly be wrong here?
EDITED: Adding actions code:
// Get Resources
export const getResources = () => dispatch => {
axios
.get("/api/resources")
.then(res =>
dispatch({
type: GET_RESOURCES,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_RESOURCES,
payload: null
})
);
};
And the reducer:
import {
ADD_RESOURCE,
GET_RESOURCES,
DELETE_RESOURCE,
GET_RESOURCE
} from "../actions/types";
const initialState = {
resources: [],
resource: {}
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_RESOURCES:
return {
...state,
resources: action.payload
};
case GET_RESOURCE:
return {
...state,
resource: action.payload
};
case DELETE_RESOURCE:
return {
...state,
resources: state.resources.filter(resource => resource._id !== action.payload)
};
case ADD_RESOURCE:
return {
...state,
resources: [action.payload, ...state.resources]
};
default:
return state;
}
}
Parent component that runs a for-loop:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ResourceCard from './ResourceCard';
class ResourceFeed extends Component {
render() {
const { resources } = this.props;
return resources.map(resource => <ResourceCard key={resource._id} resource={resource} />)
}
}
ResourceFeed.propTypes = {
resources: PropTypes.array.isRequired
}
export default ResourceFeed;
And the component above contains this parent:
<ResourceFeed resources={resources}/>
Issue is here:
const mapStateToProps = state => ({
auth: state.auth,
resource: state.resource // <========== in this line
});
In ResourceCard component you are passing the resource props from two places, one from parent component and one from redux store, that's why.
Your component is expecting the resource value from parent, so remove this line:
resource: state.resource
After combining two reducers together (EditButton and TodoApp), my app everytime start crash. Before it, when I just use only one reducer TodoApp I did not have any problem with reducers. But now I cannot figure out what is wrong, because every time I get the error in map function of component below . Error "TypeError: Cannot read property 'map' of undefined".
So, what is I forgot? Also I cannot get the state in nested components or containers of App. It's strange too, but in App I can do that by console.log() for example.
/* REDUCERS */
import { combineReducers } from 'redux'
import { ADD_TODO, EDIT_TODO, DELETE_TODO, FILTER_TODO_UP, FILTER_TODO_DOWN } from '../Variables/Variables'
const initialState = {
todos: []
}
function EditButton(state, action) {
if (typeof state === 'undefined') {
return 'Edit';
}
switch (action.type) {
case EDIT_TODO:
return state = "Edit" ? "Done" : "Edit"
default:
return state
}
}
function TodoApp(state, action) {
if (typeof state === 'undefined') {
return initialState;
}
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
id: action.id,
text: action.text,
done: action.done
}
]
});
case EDIT_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
id: action.id,
text: action.text,
done: action.done
}
]
});
case DELETE_TODO:
return Object.assign({}, {
todos: state.todos.filter(todos => todos.id !== parseInt(action.id))
});
case FILTER_TODO_UP:
return Object.assign({}, {
todos: [
...state.todos.sort((a, b) => b.id - a.id)
]
});
case FILTER_TODO_DOWN:
return Object.assign({}, {
todos: [
...state.todos.sort((a, b) => a.id - b.id)
]
});
default:
return state;
}
}
export default combineReducers({TodoApp, EditButton})
/* APP */
import React, { Fragment } from 'react';
import TodoFormAdd from '../Containers/TodoFormAdd';
import TodoListAdd from '../Containers/TodoListAdd';
import TodoFormFilterAdd from '../Containers/TodoFormFilterAdd';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<Fragment>
// console.log(this.props.state.getState()) - work!
<TodoFormAdd />
<TodoListAdd store={this.props.store} />
<TodoFormFilterAdd />
</Fragment>
);
}
}
export default App;
/* CONTAINER */
import { connect } from 'react-redux';
import TodoList from '../Components/TodoList/TodoList';
import { DeleteTodo } from '../Actions/AddTodo'
// console.log(this.props.state.getState()) - does not work!
const mapStateToProps = state => ({
todos: state.todos
});
const mapDispatchToProps = dispatch => ({
todoFormDelete: todo => dispatch(DeleteTodo(todo))
});
export default connect(
mapStateToProps,
mapDispatchToProps)(TodoList)
/* COMPONENT */
import React from 'react';
import TodoIteam from '../TodoIteam/TodoIteam'
class TodoList extends React.Component {
handleDelete = (e) => {
let target = e.target;
let closestDelete = target.closest('span');
let closestEdit = target.closest('button');
if (closestDelete) {
let index = closestDelete.parentNode.getAttribute('index');
this.props.todoFormDelete(index);
} else {
return
}
}
render(props) {
// console.log(this.props.state.getState()) - does not work!
return (
<ul onClick={this.handleDelete}>{this.props.todos.map((iteam, index) =>
// this where I get an error
<TodoIteam key={index} index={iteam.id} {...iteam} />
)}
</ul>
);
}
}
export default TodoList;
As you are using ES6 property shorthand notation in combineReducers :
combineReducers({TodoApp, EditButton})
This is equivalent to writing combineReducers({ TodoApp: TodoApp, EditButton: EditButton })
But inside your CONTAINER you are accessing state.todos there is nothing called todos coming from state instead its TodoApp and Hence you get error in your .map():
this.props.todos.map((iteam, index) {}
EDIT :
As you are returning an object containing an array from your reducers called todos so to access correct state you need to use reducer Name followed by an array name you are returning which would be TodoApp.todos
So inside your Container you need to access correct reducer
const mapStateToProps = state => ({
todos: state.TodoApp.todos // Notice TodoApp is used instead of todos
});
You can read more about combineReducers on Redux Documentation
I have React with Redux and Electron project. I try to save current screen id to redux and get the saved state on next screen. The problem is, that when I use getSettings, the return value should be my saved state:
Object{settings: Object}
but is action's object:
Object{type: "GET_SETTINGS", payload: ""}
When I put console log to reducer_settings.js, it show correct state. So it seems it is something with binding the getSettings method. Thanks for your help
containers/screen_picker.js:
import React, {Component} from 'react';
import Navigation from "../components/navigation";
const {desktopCapturer, ipcRenderer} = require('electron');
import {connect} from 'react-redux';
const domify = require('domify')
import App from '../components/app'
import {bindActionCreators} from 'redux';
import {setSettings, getSettings} from "../actions/index";
class ScreenPicker extends App {
constructor(){
super();
this.showPicker();
}
showPicker(){
ipcRenderer.send('show-picker', { types: ['screen'] });
ipcRenderer.on('get-sources', (event, options) => {
desktopCapturer.getSources(options, (error, sources) => {
if (error) throw error
let sourcesList = document.querySelector('.capturer-list')
for (let source of sources) {
let thumb = source.thumbnail.toDataURL()
if (!thumb) continue
let title = source.name.slice(0, 20)
let item = `<li><img src="${thumb}"><span>${title}</span></li>`
sourcesList.appendChild(domify(item))
}
let links = sourcesList.querySelectorAll('a')
for (let i = 0; i < links.length; ++i) {
let closure = (i) => {
return (e) => {
e.preventDefault()
// ipcRenderer.send('source-id-selected', sources[i].id)
// sourcesList.innerHTML = ''
this.props.setSettings({
screenId: sources[i].id
});
}
}
links[i].onclick = closure(i)
}
})
})
}
render() {
return (
<div className="window-wrapper">
<div className="main-content">
<div className="capturer-container dn">
<div className="cr">
<p className="mbl">Select the window you want to share:</p>
<ul className="capturer-list"></ul>
</div>
</div>
</div>
<Navigation nextRouteUrl="/camera-test" backRouteUrl="/" />
</div>
)
}
}
function mapStateToProps(state){
return {
settings: state.settings
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({setSettings, getSettings}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ScreenPicker);
containers/camera_test.js
const {ipcRenderer} = require('electron');
import React, {Component} from 'react';
import Navigation from "../components/navigation";
import {connect} from 'react-redux';
import App from '../components/app'
import {bindActionCreators} from 'redux';
import {getSettings} from "../actions/index";
class CameraTest extends App {
constructor(){
super();
}
componentDidMount() {
console.log("settings in camera test start");
console.log(this.props.getSettings());
console.log("settings in camera test end");
ipcRenderer.send('stepWindow:create', { });
}
render() {
return (
<div className="window-wrapper">
<div className="main-content">
CameraTest div
</div>
<Navigation nextRouteUrl="/camera-test" backRouteUrl="/screen-picker" />
</div>
)
}
}
function mapStateToProps(state){
return {
settings: state.settings
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({getSettings}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(CameraTest);
reducers/reducer_settings.js:
import {GET_SETTINGS, SET_SETTINGS} from "../actions/index";
export default function (state = {},action) {
let newState;
switch (action.type){
case GET_SETTINGS:
console.log("reducer GET_SETTINGS");
console.log(state);
return state;
case SET_SETTINGS:
newState = { ...state, ["settings"]: action.payload };
console.log("Start newstate");
console.log(newState);
console.log("End newstate");
return newState;
default:
return state
}
}
actions/index.js
export const SET_SETTINGS = 'SET_SETTINGS';
export const GET_SETTINGS = 'GET_SETTINGS';
export function setSettings(values, callback){
return {
type: SET_SETTINGS,
payload: values
}
}
export function getSettings(){
console.log("actions#getSettings");
return {
type: GET_SETTINGS,
payload: ""
}
}
you dont need the getSetting action creator.
in your component did mount access the settings like this.
componentDidMount() {
console.log("settings in camera test start");
const { settings} = this.props;
console.log(settings);
console.log("settings in camera test end");
ipcRenderer.send('stepWindow:create', { });
}
assuming your object is called settings. normally the object is given the name you are exporting in the reducer. so if you are not able to see an object called settings in props, you need to give your reducer function a name
export default function settings (state = {},action) {
let newState;
switch (action.type){
case GET_SETTINGS:
console.log("reducer GET_SETTINGS");
console.log(state);
return state;
case SET_SETTINGS:
newState = { ...state, ["settings"]: action.payload };
console.log("Start newstate");
console.log(newState);
console.log("End newstate");
return newState;
default:
return state
}
}
EDIT: its mapStateToProps which gives the object name is props.
Im not 100% sure if it is working correct, but it does noet give the result of the video course that I followed.
The renderPosts is just suppose to render the list, but instead it get a blank array the first time round. and when mapStateToProps is called the second time, the array is filled with the expected values.
it is as if the first time mapStateToProps is invoked, it did not pass through the action creator first or something.
COMPONENT
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
// console.log("renderPosts - this.props.posts",this.props.posts);
if(this.props.posts){
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
ACTION
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export const CREATE_POST = 'CREATE_POST';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=qwerty123';
export function fetchPosts(){
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
export function createPost(props) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return{
type: CREATE_POST,
payload: request
}
}
REDUCER
import { FETCH_POSTS } from '../actions/index';
const INITIAL_STATE = { postsList:[], post:null };
export default function(state = INITIAL_STATE, action){
console.log("action.type",action.type);
switch (action.type) {
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
default:
return state;
}
}
mapStateToProps is called twice. on the initial call the array is empty. on the second call I have my ten posts inside the array.
Problem is that it seems to want to render the first array and ignores the second
I have put an consol.log in the
renderPosts
and
mapStateToProps
and it renders as follows.
Console
any Ideas?
I think the error is coming from the way you handle the Promise. The first time you see the mapStateToProps in the console you can see you have no data so this is PENDING, the second is when it's FULFILLED. You need to find a way to handle this.
Example but not the best, I think you can just change you if statement.
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.props.posts !== [] this.renderPosts() : <h1>Loading...</h1>}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
The second one should be by changing the way you do the promise. A good library is redux-promise-middleware
This is a example of my app what I did.
Actions
export const reqAllGames = games => {
const promise = new Promise((resolve, reject) => {
request
.get(`${config.ROOT_URL}/${config.API_KEY}`)
.end((err, res) => {
if (err) {
reject(err);
} else {
resolve(res.body.top);
}
});
});
return {
type: types.RECEIVE_ALL_GAMES,
payload: promise
};
};
Reducer
import * as types from "../constants/";
const gameReducer = (games = { isFetched: false }, action) => {
switch (action.type) {
case `${types.RECEIVE_ALL_GAMES}_PENDING`:
return {};
case `${types.RECEIVE_ALL_GAMES}_FULFILLED`:
return {
games: action.payload,
err: null,
isFetched: true
};
case `${types.RECEIVE_ALL_GAMES}_REJECTED`:
return {
games: null,
err: action.payload,
isFetched: true
};
default:
return games;
}
};
export default gameReducer;
Component
const Games = ({ games, err, isFetched }) => {
if (!isFetched) {
return <LoadingCircular />;
}
else if (err === null) {
return (
<div>
<GamesList games={games} />
</div>
);
} else {
return <h1>Games not find!</h1>;
}
};
const mapStateToProps = (state) => state.games;
export default connect(mapStateToProps)(Games);
If you using react-router you can use the onEnter api and do the actions right here. With that you know your component gonna get the post. A good tutorial is this one from RallyCoding https://www.youtube.com/watch?v=JicUNpwLzLY
Hope that can help you
https://www.udemy.com/react-redux/learn/v4/questions/1693796
In your reducer you're assigning the list of posts to the key postsList.
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
We can confirm that they are properly being assumed to postsList by looking at the mapStateToProps console log you have in your screenshot.
Your mapStateToProps, however, is looking at the property state.posts.all
return {posts: state.posts.all}
The list of posts are not assigned to the all property, they are assigned to the postsList property. This is why you don't see the updated list of posts in your component. You'll need to update either the property the reducer is placing the list of posts on or update your mapStateToProps to pull the list of posts from the correct property.
-Stephen Grider