My Redux state is getting changed, but it doesn't re-render - reactjs

I have a parent component and a child component. I can see that the reducer is getting an update (Please refer to screenshot) but it is not re-rendering the component. I am calling some API, and once the response comes, it will just update some variable and re-render the component.
Parent component
import React, { Component } from 'react';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import {connect} from "react-redux";
import { bindActionCreators } from 'redux'
import ProfileInfo from '../components/Profile/ProfileInfo'
import Details from '../components/Profile/Details'
import UnitSpec from '../components/Profile/UnitSpec'
import * as profileActionCreators from "./../actions/profileActions";
import * as StaticData from '../utils/country'
class NewProfilePage extends Component {
constructor(props) {
super(props);
this.state = { tabIndex: 0 ,
countryList: StaticData.Country,
};
let { dispatch } = this.props
this.actions = bindActionCreators({...profileActionCreators} , dispatch);
}
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps)
}
render() {
return (
<div className="admin-holder">
<div className="row profile-row">
<div className="small-12 large-4 columns">
<ProfileInfo></ProfileInfo>
</div>
<div className="small-12 large-8 columns">
<Tabs selectedIndex={this.state.tabIndex} onSelect={tabIndex => this.setState({ tabIndex })}>
<TabList>
<Tab>Details</Tab>
<Tab>Unit Spec</Tab>
<Tab>Imaginery</Tab>
<Tab>Menu</Tab>
</TabList>
<TabPanel>
<Details updateDetails = {(userDetail) => this.actions.updateDetails(userDetail)} countryList={this.state.countryList}/>
</TabPanel>
<TabPanel>
<UnitSpec staticData={StaticData}/>
</TabPanel>
<TabPanel>
</TabPanel>
<TabPanel>
</TabPanel>
</Tabs>
</div>
</div>
</div>
);
}
}
const mapStateToProps= (state) => {
console.log('state', state)
return{
profileReducer: state.profileReducer
};
};
export default connect(mapStateToProps)(NewProfilePage);
Reducer
import * as types from '../constant';
const initialCommonState = {
countryList: [],
loadingCountryList: false,
isProfileUpdated: false
}
const profileReducer = (state=initialCommonState, action) => {
switch(action.type){
case types.UPDATE_DETAILS + "_FULFILLED":
const response = action.payload;
return Object.assign({}, state, {
isProfileUpdated: true
});
case types.UPDATE_DETAILS + "_PENDING":
return Object.assign({}, state, {
isProfileUpdated: false
});
case types.UPDATE_DETAILS + "_REJECTED":
return Object.assign({}, state, {
isProfileUpdated: false
});
default :
return state
}
}
export default profileReducer;
Please look into screenshot as well and you can see "isProfileUpdated" is changed to true
Store
import {createStore,combineReducers,applyMiddleware} from 'redux'
import logger from 'redux-logger';
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import reducers from '../reducers';
const middleware = [];
if (process.env.NODE_ENV === 'development') {
middleware.push(logger);
}
export default createStore(
combineReducers({reducers}),
{},
//applyMiddleware(logger(),thunk , promise())
applyMiddleware(logger, thunk , promise())
)

Your state contains profile
const mapStateToProps= (state) => {
console.log('state', state)
return{
profileReducer: state.profile
};
};

Related

State is empty when using mapStateToProps

