Redux state updated but props still received as undefined - javascript

I have the following component which calls a Factory to create both heroes and cells, and once the array is full, pass it to the state variables with the same name.
Field.js
import React,{Component} from 'react';
import { connect } from 'react-redux';
import _ from "lodash";
import Factory from './../Factory/Factory';
import { addHero, addCell } from './../../store/actions/actionsCreator';
class Field extends Component {
componentWillMount(){
let heros = [],
cells = [];
//id=0 will throw a error, always start with 1
for (let i = 1; i < 3; i++) {
heros.push(this.callFactory('hero', i));
}
this.props.addHero(heros);
for (let i = 1; i < 4; i++) {
for (let j = 1; j < 12; j++) {
let cell = this.callFactory('cell', j, i);
cells.push(cell);
}
}
this.props.addCell(cells);
this.movePerson(1, {x: 2, y: 1});
}
callFactory(type, id, number){
return Factory.build(type, id, number)
}
render() {
const {heros,cells} = this.props;
if(heros === undefined) return null;
// console.log(this.props);
return (
<div>
<table>
<tbody>
<tr>
{cells[0]}
</tr>
</tbody>
</table>
{heros[0]}
</div>
);
}
}
const mapStateToProps = state => {
return {
heros: state.heros,
cells: state.cells
}
}
const mapDispatchToProps = dispatch => {
return {
addHero(hero) {
dispatch(addHero(hero));
},
addCell(cell) {
dispatch(addCell(cell));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Field);
There is my reducer file:
index.js (reducer file)
import {createStore } from 'redux'
const initialState = {
heros: [],
cells: []
};
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_HERO':
return {
...state,
heros: [...state.heros, action.hero]
}
case 'ADD_CELL':
return {
...state,
cells: [...state.cells, action.cell]
}
default:
return {
...state
}
}
}
export default createStore(reducer, initialState);
The file with all my actions:
actionCreator.js
const addHero = hero => {
return {
type: 'ADD_HERO',
hero
}
}
const addCell = cell => {
return {
type: 'ADD_CELL',
cell
}
}
export { addHero, addCell };
And my app entry point:
index.js
import registerServiceWorker from './registerServiceWorker';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './styles/index.css';
import Field from './components/Field/Field';
import store from './store/index';
ReactDOM.render(
<Provider store= {store}>
<Field/>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
The key problem here is when I try to log my props value at any point it will be logged as undefined, but if I log the props inside render it will be called 2 times, the first one as undefined and the second one with the updated state.
Is there any way to handle this problem so I can use the props values outside render?

Please look at these two react component cycle.
First one is componentDidMount(). You should this method instead of componentWillMount().
Second one is componentWillReceiveProps(nextProps). With the help of this method, you can see the changes nextProps and currently prop you have.

As #brub and #yasinv pointed, the main problem was calling all the logic inside ComponentWillMount and not ComponentDidMount.
If someone is in the same spot, I decided to pass this information (heros and cells) as props to my Field component, and then trigger ComponentDidMount to treat this data.

Related

Timer React + Redux. React don't dispatch action by timer (SetInterval) in ComponentDidMount

I'm trying to make a timer in my App with React + Redux.
So I have a component parent:
import React, { Component } from "react";
import { connect } from "react-redux";
import { compose } from "redux";
import QuestionCounter from "../question-counter";
import FinishButton from "../finish-button";
import TimeCounter from "../time-counter";
import PauseButton from "../pause-button";
import testFinished from "../../actions/test-finished";
import timerTick from "../../actions/timer-tick";
import setTimer from "../../actions/set-timer";
import totalWithEwStruct from "../hoc/total-with-ew-structure";
import withIndicators from "../hoc/with-indicators";
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
const mapStateToProps = ({ total, loading, error }) => {
return { total, loading, error };
};
const mapDispatchToProps = {
testFinished,
setTimer,
timerTick
}
export default compose(
totalWithEwStruct(),
connect(mapStateToProps, mapDispatchToProps),
withIndicators()
)(Total);
I try use timerTick by timer in componentDidMount
import React, { Component } from "react";
export default class TimeCounter extends Component {
componentDidMount() {
const { setTimer, timerTick } = this.props;
let timer = setInterval(() => {
timerTick();
console.log("tick");
}, 1000);
setTimer(timer);
}
componentDidUpdate() {
const { timeLeft, testFinished } = this.props;
if (timeLeft <= 0) {
testFinished();
}
}
render() {
const { timeLeft } = this.props;
return (
<div className="question-counter__timeleft">
Времени осталось
<span className="question-counter__timer">{timeLeft}</span>
</div>
);
}
}
So I see "tick" - "tick" - "tick" in console, but React doesn't dispatch my timerTick() function to reducer.
I have tried log to console action.type for debugging, and there is no action of timerTick.
const timerTick = () => {
return {
type: "TIMER_TICK"
};
};
export default timerTick;
Its code of action.
I don't understand why it doesn't work.
Your Total component needs to take timerTick function from props which is the one that is linked with redux store as you have added it to mapDispatchToProps.
If you do not destructure it from props, the ccomponent will use the imported function which isn't an action created unless its passed to dispatch function
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft, timerTick } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
You need to add dispatch of timer tick inside timer tick component. Because child component not aware about the actions.
Please refer below link for more details:
https://itnext.io/dispatching-actions-from-child-components-bd292a51f176
Response
if your component is not connected to redux you won’t be able to dispatch any action.
What do I mean?
Example
import React from “react”;
import { connect } from “react-redux”;
class MyCom extensa React.Component {
componentDidMount () {
const { action } = this.props;
action();
}
render () {
.....
}
}
const toState = state => ({....});
const toDispatch = {
action
};
export default connect(toState, toDispatch)(MyCom);
Explains
Basically connect from ”react-redux” is a HOC a high order component that on javascript world: is none but a high order function. a function that return another function.

