I am new to react redux and I am facing an issue with store note changing it values. I read a manual and then implemented the reducer and action. Implemented ACTION AND Reducer but state is not getting updated. Any help would be appreciated.
See below for my component file
import React from 'react'
import { Grid } from 'semantic-ui-react'
import uuid from 'uuid'
import axios from 'axios'
import _ from 'lodash'
import PropTypes from "prop-types";
import EditableTimerList from './EditableTimerList'
import ToggleableTimerForm from './ToggleableTimerForm'
import { newTimer } from './helpers'
import { updateAll, createUrlWithParams, updateTrackOnStartOrStop } from './services';
import Filters from './Filters';
import { connect } from "react-redux";
import {getDataForTimerDashBoard} from '../actions/timerAction';
var querystring = require('querystring');
class TimerDashboard extends React.Component {
constructor () {
super()
this.queryJson = { runningSince: '', title: ''};
this.state = {
timers: [
{
title: 'The default one',
description: 'This is a description',
elapsed: null,
runningSince: null,
id: uuid.v4(),
updateDate: new Date().toISOString()
}
]
}
};
componentDidMount() {
this.getData(this);
console.log(this.props.timers);
}
getData(that) {
this.props.getDataForTimerDashBoard(this.state.timers);
}
updateTimer (attrs) {
}
createTimer (timer) {
}
deleteTimer (timerId) { }
startTimer (timerId) {
}
stopTimer (timerId) {
}
onQueryChange(query) {
}
saveDataToState(that, data) {
}
render () {
const onQueryChange = _.debounce((query)=>{this.onQueryChange(query)}, 400);
return (
<div className="container">
<div className="row">
<EditableTimerList
timers={this.state.timers}
onFormSubmit={attrs => this.updateTimer(attrs)}
onTrashClick={timerId => this.deleteTimer(timerId)}
onStartClick={timerId => this.startTimer(timerId)}
onStopClick={timerId => this.stopTimer(timerId)}
/>
<ToggleableTimerForm
onFormSubmit={timer => this.createTimer(timer)}
/>
<Filters
onTextChange={(query)=>{onQueryChange(query)}}
onCheckboxChange={(query)=>{this.onQueryChange(query)}}
/>
</div>
</div>
)
}
}
TimerDashboard.propTypes = {
getDataForTimerDashBoard: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
timers: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
errors: state.errors,
timers: state.timers
});
export default connect(
mapStateToProps,
{getDataForTimerDashBoard}
)(TimerDashboard);
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;
See below fro my type file
type.js
export const GET_ERRORS = "GET_ERRORS";
export const USER_LOADING = "USER_LOADING";
export const SET_CURRENT_USER = "SET_CURRENT_USER";
export const LOAD_TIMER_DATA = "LOAD_TIMER_DATA";
reducer.js
import {LOAD_TIMER_DATA} from "../actions/types";
import uuid from 'uuid';
const isEmpty = require("is-empty");
const initialState = {
isAuthenticated: false,
user: {},
loading: false,
timers: {}
};
export default function (state = initialState, action) {
switch (action.type) {
case LOAD_TIMER_DATA:
console.log(action)
return {
...state,
isAuthenticated: !isEmpty(action.payload.usertoken),
user: action.payload.usertoken,
timers: action.payload.timers
};
default:
return state;
}
}
Timeraction
import axios from "axios";
import jwt_decode from "jwt-decode";
import {GET_ERRORS, LOAD_TIMER_DATA} from "./types";
var querystring = require('querystring');
// Register User
export const getDataForTimerDashBoard = (timerData) => async dispatch => {
const token = localStorage.getItem("jwtToken");
const decoded = jwt_decode(token);
//If no data remains in db, put the two dummy data of state into the db
await axios.get('/getAll').then(function (response) {
let savedTimers = [];
if (response.data.length === 0) {
timerData.timers.forEach((timer) => {
axios.post('/insert',
querystring.stringify(timer), {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}).then(function (response) {
timer.id = response.data.id
savedTimers.push(timer);
dispatch({
type: LOAD_TIMER_DATA,
payload: savedTimers
})
}).catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
});
});
} else {
alert(response.data);
const payload ={};
payload.timers = response.data;
payload.usertoken = decoded;
dispatch({
type: LOAD_TIMER_DATA,
payload: payload,
})
}
});
};
I think there is an issue in the code, while dispatching in payload you are pushing only SavedTimers but in reducer, you are trying to access userToken
savedTimers.push(timer);
dispatch({
type: LOAD_TIMER_DATA,
payload: savedTimers
})
Please add userToken also to your payload.
EDIT
import axios from "axios";
import jwt_decode from "jwt-decode";
import {GET_ERRORS, LOAD_TIMER_DATA} from "./types";
var querystring = require('querystring');
// Register User
export const getDataForTimerDashBoard = (timerData) => async dispatch => {
const token = localStorage.getItem("jwtToken");
const decoded = jwt_decode(token);
const payload ={};
payload.usertoken = decoded;
//If no data remains in db, put the two dummy data of state into the db
await axios.get('/getAll').then(function (response) {
let savedTimers = [];
if (response.data.length === 0) {
timerData.timers.forEach((timer) => {
axios.post('/insert',
querystring.stringify(timer), {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}).then(function (response) {
timer.id = response.data.id
savedTimers.push(timer);
payload.timers = savedTimers;
dispatch({
type: LOAD_TIMER_DATA,
payload: payload,
})
}).catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
});
});
} else {
payload.timers = response.data;
dispatch({
type: LOAD_TIMER_DATA,
payload: payload,
})
}
});
};
Related
I am new in react native, i created some pages, which works fine, but some time i am getting this error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method
i am using functional component, i am not able to identify why i am getting this issue ? if anyone can see my code and help me to resolve this issue, that will be the great, here i have added my whole code of it, thanks
JobDetailScreen.js
useEffect(() => {
let data = {}
data.user = login
data.job_id = route.params.job_id
dispatch(get_single_jobs(data));
},[]);
jobdetai.action.js
import * as types from "./jobdetail.type";
export const get_single_jobs = (data) => ({
type: types.GET_SINGLE_JOB,
payload: data,
});
jobdetail.type.js
export const GET_SINGLE_JOB = "GET_SINGLE_JOB";
jobdetail.saga.js
import { takeLatest, call, put,takeEvery } from "redux-saga/effects";
import SinglejobsServices from './jobdetail.services';
import JobsServices from '../jobs/jobs.services';
import * as types from './jobdetail.type';
import * as loadertypes from '../loader/loader.type';
function* get_single_jobs_service(payload) {
try {
yield put({ type: loadertypes.LOADERSTART});
const response = yield call(SinglejobsServices.list,payload);
if(response.status == false){
yield put({ type: types.SINGLE_JOBS_ERROR, response });
yield put({ type: loadertypes.LOADERSTOP});
}else{
yield put({ type: types.SET_SINGLE_JOB, response });
yield put({ type: loadertypes.LOADERSTOP});
}
} catch(error) {
let response = error.response.data;
yield put({ type: types.SINGLE_JOBS_ERROR, response });
yield put({ type: loadertypes.LOADERSTOP});
}
}
export default function* watchSinglejobs() {
yield takeEvery(types.GET_SINGLE_JOB, get_single_jobs_service);
}
Jobdetail.services.js
import axios from "axios";
import {API_URL} from '../../config/constant';
function services(){
const list = (request) => {
let req = request.payload;
const config = {
method: 'get',
url: API_URL + '/jobs/details/'+req.job_id,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${req.user.userToken}`
},
}
return axios(config)
.then(response => {
let result = response.data;
return result;
})
}
return {
list,
};
}
const SinglejobsServices = services();
export default SinglejobsServices;
Jobdetail.reducers.js
import * as types from "./jobdetail.type";
import singlejobsInitialState from "./jobdetail.initialstate";
const singleJobReducer = (state = singlejobsInitialState, action) => {
let response = action.response;
switch (action.type) {
case types.SET_SINGLE_JOB:
return {
...state,
data : response.data,
};
case types.SINGLE_JOBS_ERROR: {
return {
...state,
show: true,
msg: response.message,
};
}
case types.CLOSE_SINGLE_JOBS_MSG: {
return {
...state,
show: false,
msg: null,
};
}
default:
return state;
}
};
export default singleJobReducer;
JobdetailInitiate.js
const singlejobsInitialState = {
show : false,
msg : null,
data : []
};
export default singlejobsInitialState;
Jobdetail.controller.js
import React,{useState,useEffect} from 'react';
import JobDetails from './jobdetail'
import { useSelector } from "react-redux";
import {update_wishlist,apply_for_job} from '../jobs/jobs.action'
import {update_single_fav_job} from './jobdetail.action'
import { useDispatch } from "react-redux";
export default function JobDetailsController ({route,navigation}) {
// const jobs = useSelector((state) => state.jobs);
const singlejob = useSelector((state) => state.singlejob);
const login = useSelector((state) => state.login);
const dispatch = useDispatch();
const UpdateFav = (job_id) => {
let data = {};
data.job_id = job_id;
data.user = login;
dispatch(update_single_fav_job(data));
}
const ApplyForJob = (job_id) => {
let data = {};
data.home = false
data.job_id = job_id;
data.user = login;
dispatch(apply_for_job(data));
}
return (
<JobDetails navigation={navigation} job={singlejob.data} UpdateFav={UpdateFav} ApplyForJob={ApplyForJob} />
);
}
I want to pass the state from my searchReducer to my movieReducer. The search takes in an input and saves the id of the movie(s), into state, I want to take that id value and pass it into the fetch for my movies, so that I can fetch each movie with the id and save the data into the movieReducer's state. How can I do this?
actions.js
// ------------------ SEARCH ------------------
export const searchMovie = text => dispatch => {
dispatch({
type: SEARCH_MOVIE,
payload: text
})
}
export const fetchSearch = text => dispatch => {
axios.get(`https://api.themoviedb.org/3/search/multi?api_key=API_KEY&language=en-US&query=${text}&page=1&include_adult=false`)
.then(response => dispatch({
type: FETCH_SEARCH,
payload: response.data.results.map(search => search.id)
}))
.catch(error => console.log(error))
}
// ------------------ MOVIES ------------------
export const fetchMovie = text => dispatch => {
axios.get(`https://api.themoviedb.org/3/movie/${text}?api_key=API_KEY&append_to_response=videos,credits,recommendations,watch/providers`)
.then(response => dispatch({
type: SPECIFIC_MOVIE,
payload: response.data.results
}))
.catch(error => console.log(error))
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import App from './App'
import reportWebVitals from './reportWebVitals';
import favoritesReducer from './redux/favoritesReducer.js'
import moviesReducer from './redux/moviesReducer.js'
import showsReducer from './redux/showsReducer.js'
import userReducer from './redux/userReducer';
import searchReducer from './redux/searchReducer.js'
const rootReducer = combineReducers({
favorties: favoritesReducer,
movies: moviesReducer,
shows: showsReducer,
users: userReducer,
search: searchReducer
})
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
let store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
export default
searchReducer.js
const initialState = {
text: '',
movies: [],
loading: false,
movie: []
}
const searchReducer = (state = initialState, {type, payload}) => {
switch (type) {
case 'SEARCH_MOVIE':
return {
...state,
text: payload,
loading: false
};
case 'FETCH_SEARCH':
return {
...state,
movies: payload,
loading: false
};
default:
return state;
}
}
export default searchReducer
movieReducer.js
const initialState = {
text: '',
movie: []
}
const moviesReducer = (state = initialState, {type, payload}) => {
switch (type) {
case 'SPECIFIC_MOVIE':
return {
...state,
movie: payload
};
default:
return state;
}
}
export default moviesReducer
MoviePage.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchMovie } from '../../actions/searchActions';
export class Movie extends Component {
componentDidMount() {
this.props.fetchMovie(this.props.match.params.id);
}
render() {
const { movie } = this.props;
let movieInfo = (
<div className="container">
<img src={movie.Poster} className="thumbnail" alt="Poster" />
<h2 className="mb-4">{movie.Title}</h2>
<li>Genre:</li> {movie.Genre}
<li>Released:</li> {movie.Released}
<li>Rated:</li> {movie.Rated}
<li>IMDB Rating:</li> {movie.imdbRating}
<li>Director:</li> {movie.Director}
<li>Writer:</li> {movie.Writer}
<li>Actors:</li> {movie.Actors}
<h3>About </h3>
{movie.Plot}
</div>
);
return <div>{}</div>;
}
}
const mapStateToProps = state => ({
movie: state.movies.movie
});
export default connect(mapStateToProps,{ fetchMovie })(Movie);
You can access the current state tree of your application using getState method inside of your action creator.
export const fetchMovie = text => (dispatch, getState) => {
console.log(getState()); // you can see the info about your state tree here
axios.get(`https://api.themoviedb.org/3/movie/${text}?api_key=API_KEY&append_to_response=videos,credits,recommendations,watch/providers`)
.then(response => dispatch({
type: SPECIFIC_MOVIE,
payload: response.data.results
}))
.catch(error => console.log(error))
}
I am getting a dispatch is not defined error from 'shopfront' code. I believe it is because i'm not passing the properties down to the next level but I'm not sure if that is correct or not. I want to be able to pass the dispatch function through to the product.actions code correctly.
I have tried to narrow down the problem as much as possible by removing unnecessary code. I have a user reducer that is working correctly but I don't know why this product reducer isn't
// products.reducer
const initialState = {
products: null,
error: null
};
const ProductReducer = (state = initialState, action) => {
let newState = null;
switch(action.type){
case "GET_ALL_PRODUCTS": newState = {
...state,
products: action.products
};
return newState;
case "GET_ALL_PRODUCTS_FAIL": newState = {
...state,
error: action.error
};
return newState;
default: return state;
}
};
export default ProductReducer;
// index
import React from "react";
import ReactDOM from "react-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import { register } from "./serviceWorker";
import { createStore, combineReducers, applyMiddleware, compose } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import UserReducer from "./store/reducers/users.reducers";
import ProductReducer from "./store/reducers/products.reducer";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const appReducer = combineReducers({
usersRed: UserReducer,
productsRed: ProductReducer
});
const logger = (store) => {
return next => {
return action => {
console.log("Middleware dispatching ");
console.log(action);
const result = next(action);
console.log("Middleware next state ");
console.log(store.getState());
return result;
};
};
};
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const appStore = createStore(appReducer, composeEnhancers(applyMiddleware(logger, thunk)));
const app = (
<BrowserRouter>
<Provider store={appStore}>
<App />
</Provider>
</BrowserRouter>
);
ReactDOM.render(app, document.getElementById("root"));
register();
// shopfront
import React, { Component } from "react";
import { Container, Row, Col, InputGroup, InputGroupAddon, Button } from "reactstrap";
import { withRouter } from "react-router-dom";
import { connect } from "react-redux";
import { Alert } from "reactstrap";
import * as actionMethods from "../../store/actions/index.actions";
import Product from "../../components/Product/Product";
class Shopfront extends Component {
state = {
onAlert: false,
internalError: null
};
componentDidMount() {
this.props.loadAllProducts(5);
console.log("component_did_mount_run")
}
render() {
let ProductsList = <h1>No Products Yet!</h1>;
if (this.props.products !== null) {
ProductsList = this.props.products.map(Product => {
return <Product
key={Product.id}
title={Product.name}
excerpt={Product.description}
medialink={Product.permalink}
ProductId={Product.id}
/>;
});
}
return (
<Container>
{ProductsList}
</Container>
);
}
};
const mapStateToProps = (state) => {
return {
products: state.productsRed.products,
error: state.productsRed.error
};
};
const mapDispatchToProps = (dispatch) => {
return {
loadAllProducts: (perpage) => { dispatch(actionMethods.loadAllProducts(perpage)) }
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Shopfront));
// index.actions
export {
loadAllProducts
} from "./product.actions";
// product.actions
import wcapi from "../../axios-wp";
export const loadAllProducts = (perpage) => {
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
Thank you for your help!
it is because you are not returning dispatch from your loadAllProducts action
/ product.actions
import wcapi from "../../axios-wp";
export const loadAllProducts = (perpage) => (dispatch) => { //make this change
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
I have ever faced such this issue before. Then I used return dispatch => {} like this:
export const loadAllProducts = perpage => {
return dispatch => {
wcapi.get("products", {
per_page: perpage,
})
.then((response) => {
// Successful request
let productsRes = response.data;
dispatch({ type: "GET_ALL_PRODUCTS", products: productsRes });
})
.catch((err) => {
// Invalid request, for 4xx and 5xx statuses
dispatch({ type: "GET_ALL_PRODUCTS_FAIL", error: err });
})
}
}
I'm trying to create an alert component, however for this I need to add an item (from anywhere) to the list of alerts in the state.
I have this code:
alertReducer.js:
import { SET_ALERT, GET_ALERTS, SET_ALERT_SHOWED } from "../actions/types";
const initialState = {
alerts: [
{
id: 0,
title: "teste",
message: "teste",
isShowed: false,
type: "success"
}
]
};
export default function(state = initialState, action) {
switch (action.type) {
case SET_ALERT:
return { ...state, alert: action.payload };
case SET_ALERT_SHOWED:
return {
...state,
alert: state.alerts.map(a =>
a.id === a.payload.id ? (a = action.payload) : a
)
};
case GET_ALERTS:
return { ...state };
default:
return state;
}
}
alertActions.js
import { SET_ALERT, GET_ALERTS, SET_ALERT_SHOWED } from "./types";
import axios from "axios";
export const getAlerts = () => dispatch => {
dispatch({
type: GET_ALERTS,
payload: null
});
};
export const setAlertShowed = alert => dispatch => {
dispatch({
type: SET_ALERT_SHOWED,
payload: null
});
};
export const setAlert = alert => dispatch => {
console.log("set alert:");
this.setState(state => {
state.alert.alerts.push(alert);
return null;
});
dispatch({
type: SET_ALERT,
payload: null
});
};
alerts.js (component)
import React from "react";
import { Link } from "react-router-dom";
import { Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
import {
Panel,
PanelHeader,
PanelBody
} from "./../../components/panel/panel.jsx";
import SweetAlert from "react-bootstrap-sweetalert";
import ReactNotification from "react-notifications-component";
import "react-notifications-component/dist/theme.css";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { getAlerts, setAlertShowed } from "../../actions/alertActions";
class Alerts extends React.Component {
constructor(props) {
super(props);
this.addNotification = this.addNotification.bind(this);
this.notificationDOMRef = React.createRef();
}
componentWillReceiveProps(nextProps) {
console.log("atualizou alertas");
console.log(this.props);
console.log(nextProps);
}
componentDidMount() {
this.props.getAlerts();
this.showAlerts();
}
showAlerts() {
const { alerts } = this.props;
alerts
.filter(a => !a.isShowed)
.map((a, i) => {
this.addNotification.call(this, a);
a.isShowed = true;
setAlertShowed(a);
});
}
addNotification(alert) {
this.notificationDOMRef.current.addNotification({
title: alert.title,
message: alert.message,
type: alert.type,
insert: "top",
container: "top-right",
animationIn: ["animated", "fadeIn"],
animationOut: ["animated", "fadeOut"],
dismiss: { duration: 2000 },
dismissable: { click: true }
});
}
render() {
const { alerts } = this.props;
return <ReactNotification ref={this.notificationDOMRef} />;
}
}
Alerts.propTypes = {
alerts: PropTypes.array.isRequired,
getAlerts: PropTypes.func.isRequired,
setAlertShowed: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
alerts: state.alert.alerts
});
export default connect(
mapStateToProps,
{ getAlerts, setAlertShowed }
)(Alerts);
So I have this helper I'm trying to do, it would serve so that from anywhere in the application I can trigger addAlert and generate a new alert, but I have no idea how to call the setAlert function inside the alertActions.js, what I was able to do is call the SET_ALERT through the store.dispatch, but apparently this is not triggering the setAlert or I am doing something wrong
import uuid from "uuid";
import { createStore } from "redux";
import { setAlert } from "../actions/alertActions";
import { SET_ALERT } from "../actions/types";
import alertReducer from "../reducers/alertReducer";
export function addAlert(state, title, message, type = "success") {
// const store = createStore(alertReducer);
// const state = store.getState();
const newalert = {
id: uuid.v4(),
title,
message,
isShowed: false,
type: type
};
console.log("state");
console.log(state);
// this.setState(state => {
// state.alert.alerts.push(alert);
// return null;
// });
// store.dispatch({
// type: SET_ALERT,
// payload: newalert
// });
// store.dispatch(setAlert(newalert));
// store.dispatch(SET_ALERT);
// this.setState(prevState => ({
// alert.alerts: [...prevState.alert.alerts, newalert]
// }))
}
PS. My react knowledge is very low yet and English it's not my primary language, if I don't make myself clear please ask anything.
Thank you.
Do like this:
// Create alert which you want to show
const alerts = [
{
id: 0,
title: "teste",
message: "teste",
isShowed: false,
type: "success"
}];
componentDidMount() {
this.props.getAlerts();
this.showAlerts();
// this will call alerts action
this.props.callAlert(alerts );
}
const mapDispatchToProps = dispatch=> ({
callAlert: (alert) => dispatch(setAlert(alert)),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
{ getAlerts, setAlertShowed }
)(Alerts);
Finally! I created the store by adding compose and applyMiddleware, I still have to study how this works best but it worked.
The helper code to add alert looks like this:
import uuid from "uuid";
import { createStore, dispatch, compose, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { setAlert } from "../actions/alertActions";
import alertReducer from "../reducers/alertReducer";
export function addAlert(title, message, type = "success") {
const store = createStore(alertReducer, compose(applyMiddleware(thunk)));
const newalert = {
id: uuid.v4(),
title,
message,
isShowed: false,
type: type
};
store.dispatch(setAlert(newalert));
}
I am starting with ReactJS and Redux and last few days, I am being stuck on a problem when I leave my app open in the browser for a while and then got back to it, I see there this error:
TypeError: Cannot read property 'push' of undefined
It's here, in my Event.js component:
import React, { Component } from 'react';
import axios from 'axios';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { registerUser, logoutUser } from '../redux/actions/authentication';
import { withRouter } from 'react-router-dom';
class Event extends Component {
constructor() {
super();
this.state = {
...
}
UNSAFE_componentWillMount() {
if(!this.props.auth.isAuthenticated) {
console.log('Unauthorized - Event action');
this.props.history.push('/');
}
}
componentDidMount() {
axios.get('/api/events')
.then((response) => {
this.setState({events: response.data});
console.log('events: ', this.state.events);
}).catch(err => {
console.log('CAUGHT IT! -> ', err);
});
}
componentWillReceiveProps(nextProps) {
if(nextProps.errors) {
this.setState({
errors: nextProps.errors
});
}
}
...
render() {
const { errors } = this.state;
const {isAuthenticated, user} = this.props.auth;
return (...)
}
Event.propTypes = {
registerUser: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
errors: state.errors
});
export default connect(mapStateToProps,{ registerUser })(withRouter(Event))
Then, my redux/actions/authentication.js looks like this:
import axios from 'axios';
import { GET_ERRORS, SET_CURRENT_USER } from './types'; // we list here the actions we'll use
import setAuthToken from '../../setAuthToken';
import jwt_decode from 'jwt-decode';
export const registerUser = (user, history) => dispatch => {
axios.post('/api/users/register', user)
.then(res => history.push('/login'))
.catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
});
});
}
export const loginUser = (user) => dispatch => {
axios.post('/api/users/login', user)
.then(res => {
//console.log(res.data);
const { token } = res.data;
localStorage.setItem('jwtToken', token);
setAuthToken(token);
const decoded = jwt_decode(token);
dispatch(setCurrentUser(decoded));
})
.catch(err => {
dispatch({
type: GET_ERRORS,
payload: err.response.data
});
});
}
export const setCurrentUser = decoded => {
return {
type: SET_CURRENT_USER,
payload: decoded
}
}
export const logoutUser = (history) => dispatch => {
localStorage.removeItem('jwtToken');
setAuthToken(false);
dispatch(setCurrentUser({}));
history.push('/login');
}
And reducers - authReducer.js:
import { SET_CURRENT_USER } from '../actions/types';
import isEmpty from '../../validation/is-empty';
const initialState = {
isAuthenticated: false,
user: {}
}
export default function(state = initialState, action) {
switch(action.type) {
case SET_CURRENT_USER:
return {
...state,
isAuthenticated: !isEmpty(action.payload),
user: action.payload
}
default:
return state;
}
}
errorReducer.js goes like this:
import { GET_ERRORS } from '../actions/types';
const initialState = {};
export default function(state = initialState, action ) {
switch(action.type) {
case GET_ERRORS:
return action.payload;
default:
return state;
}
}
and index.js:
import { combineReducers } from 'redux';
import errorReducer from './errorReducer';
import authReducer from './authReducer';
export default combineReducers({
errors: errorReducer,
auth: authReducer
});
In the nabber menu, I have a link to log out the user. If the user clicks the link, I log him out like this:
onLogout(e) {
e.preventDefault();
this.props.logoutUser(this.props.history);
}
However, I am still unable to figure out why I am seeing the error above. What I also don't understand here is that when I get that error screen and then refresh the page, the error page disappears and I am redirected from localhost:3000/events to localhost:3000.
You should use
withRouter(connect(...)(MyComponent))
and not
connect(...)(withRouter(MyComponent))
here is the documentation
I think this.props.history is undefined in your example because of this.
Make sure your the object you pass to the logoutUser function is not undefined and the history parameter is received in the right way. You are trying to invoke the push method on the history object, but in this case it tells you that the function can not be found because history is undefined. Hope this helps.