The state is empty when I try to map the state from the store to the properties of a component. I try to get a value of a string displayed in JSX but it is missing. I cant manage to get anything to display from the redux store.
Reducer:
const initialState = {
visibles: "false",
error: null,
text: ""
};
const rootReducer = (
state = initialState,
action
) => {
switch (action.type) {
case "OPEN_MODAL":
return {
...state,
visibles: "true",
error: null
};
default:
return state;
}
}
export default rootReducer;
and index.js
import {createStore } from "redux";
import {Provider } from "react-redux";
import rootReducer from "./components/Redux/Reducer";
const store = createStore(rootReducer);
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
consumer of the redux store
import React, { Component } from 'react'
import {connect} from "react-redux";
import styles from "./modal.module.css";
export class Modal extends Component {
render() {
console.log(this.props)
return (
<div className={styles.root}>
<p className={styles.title}>{this.props.visible}</p>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
visible: state.visibles
}
}
export default connect(mapStateToProps)(Modal)
Found the reason why. I had to refactor the Modal class to not use "export class" and then I could get the state from the store with connect.
class Modal extends React.Component {
render() {
console.log(this.props)
return (
<div className={styles.root}>
<p className={styles.title}>{this.props.visible}</p>
</div>
)
}}

Action Not reaching Reducer in React redux

I have a snippet of code that I want to use to call an api, but before show a loading screen. For some reason in my code below I cannot seem to get the action REQUEST_GAMES to hit my combined gameReducer. I have included all the code below. Any reason as to why the action type is not being picked up by the reducer? I am not sure what i am doing wrong. Have i not connected it to my component correctly? The redux logger is showing that action is being called.
AddGame.js (component)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
import { getIsFetching, getVisibleGames } from '../reducers'
class AddGame extends Component{
constructor(props){
super(props);
this.state = {term: ''}
this.onInputChange = this.onInputChange.bind(this)
this.onFormSubmit = this.onFormSubmit.bind(this)
}
onInputChange(event){
this.setState({term:event.target.value})
}
onFormSubmit(event){
const { requestGames, fetchGames } = this.props;
event.preventDefault();
// we need to go fetch weather data
requestGames();
fetchGames(this.state.term);
this.setState({term:'' })
}
renderContent(){
const { isFetching , games} = this.props
if (isFetching && !games.length){
return <p>Loading</p>
} else if (games){
return <div>{games.map(this.getGame)}</div>
}
}
styleCSS = {
padding:'20px'
};
getGame(data){
return(
<div>
<pre key={data.id}>{data.name}</pre>
<img src ={data.cover.url} alt = "" />
</div>
)
}
render(){
return(
<div style={this.styleCSS}>
<form onSubmit={this.onFormSubmit}>
<input
value={this.state.term}
onChange={this.onInputChange}/>
<button type="submit">
Search
</button>
{this.renderContent()}
</form>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isFetching :getIsFetching(state),
games:getVisibleGames(state),
}
}
export default connect(mapStateToProps,actions)(AddGame);
index.js (reducer)
import { combineReducers } from 'redux';
import authReducers from './authReducer';
import { reducer as formReducer } from 'redux-form';
import gameReducer, * as fromGames from './gameReducer';
const allReducers= combineReducers({
auth: authReducers,
form: formReducer,
game: gameReducer
});
export default allReducers;
export const getIsFetching = (state) => fromGames.getIsFetching(state.game);
export const getVisibleGames = (state)=> fromGames.getGames(state.game)
gameReducer.js
import { REQUEST_GAME, FETCH_GAME } from '../actions/types';
import { combineReducers } from 'redux';
const gameReducer = () => {
const games = (state=[], action) => {
console.log(action);
switch(action.type){
case FETCH_GAME:
return action.payload || false;
default:
return state;
}
};
const isFetching = (state = false, action) => {
switch (action.type) {
case REQUEST_GAME:
return true;
case FETCH_GAME:
return false;
default:
return state;
}
};
return combineReducers({
games,
isFetching
});
};
export default gameReducer;
export const getIsFetching = state => state.isFetching
;
export const getGames = state => state.games;
actions.js
import axios from 'axios';
import { FETCH_USER, REGISTER_USER, FETCH_GAME, REQUEST_GAME } from './types';
export const fetchUser = ()=> async dispatch=>{
const res = await axios.get('/api/current_user');
dispatch({type:FETCH_USER, payload:res.data});
console.log('fetchuser:',res.data)
};
export const fetchGames = (search)=> async dispatch=>{
const proxy = 'https://still-eyrie-36200.herokuapp.com/'
const res = await axios.get(`${proxy}https://api-2445582011268.apicast.io/games/?search=${search}&fields=name,category,genres,game_modes,cover,first_release_date,summary`,{
headers: {
'user-key':'18430b84d6bfaab720b08eeda8f2810d',
'Accept':'application/json',
'Content-Type':'application/json',
}
})
dispatch({type:FETCH_GAME, payload:res.data});
console.log('gamedata:',res.data)
};
export const requestGames = () =>({
type: REQUEST_GAME
})
The REQUEST_GAME action is probably processed by the reducer. After requestGame(), fetchGames() is called immediately. fetchGames() changes the state back to false. And both these actions happen in the same function block. So, there is no chance for the prop changes to cause a component re-render.

Child component not connecting to store

I have a component that connects to a store and displays a child component like below:
render() {
return <div>
<div className="userBox">
<ProfilePhoto userid={this.props.id} />
</div>
<div className="nameTitleBox">
<div className="firstLastTitle">
<h1>{this.props.firstName} {this.props.lastName}</h1>
</div>
<IDBox userid={this.props.id} />
</div>
<div className="childcomponent">
<childComponent />
</div>
<div className="profileBox">
<EditInterests interestsList={this.props.interest} />
</div>
</div>
}
}
export default connect(
(state) => state.user,
UserState.actionCreators
)(User);
I want the child component to be a smart component that loads it's own data and controls everything itself. The code for it is pretty simple.
import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import { ApplicationState } from '../../store';
import { connect } from 'react-redux';
import * as ChildState from '../../store/childStore';
export class ChildComponent extends React.Component {
componentWillMount() {
this.props;
}
render() {
return (<div>
<div className="textCenter"><h2 id="sss">{this.props.text}</h2></div>
<div className="textRight">
<input type="button" className="button" value="Yes" /> <b className="textColor">No</b>
</div>
</div>
</div>
</div>)
}
}
const mapDispatchToProps = (dispatch) => {
return {
action: dispatch(ChildState.actionCreators.requestChildren())
}
}
export default connect(
mapDispatchToProps,
ChildState.actionCreators
)(ChildComponent);
this.props in the child component is always an empty object. Nothing from the child state is in there, the initial state, the actions, dispatch...anything. I've tried a few different things. ChildState loads fine if I actually load it in the parent. Don't know why it's not loading in the child and connecting the props.
Adding the store below:
import { Action, Reducer } from 'redux';
import { fetch, addTask } from 'domain-task';
import { AppThunkAction } from './';
export const actionCreators = {
requestChildren: () => (dispatch, getState) => {
let url = 'random';
var myheaders = new Headers();
myheaders.append("X-Requested-With", "XMLHttpRequest");
let fetchTask = fetch(url, {
headers: myheaders,
credentials: "same-origin"
})
.then(response => response.json())
.then(data => {
dispatch({ type: 'POST_ACTION', children: data });
});
addTask(fetchTask);
}
}
export const initialState = { ... };
export const reducer = (state = initialState, incomingAction) => {
const action = incomingAction;
switch (action.type) {
case 'REQUEST_ACTION':
return {
...
};
case 'POST_ACTION':
return {
...
};
default:
}
return state || initialState;
};
I believe the problem is in mapDispatchtoProps have you tried using bindActionCreators
bindActionCreators make sure action (ChildState.actionCreators.requestChildren) flows through the middleware if there is any and then to the reducers
import { bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
ChildState.actionCreators.requestChildren}, dispatch); }
export default connect(
ChildState.actionCreators,
mapDispatchToProps
)(ChildComponent);
This was happening because I was exporting both the child component and the connect function. I removed the export on the child component and its working now as expected.