Action not being fired - react-redux counter example

I'm trying to setup up my react project with redux and I'm following a basic example with a counter, which I can increment and decrement. The counter displays correctly on the page as 0 initially - however when I hit the button, the increment action doesn't seem to be dispatched, and as a consequence, the counter does not update.
My LoginPage.js:
/* eslint-disable no-unused-expressions */
import { connect } from "react-redux";
import React, { Component } from "react";
import { selectCounter } from "./../../selectors/counter";
import { actions as counterActions } from "./../../actions/counter";
class LoginPage extends Component {
componentDidMount() {}
render() {
const { counter, increment } = this.props;
return (
<div>
<p>{`Hi ${counter}`}</p>
<button onClick={() => increment()}>+</button>
</div>
);
}
}
LoginPage = connect(
(state, props) => ({
counter: selectCounter(state, props)
}),
{ ...counterActions }
)(LoginPage);
export default LoginPage;
My actions/counter.js:
import { INCREMENT } from "./../types/counter";
const increment = () => {
return { type: INCREMENT };
};
export const actions = {
increment
};
My /reducers/counter.js:
const { INCREMENT, DECREMENT } = "./../types/counter";
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
module.exports = { counterReducer };
My /reducers/index.js:
import { combineReducers } from "redux";
import { counterReducer } from "./counter";
const rootReducer = combineReducers({
counter: counterReducer
});
export default rootReducer;
I'm omitting the App.js and index.js files as these are pretty simple and don't seem to be related to the problem.
UPDATE:
My actions/counter.js:
import { INCREMENT } from "./../types/counter";
import { useDispatch } from "react-redux";
const increment = () => {
return { type: INCREMENT };
};
const mapDispatchToProps = dispatch => {
return {
increment: () => dispatch(increment())
};
};
export const actions = {
...mapDispatchToProps(useDispatch)
};
Now I am seeing the bug:
react-dom.development.js:14724 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Updated
Need to properly define mapDispatchToProps function and pass it to connect(). In your code increment() doesn't seem to dispatch an action.
const mapDispatchToProps = (dispatch) =>{
increment: ()=>dispatch(actions.increment())
}
LoginPage = connect(
(state, props) => ({
counter: selectCounter(state, props)
}),
mapDispatchToProps
)(LoginPage);
Update
The error is due to useDispatch() usage outside component. It has to be declared and used within a functional component.

React component not updating on redux state update

