I am reusing the same reducer logic for two different events. The idea is to toggle a class depending on which text you clicked on. Each event fires, but my object is not toggling. Any thoughts?
App:
import React from "react"
import { bindActionCreators } from 'redux'
import { connect } from "react-redux"
import * as toggleactionCreators from '../actions/toggleActions';
function mapStateToProps(state) {
return {
hiddenA: state.toggleA.hidden,
hiddenB: state.toggleB.hidden
}
}
function mapDispachToProps(dispatch) {
return bindActionCreators({...toggleactionCreators}, dispatch)
}
class Main extends React.Component {
toggleDiv() {
this.props.toggleDiv();
console.log(this.props)
}
render() {
const { hiddenA, hiddenB } = this.props;
return (
<div>
<div>
<h3 onClick={this.toggleDiv.bind(this)} className={ hiddenA ? null : "toggled"} >Good Day!</h3>
<h1 onClick={this.toggleDiv.bind(this)} className={ hiddenB ? null : "toggled"} >Hello There!</h1>
</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispachToProps)(Main);
Index Reducer:
import { combineReducers } from "redux"
import toggle from "./toggleReducer"
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const {name} = action;
const isInitializationCall = state === undefined;
if(name !== reducerName && !isInitializationCall) return state;
return reducerFunction(state, action);
}
}
const thereducer = combineReducers({
toggleA : createNamedWrapperReducer(toggle, 'A'),
toggleB : createNamedWrapperReducer(toggle, 'B'),
});
export default thereducer;
toggleReducer:
const toggle = (state = { hidden: true}, action) => {
switch(action.type) {
case 'TOGGLE_DIV':
return Object.assign({}, ...state, {hidden: !state.hidden});
default:
return state;
}
};
export default toggle;
toggleAction:
export const toggleDiv = () => {
return {
type: 'TOGGLE_DIV',
}
}
This is how I would debug this.
Download Redux DevTools for your browser. This is the URL for chrome: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
Download React devtools for you browser. This is the URL for chrome: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
Look in Redux Devtools:
Is the action emitted from your action creator
Does the reducer update the state correctly?
If both the actions, and reducers looks correctly, check your React component:
Does the component receive the correct props? If yes, it's something with how the props are rendered. If no, it's something with how the store is connected to your component.
Hope this debugging tutorial is useful for you. If you have any follow up questions, please don't hesitate to ask :)
Related
I am trying to build the todoList using react, redux, redux-form and firestore database, I am able to insert the todo inside the database using actions or action creator also, I am able to fetch the data store in database but I am unable to show data on the website when the user first visits the website, also when user add todo and submit the form the first todo item shows to me blank!. Can someone help me to tell what's wrong with my code?
App.js
import React from 'react'
import { Container} from 'react-bootstrap'
import { connect } from 'react-redux'
import InputForm from './InputForm'
import Todo from './Todo'
import {listTodo} from '../actions'
class App extends React.Component {
constructor(props){
super(props);
this.renderList = this.renderList.bind(this);
}
componentDidMount(){
this.props.listTodo();
console.log(this.props.list);
}
renderList(){
return this.props.list.map((todo,id) => {
return (
<Todo todo={todo} key={id}/>
)
})
}
render() {
console.log("rendered");
if(!this.props.list){
return <div>Loading...</div>
}
return (
<Container fluid>
<h1 className="text-center" style={{marginTop: "5vh"}}>Todo App</h1>
<InputForm />
{this.renderList()}
</Container>
)
}
}
const mapStateToProps = (state) => {
console.log(state.todoList);
return {
list: state.todoList
}
}
export default connect(mapStateToProps, {listTodo})(App)
Todo.js
import React from 'react'
import { Card } from 'react-bootstrap'
class Todo extends React.Component {
render(){
console.log(this.props.todo);
return (
<Card className="text-center">
<Card.Body>
<Card.Text>{this.props.todo.todo}</Card.Text>
</Card.Body>
<Card.Footer className="text-muted">DeadLine by {this.props.todo.date}</Card.Footer>
</Card>
)
}
}
export default Todo
action(index.js)
import database from '../firebase/firebase'
export const addTodo = (inputValue) => {
database.collection('Todo').add({
todo: inputValue.todo_input,
date: inputValue.todo_date
});
return {
type: "ADD_TODO",
payload: inputValue
};
}
export const listTodo = () => {
const data = [];
database.collection('Todo').onSnapshot(snapshot => snapshot.docs.map(doc => data.push(doc.data())));
return {
type: 'LIST_TODO',
payload: data
}
}
export const deleteTodo = id => {
//delete todo
return {type: 'DELETE_TODO'}
}
TodoReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload];
case 'LIST_TODO':
return action.payload;
case 'DELETE_TODO':
return state.filter(item => item.id !== action.payload);
default:
return state;
}
}
reducers.js
import { combineReducers } from "redux";
import {reducer as formReducer} from 'redux-form'
import todoReducer from './TodoReducer'
export default combineReducers({
form: formReducer,
todoList: todoReducer
});
In your listTodo action creator, you create an object data and return that as part of your LIST_TODO action. This data object is then persisted into your store and then manipulated by your onSnapshot callback to add items to that data after the fact.
Those changes cannot be detected by React-Redux and so your component does not update by itself with content.
export const listTodo = () => {
const data = [];
// do never do something like this!
database.collection('Todo').onSnapshot(snapshot => snapshot.docs.map(doc => data.push(doc.data())));
return {
type: 'LIST_TODO',
payload: data
}
}
This breaks the second Redux Principle "The only way to change the state is to emit an action, an object describing what happened".
To do something like this, you would need to use a middleware, most commonly the redux-thunk middleware.
That is already included in the official Redux Toolkit, which I would generally recommend you to check out.
Since showing you how to do this correctly would turn into a 1:1 copy of the official tutorials, check those out instead:
for old-style "Vanilla Redux": Redux Fundamentals, Part 6: Async Logic and Data Fetching
for "Modern Redux" with Redux Toolkit: Redux Essentials, Part 5: Async Logic and Data Fetching
import React from 'react';
import './search.styles.scss';
import { connect } from 'react-redux';
import { setSearchField } from '../../redux/search/search.actions';
import { combineReducers } from 'redux';
class Search extends React.Component{
render(){
const { onSetSearchField, search } = this.props;
return (
<div className="search-container">
<p>user: {search}</p>
<input className="search-box" type="text" onChange={onSetSearchField} />
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSetSearchField: (event) => dispatch(setSearchField(event.target.value))
}
}
const mapStateToProps = (state) => {
console.log(state.search, "-----------")
if(state.search === "user:"){
alert(state.search)
}
return {
search: state.search
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Search);
reducer:
const initialStateSearch = {
search: null
}
export const getSearchType = (state=initialStateSearch, action={}) => {
switch (action.payload) {
case 'user':
return Object.assign({}, state, {search: action.payload})
case 'post':
return Object.assign({}, state, {search: action.payload})
default:
return state
}
}
action:
import { SearchActionTypes } from './search.types';
export const setSearchField = (text) => ({
type: SearchActionTypes.SEARCH_START,
payload: text
})
Here I'm adding search functionality using react and redux
But when I'm checking my payload in the reducer. It is coming but it is not coming in my search component.
I am alerting and trying to alert and display the text on a p tag
Please have a look
As I mentioned in a comment to your question, the code you showed for your component and redux looks fine, and the fact that you can see your redux store being populated makes me believe the problem lies with the configuration.
Have you tried making sure the Provider is a wrapper at the root level, so the components will know about the redux store https://react-redux.js.org/api/provider? If that exists, then I would advise looking at the redux example https://codesandbox.io/s/9on71rvnyo online and try to follow there logic and see what potentially you could be missing.
Can you confirm if you have used the same constant in Action Creator setSearchField for which you are checking in reducer. Is SearchActionTypes.SEARCH_START === 'user' or 'post'.
I'm wanting to update my trending array with the results calling the tmdb api. I'm not sure if im going about this the wrong way with calling the api or if im messing up somewhere else along the way. So far I've really been going in circles with what ive tried. Repeating the same things and not coming to a real solution. Havent been able to find another question similar to mine.
my actions
export const getTrending = url => dispatch => {
console.log("trending action");
axios.get(url).then(res =>
dispatch({
type: "TRENDING",
payload: res.data
})
);
};
my reducer
const INITIAL_STATE = {
results: [],
trending: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case "SEARCH_INFO":
return {
results: [action.payload]
};
case "TRENDING":
return { trending: action.payload };
default:
return state;
}
};
and my component im trying to get the results from
import React, { Component } from "react";
import Trending from "./Treding";
import "../App.css";
import { getTrending } from "../actions/index";
import { connect } from "react-redux";
export class Sidebar extends Component {
componentDidMount = () => {
const proxy = `https://cors-anywhere.herokuapp.com/`;
getTrending(`${proxy}https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
render() {
return (
<div>
<h3 className="trending">Trending</h3>
{
this.props.trending ? (
<Trending movies={this.props.trending} />
) : (
<div>Loading</div>
)}
</div>
);
}
}
const mapStateToProps = state => {
return {
trending: state.trending
};
};
export default connect(mapStateToProps)(Sidebar);
Since you are directly calling the getTrending without passing it to connect method, it might be the issue.
Instead that you can pass getTrending to connect method so it can be available as props in the component. After that it can be dispatched and it will be handled by redux/ redux-thunk.
export default connect(mapStateToProps, { getTrending })(Sidebar);
And access it as props in the component.
componentDidMount = () => {
// const proxy = `https://cors-anywhere.herokuapp.com/`;
this.props.getTrending(`https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
Click the login button
Auth's action is called
Reducer called
Connect mapDispatchToProps is called
However, it is not redrawn
I am in trouble because the render method of React.Component 5 is not called and redrawing is not executed.
After reading this article, I think that using Render.Component's Object.assign should call Render of React.Component.
But it does not work.
Where is wrong?
app.js
import { connect } from 'react-redux'
import App from '../components/app'
function mapStateToProps(state){
return {login: state.login}
}
function mapDispatchToProps(dispatch) {
return {
dispatch
};
}
export default connect(mapStateToProps,mapDispatchToProps)(App);
components/app.js
import React from 'react'
import { Link } from 'react-router'
import { auth } from '../actions/auth'
export default class App extends React.Component {
render() {
const { dispatch } = this.props;
return (
<div className="columns is-gapless">
<div className="column is-10 content">
<div className="content-body">
<div className="has-text-right">
{(this.props.login)?"TRUE":"FALSE"}
<button className="button is-primary" onClick={()=>dispatch(auth("test#test.com","aaaa"))}>login</button>
</div>
</div>
</div>
</div>
);
}
}
actions/auth.js
import {constant} from './constant';
export function auth(email,password) {
return {
type: constant.ACTION.AUTH,
email: email,
password:password
}
}
reducer
import {constant} from './actions/constant';
const initialState = {
login: false
};
export default function reducersIndex(state = initialState, action) {
console.log("reducers");
if (typeof state === 'undefined') {
return 0
}
switch (action.type) {
case constant.ACTION.AUTH:
return Object.assign({}, state,{
login:!state.login
});
default:
return state
}
}
Your state contains the property login, which is set in your reducer. In your component, you are also checking this.props.login.
However, in mapStateToProps, you are mapping state.state to the prop state, leaving this.props.login === undefined. For this reason, {(this.props.login)?"TRUE":"FALSE"} will always evaluate to "FALSE".
To resolve this, map login from your state to the prop login in your container:
function mapStateToProps(state){
return {login: state.login}
}
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