'dispatch' is not defined

I've been debugging this program but with no clue, I followed this tutorial word by word trying to make a TODO app but I could not figure out why I am getting this error message.
./src/containers.js
Line 12: 'dispatch' is not defined no-undef
Line 13: 'dispatch' is not defined no-undef
components.js
import React from 'react'
class Todo extends React.Component {
render() {
const { todo } = this.props
if (todo.isDone) {
return <strike>{todo.text}</strike>
} else {
return <span>{todo.text}</span>
}
}
}
export default class TodoList extends React.Component {
render() {
const {todos, toggleTodo, addTodo } = this.props
console.log(toggleTodo)
return (
<div className="todo">
<input type="text" placeholder="Add todo"/>
<ul className='todo__list'>
{todos.map(t => (
<li key={t.id} className='todo__item'>
<Todo todo={t} />
</li>
))}
</ul>
</div>
)
}
}
containers.js
import * as components from './components.js'
import { addTodo, toggleTodo } from './actions.js'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
return {todos: state}
}
const mapDispatchToProps = (state) => {
return {
addTodo: text => dispatch(addTodo(text)),
toggleTodo: id => dispatch(toggleTodo(id))
}
}
const TodoList = connect(mapStateToProps, mapDispatchToProps)(components.TodoList)
export default TodoList
reducers.js
const reducer = (todos = [], action) => {
switch(action.type) {
case 'ADD_TODO': {
return [...todos, {id: action.id, text: action.text, completed: false}]
}
case 'TOGGLE_TODO': {
return todos.map(todo => todo.id === action.id ? {...todo, completed: !todo.completed} : todo)
}
default: {
return todos
}
}
}
export default reducer
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { TodoList } from './containers'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import reducer from './reducers'
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<TodoList />
</Provider>,
document.getElementById("app")
)
Instead of state here:
mapDispatchToProps = (state) =>
Use dispatch:
mapDispatchToProps = (dispatch) =>
In containers.js.
DOC:
container components can dispatch actions. In a similar fashion, you
can define a function called mapDispatchToProps() that receives the
dispatch() method and returns callback props that you want to inject
into the presentational component.