I am using react, redux and redux-saga for my project. While fetching a list of users using Redux-saga, I can see that my redux store is getting updated (I can see it from the redux dev tool), But in the component , props are not changing.
I am using a button to get the list of users. And the users are showing up in that component only.
App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import './App.css';
import { fetchUsers } from "./actions";
import { Row, Col, ListGroup, ListGroupItem } from "reactstrap";
class App extends Component {
// eslint-disable-next-line no-useless-constructor
constructor(props){
super(props);
}
render() {
console.log("in render func");
console.log(this.props);
return (
<div className="App">
<h2>Redux Saga App</h2>
<Row>
<Col sm={6}>
{this.props.userList?this.props.userList.map((user)=>(
user.first_name + user.last_name
)) : ''}
</Col>
</Row>
<button onClick={this.props.getUserList}>Click to get the users</button>
</div>
);
}
}
const mapStateToProps = (state)=>{
console.log("in map statetpprop");
//console.log(state.userList);
return {userList:state.userList}
}
const mapDispatchToProps = (dispatch)=>{
return {getUserList :() => {dispatch(fetchUsers())}}
}
App = connect(mapStateToProps,mapDispatchToProps)(App);
export default App;
action.js
export function fetchUsers(){
return {
type:'FETCH_USERS',
}
}
reducer.js
const initialState = {
userList:[]
}
export function userReducer(state=initialState,action){
switch(action.type){
case 'ADD_USER':
return Object.assign(state,{
user:action.data
})
case 'SET_USERS':
return Object.assign(state, {userList : action.data});
default:
return state;
}
}
saga.js
import {call, put , takeEvery , takeLatest } from 'redux-saga/effects';
import axios from 'axios';
import { setUsers } from "./actions";
export function fetchUsersFunc(userId){
return axios.get('https://reqres.in/api/users');
}
function* fetchUsers(action){
const users = yield call(fetchUsersFunc);
console.log("in fetch users");
if(users.data.data){
const userList = users.data.data;
console.log(userList);
console.log(userList[0].first_name)
yield put(setUsers(userList));
}
}
export function* rootSaga(){
yield [
takeLatest('FETCH_USERS',fetchUsers)
];
}
Thanks for the help!
If you use Object.assign and you want to make a new copy of the state, you need to make the target object to a new empty object instead of what you are currently doing (it mutates the state object, which makes the react unable to re-render). (You can see See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign for more information on Object.assign) For example:
// copy previous state and new updates to a new empty object
return Object.assign({}, state, {userList : action.data});
I would recommend using the spread operator instead of Object.assign though:
return {...state, userList : action.data}
If state is updated but UI is not, it means something went wrong with reducer.
Checkout reducer function carefully.
If you are using spread operator (...) in reducer function, make sure the updated data is mentioned explicitly after spread operator.
Example of working reducer function is as below:
const UvNumberReducer = (state=initialState, action: UVAction) => {
switch(action.type) {
case UV_NUMBER.LOAD:
return {
...state,
data: {
title: action.data.title,
subtitle: action.data.subtitle
}
}
default:
return state;
}
}

'x' is not defined - React map dispatch to Props

Currently have an issue where by I want to update props based on 'componentdidupdate'. However everytime i call this function (onUpdateSelectedDate), it keeps saying
onUpdateSelectedDate is not defined
I have tried the following:
onUpdateSelectedDate(toggledDate)
this.onUpdateSelectedDate(toggledDate)
this.props.onUpdateSelectedDate(toggledDate)
and still unsure why i am getting this error.
Code below
import DayPicker from "react-day-picker"
import React, {Component} from 'react'
import './calendarDatePicker.scss'
import propTypes from 'prop-types'
import { connect } from 'react-redux'
class CalendarDatePicker extends Component {
state = {
toggledDate: null,
}
componentDidUpdate = () => {
const toggledDate = this.state.toggledDate
onUpdateSelectedDate(toggledDate)
}
render() {
const selectedDate = this.props.selectedDays
const onDayClick = this.props.onDayClick
const toggledDate = this.state.toggledDate
const modifiers = {
}
return (
<DayPicker
selectedDays={toggledDate===null ? selectedDate : toggledDate}
onDayClick={onDayClick}
todayButton="Go to Today"
firstDayOfWeek={1}
modifiers = {modifiers}
onMonthChange={(d) => this.setState({toggledDate: d})}
/>
)
}
}
CalendarDatePicker.propTypes = {
selectedDays: propTypes.instanceOf(Date),
onDayClick: propTypes.func,
onUpdateSelectedDate: propTypes.func,
}
const mapStateToProps = (state) => {
return {
//toggledDate: state.diaryContext.activities.selectedDates,
}
}
const mapDispatchToProps = (dispatch) => {
return {
onUpdateSelectedDate: (toggledDate) => { dispatch(diaryActions.updateSelectedDate(toggledDate)) },
}
}
export default connect(null, mapDispatchToProps)(CalendarDatePicker)
You use a wrong signature for the componentDidUpdate method it should be componentDidUpdate(prevProps, prevState) and then you can access your function from mapStateToProps like that:
componentDidUpdate (prevProps, prevState) {
const toggledDate = prevState.toggledDate
prevProps.onUpdateSelectedDate(toggledDate)
}

React Redux - Setting Store and Updating UI using Navigation Menu

