So i'm trying to setup a timer within a react-redux store. My action are being sent to the reducer, i can see it in the console if i console.log from the reducer.
I even have the props hooked up where the button to start time and stop timer works, as i see the "TICK" action every second in the console. However my biggest confusion at the moment is, even though i got it hooked up on the store i just can't seem to see the values render in my component. this.props
is undefined on componentDidMount and render(){}
. however a button on that same component does <button onClick={this.props.startTimer}>Start</button>
trigger the startTimer action, so it is updating state, so why is component not reflecting it and why is my this.props value undefined in the component? Oh yeah and just to add my other two redux reducer/actions work perfectly fine even though they're doing a fetch/promise using the thunk
middleware. Any help/advice is welcome as this is my last hurdle on my app
Here is the code:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import reducers from './reducers'
export const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
reducers
export default (state = [], action) => {
switch (action.type) {
case "FORECAST_WEATHER":
return action.payload;
default: return state;
}
};
export default (state = [], action) => {
switch (action.type) {
case "CURRENT_WEATHER":
return action.payload;
default: return state;
}
};
const START_TIMER = "START_TIMER";
const STOP_IT = "STOP_IT";
const RESUME_IT = "RESUME_IT";
const TICK = "TICK";
const initialState = {
timerOn: true,
timerStart: 0,
timerTime: 30
};
export default (state = initialState, action) => {
console.log("state is ", state);
console.log("action is ", action);
switch (action.type) {
case START_TIMER:
return {
...state,
timerOn: true,
timerTime: state.timerTime,
timerStart: Date.now() - state.timerTime,
timerId: action.timerId
};
case STOP_IT: {
return {
...state,
timerOn: false,
timerStart: 0
};
}
case TICK:
return { ...state, timerTime: state.timerTime + 1 };
default:
return state;
}
};
import { combineReducers } from "redux";
import currentWeatherReducer from './currentWeatherReducer';
import forecastReducer from './forecastReducer';
import currentTimeReducer from './currentTimeReducer';
export default combineReducers({currentWeather: currentWeatherReducer, forecast: forecastReducer, currentTime: currentTimeReducer});
actions
import { getForecast } from "../api/GET/getForecast";
import { getCurrentWeather } from "../api/GET/getCurrentWeather";
export const fetchCurrentWeather = () => async (dispatch, getState) => {
const { name, dt, main } = await getCurrentWeather();
const cityProperties = { name, dt, main };
dispatch({ type: "CURRENT_WEATHER", payload: cityProperties });
};
export const fetchForecasts = () => async (dispatch) => {
const { list } = await getForecast();
dispatch({ type: "FORECAST_WEATHER", payload: list });
};
timer component
import React, { useState, useEffect } from "react";
import "./Timer.scss";
import { connect } from "react-redux";
import { start, stop, tick } from "../../actions";
class Timer extends React.Component {
constructor(props) {
super(props);
}
handleStop = () => {
clearInterval(this.props.timerId);
this.props.stopTimer();
};
componentDidMount() {
console.log("TIMER PROPS IS UNDEFINED HERE", this.props);
}
render() {
const { timerTime } = this.props;
console.log("PROPS ARE ALSO UNDEFINED HERE", this.props);
return (
<div className="timer-container">
<button onClick={this.props.startTimer}>Start</button>
TIME IS: {timerTime}
<button onClick={this.handleStop}>Stop</button>
<button onClick={this.props.resetTimer}>Reset</button>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return {
timerOn: state.timerOn,
timerStart: state.timerStart,
timerTime: state.timerTime,
timerId: state.timerId,
};
};
const mapDispatchToProps = (dispatch) => {
console.log("dispatch", dispatch);
let timerId;
return {
startTimer: () => {
timerId = setInterval(() => dispatch({ type: "TICK" }), 1000);
dispatch({ type: "START_TIMER", timerId });
},
stopTimer: () => {
dispatch({ type: "STOP_IT" });
clearInterval(timerId);
},
resetTimer: () => dispatch({ type: "RESUME_IT" }),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Timer);
Other components connected to the store that works as expected
import React from "react";
import "./Header.scss";
import Timer from "../Timer";
import { connect } from "react-redux";
import { fetchCurrentWeather } from "../../actions";
class Header extends React.Component {
componentDidMount() {
this.props.fetchCurrentWeather();
console.log(this.props.currentWeather);
}
render() {
const { name, dt, temp } = this.props.currentWeather
return (
<div className="top-header">
<div className="current-city info">
<h1>{name}</h1>
<div className="time-container">
<i className="time-icon icon" >xxx</i>
<span className="time-text">{dt}</span>
<i className="time-icon icon" >xxx</i>
</div>
<span className="degrees">{temp}°</span>
</div>
<Timer />
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return { currentWeather: state.currentWeather };
};
export default connect(mapStateToProps, { fetchCurrentWeather })(Header);
Another component connected to store that works
import React, { useState, useEffect } from "react";
import "./WeatherCard.scss";
import {fetchForecasts} from '../../actions';
import {connect} from 'react-redux';
class WeatherCard extends React.Component {
constructor(props) {
super(props);
}
componentDidMount(){
this.props.fetchForecasts();
console.log('PROPS IS DEFINED HERE', this.props);
}
render() {
const allRelevantData = Object.entries(this.props.forecast).map(([key, value]) => {
const dateTime = new Date(value.dt * 1000);
const day = dateTime.toString().slice(0, 3);
const item = {
day: day,
temp: Math.round(value.main.temp),
weatherMetaData: value.weather[0],
};
return item;
});
const uniqueForecasts = Array.from(
new Set(allRelevantData.map((a) => a.day))
).map((day) => {
return allRelevantData.find((a) => a.day === day);
});
return uniqueForecasts.map(({ day, temp, weatherMetaData }, index) => {
if (index < 5) {
return (
<div className="weather-card" key={index}>
<div className="day-temperature-container">
<span className="day">{day}</span>
<span className="temperature fade-in">{temp}°</span>
</div>
<div className="weather-description">
<span
className="icon weather"
style={{
background: `url(http://openweathermap.org/img/wn/${weatherMetaData.icon}.png)`,
}}
/>
<p>{weatherMetaData.description}</p>
</div>
</div>
);
}
});
}
}
const mapStateToProps = (state, ownProps) => {
return {forecast: state.forecast};
};
export default connect(mapStateToProps, {fetchForecasts})(WeatherCard);
You need to pull off states from your state tree that is shaped with reducers. And you have three reducers. In the Timer.js file you need to pull of states from the state shaped by currentTime reducer. If you change mapStateToProps in Timer.js, as below; you can get the states:
Timer.js
...
//pulling of states from the state tree that shaped with currentTime reducer
const mapStateToProps = (state) => {
return {
timerOn: state.currentTime.timerOn,
timerStart: state.currentTime.timerStart,
timerTime: state.currentTime.timerTime,
timerId: state.currentTime.timerId,
};
};
Not related but i just wanted to promote to use redux-logger :) reducer.js
...
//Create Store
// import { createLogger } from 'redux-logger';
const loggerMiddleware = createLogger();
const rootReducer = combineReducers({
currentWeather: currentWeatherReducer,
forecast: forecastReducer,
currentTime: currentTimeReducer
})
export const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
);