How to re-render components when change in store in Redux?

I'm trying to make my first application with Redux, I've already made a version of this without Redux and I know that Redux isn't necessarily needed for this but I want to learn Redux.
I have a store which has an array of to-do items, my action successfully dispatches and updates the store.
My list of task components connects to the store and renders each item in the array as its own component.
On initial load, my to-do list shows the to-do items from the store's initial state, but once I update the state the new items from the state do not get rendered. Instead the map method to return the array of components says it 'cannot read property 'map' of undefined'.
How do I solve this?
Cheers.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Container from './components/Container.js'
import TaskReducer from './reducers/Task.js'
require("./index.css");
const defaultState = {
items: [
"task 1",
"task 2"
]
};
const store = createStore(TaskReducer, defaultState);
// Allows access to store in console log
window.store = store;
ReactDOM.render( (
<Provider store={store}>
<Container />
</Provider>
),
document.getElementById('wrapper')
);
import React from 'react';
import ReactDOM from 'react-dom';
import TaskList from './TaskList.js';
import { createStore, bindActionCreators } from 'redux';
import * as ActionCreators from '../actions/Task.js';
import Redux from 'redux';
import {connect} from 'react-redux'
class Container extends React.Component {
constructor() {
super();
}
render() {
// What does this do???
const {dispatch} = this.props;
const deleteItem = bindActionCreators(ActionCreators.deleteTodoItem, dispatch);
const addItem = bindActionCreators(ActionCreators.addTodoItem, dispatch);
function _onSubmit(e) {
e.preventDefault();
addItem(e.target.elements.task.value);
// Resets the form
e.target.reset();
}
return (
<div className="">
<header className="header">
<h1>To Do:</h1>
</header>
<form autoComplete="off" onSubmit={_onSubmit}>
<input name="task" placeholder="Task" autoComplete="off"></input>
</form>
<TaskList />
</div>
);
}
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(Container);
import React from 'react';
import Task from './Task';
import { connect } from 'react-redux';
let TaskList = (props) => {
console.log('items', props.items);
var tasks = (props.items).map( (item, key) => { return <Task data={item} key={key} listItemKey={key} /> })
return(
<ul className="task-list">
{tasks}
</ul>
);
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(TaskList);
<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>
import * as action from '../actions/Task.js'
export default function toDoItems(state = [], action) {
switch(action.type) {
case 'DELETE_ITEM':
return [
...state,
];
case 'ADD_ITEM':
console.log('ADD ITEM');
console.log('Submitted value = ', action.submittedValue)
return [
...state,
// Reducer gets action object item and appends to array
action.submittedValue
]
default:
return state;
}
}
--- Reducer ---
import * as action from '../actions/Task.js'
export default function toDoItems(state = [], action) {
switch(action.type) {
case 'DELETE_ITEM':
return [
...state,
];
case 'ADD_ITEM':
console.log('ADD ITEM');
console.log('Submitted value = ', action.submittedValue);
console.log('the state', state);
return [
...state,
// Reducer gets action object item and appends to array
action.submittedValue
]
default:
return state;
}
}
--- Action ---
export function addTodoItem(submittedValue) {
return {
type: 'ADD_ITEM',
// The action object returned has the submittedValue
submittedValue
}
}
export function deleteTodoItem() {
return {
type: 'DELETE_ITEM',
}
}
I have edited the TaskList component. You are not using the map function correctly
import React from 'react';
import Task from './Task';
import { connect } from 'react-redux';
let TaskList = (props) => {
console.log('items', props.items);
var tasks = undefined;
if(props.items && props.items.length > 0 ){
tasks = props.items.map( (item, key) => { return <Task data={item}
key={key} listItemKey={key} /> })
} //edited code
return(
<ul className="task-list">
{tasks}
</ul>
);
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(TaskList);
<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>
And as for the store use the following to configure store in your main file.
...
import TaskReducer from './reducers/Task.js';
import * as redux from 'redux';
function configure(initialState = {}){
const reducers = redux.combineReducers({
tasks : TaskReducer
});
let store = redux.createStore(reducers, initialState);
return store;
};
const store = configure();
// Allows access to store in console log
window.store = store;
ReactDOM.render( (
<Provider store={store}>
<Container />
</Provider>
),
document.getElementById('wrapper')
);

Resources