I'm extremely new to react/redux and very mediocre at Javascript, but I've been struggling with this for over a week now. Here's what I'm trying to do:
Clicking one of the navigation menu items on the left should dispatch an action to set the selected index in the store (at this point the entire store is just a number). When the index is updated it should automatically be reflected in the UI, at least by changing the css class of the selected item, but eventually it should toggle visibility for content components on the right.
Sidebar.js:
import React, { Component } from 'react';
import SidebarItem from './SidebarItem'
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export default class Sidebar extends Component
{
handleClick(index)
{
//Dispatch action here maybe?
this.props.selectedSidebarItem(index);
console.log(this);
}
render()
{
var sidebarItemNames = ["Verify Original Contract", "Evaluate Transfer Terms", "Create Future Customer", "Check Credit", "Generate Agreement", "Finalize Transfer"];
return (
<div>
<div id="sidebar-title">
<div id="sc-logo">
LOGO
</div>
<div>Contract Transfer for:</div>
<div>XYZ</div>
<br />
</div>
<ul className="list-group" id="sidebar-list">
{sidebarItemNames.map(function(n, index)
{
return <SidebarItem key={index} index={index} selectedIndex={this.selectedSidebarItem} name={n} handleClick={this.handleClick(index).bind(this)} />;
})}
</ul>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return {
selectedSidebarItem: (index) => dispatch(ActionCreators.setSelectedSidebarItem(index))
}
}
const conn = connect(
null,
mapDispatchToProps
)(Sidebar)
SidebarItem.js:
import React, { Component } from 'react';
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export class SidebarItem extends Component {
constructor(props) {
super(props);
}
setSelectedSidebarItem() {
this.props.handleClick(this.props.index);
this.props.selectedSidebarItem(this.props.index);
// const ul = document.getElementById('sidebar-list');
// const items = ul.getElementsByTagName('li');
// for (let i = 0; i < items.length; ++i) {
// items[i].classList.remove('sidebar-item-current');
// }
}
render() {
return (
<li className={"list-group-item sidebar-list-item sidebar-item-todo" + (this.props.index==this.props.selectedIndex? ' sidebar-item-current':'') } onClick={this.setSelectedSidebarItem.bind(this)}><i className="fa fa-circle fa-lg"></i> <span>{this.props.name}</span></li>
)
}
}
Store.js:
import { createStore } from 'redux'
import reducers from './Reducers'
const store = createStore(reducers)
export default store
Reducers.js
const initialState = {
selectedSidebarItem: window.initialPageState,
otherStuff: 5
};
const reducers = (state = initialState, action) => {
switch (action.type) {
case "SET_SELECTED_SIDEBAR_ITEM":
console.log("clicked sidebar index: " + action.index);
var result = Object.assign({}, state, {
selectedSidebarItem: action.index
})
console.log(result);
return result;
default:
return state
}
}
export default reducers
actions.js:
import constants from './constants'
let ActionCreators = {
setSelectedSidebarItem(index) {
var actionObject = {
type: constants.UPDATE_SELECTED_SIDEBAR_ITEM,
index
}
console.log("setting sidebar item", actionObject);
return actionObject
}
}
export default ActionCreators
Constants.js
const constants = {
UPDATE_SELECTED_SIDEBAR_ITEM: "UPDATE_SELECTED_SIDEBAR_ITEM",
ADD_ERROR: "ADD_ERROR",
CLEAR_ERROR: "CLEAR_ERROR"
};
export default constants;
I've tried a few variations of the above and have previously been able to dispatch actions, but am unsure the store is ever updated and nothing is reflected on the UI. Right now I'm getting this error when clicking sidebar items: "Cannot read property 'handleClick' of undefined"
Thanks for any help in advance.
in your sidebar.js:
instead of
handleClick={() => this.handleClick(index).bind(this)}
try this:
handleClick={this.handleClick(index).bind(this)}
And in handleClick method you have to dispatch action:
this.props.selectedSidebarItem(index)
Answer update:
import React, { Component } from 'react';
import SidebarItem from './SidebarItem'
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export default class Sidebar extends Component
{
handleClick(index)
{
//Dispatch action here maybe?
this.props.selectedSidebarItem(index);
this.selectedSidebarItem = index;
console.log(this);
}
render()
{
var sidebarItemNames = ["Verify Original Contract", "Evaluate Transfer Terms", "Create Future Customer", "Check Credit", "Generate Agreement", "Finalize Transfer"];
return (
<div>
<div id="sidebar-title">
<div id="sc-logo">
LOGO
</div>
<div>Contract Transfer for:</div>
<div>XYZ</div>
<br />
</div>
<ul className="list-group" id="sidebar-list">
{sidebarItemNames.map(function(n, index)
{
return <SidebarItem key={index} index={index} selectedIndex={this.selectedSidebarItem} name={n} handleClick={this.handleClick(index).bind(this)} />;
})}
</ul>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return {
selectedSidebarItem: (index) => dispatch(ActionCreators.setSelectedSidebarItem(index))
}
}
const conn = connect(
null,
mapDispatchToProps
)(Sidebar)

Categories