Setting redux state doesn't work after a hard refresh - reactjs

I am setting my redux state through a value I have in localStorage. This works fine when I navigate into my page. However, when I do a hard refresh the state is never set, despite the value in localStorage being passed down.
This is what my code looks like:
class SomeComponent {
componentWillMount() {
if (typeof localStorage !== 'undefined') {
console.log('I get to here...', localStorage.getItem('someValue')) // this comes in as expected always
this.props.setMyReduxState(localStorage.getItem('someValue'))
}
}
render () {
// will have the value of the localStorage item someValue when navigated into the page
// will be an empty string if I do a hard refresh
console.log('this.props.myReduxState', this.props.myReduxState)
return (
<div>
Stuff...
</div>
)
}
}
const mapStateToProps = (state) => {
return {
myReduxState: state.something.myReduxState || ''
}
}
const mapDispatchToProps = (dispatch) => {
return {
setMyReduxState (someValue) {
dispatch(setMyReduxState(someValue))
}
}
}
Any ideas?
Edit: just a small addition to simplify the problem: I also tried it sending a string directly to setMyReduxState function, without the localStorage, the state still isn't being set. So basically something like this:
componentWillMount() {
this.props.setMyReduxState('some string!')
}
From my understanding every time the redux state is set, the component should re-draw, which isn't happening when there is a hard refresh. Are there any reasons for this or something being done incorrectly?
Edit2: Including my action creator and reducer in case needed:
const SET_MY_REDUX_STRING = 'admin/users/SET_MY_REDUX_STRING'
const defaultState = {
myReduxState: ''
}
export function setMyReduxState (value) {
return {
type: SET_MY_REDUX_STRING,
myReduxState: value
}
}
export default function reducer (state = defaultState, action = {}) {
switch (action.type) {
case SET_MY_REDUX_STRING:
return Object.assign({}, state, { myReduxState: action.myReduxState })
default:
return state
}
}

Some of the checklist you need to follow while using redux -
Are you dispatching an action creator that returns an object with 'type' and data? Here 'type' is mandatory.
Does your reducer return the state with the updated data that it received?
Make sure you do not mutate the state in reducer. Always use {...state, someKey: someValue}

Related

React Redux store update doesn't trigger component rerender

I'm new in Redux and have a problem with rerendering after the store changed. I have found many similar problems here on SO but still can't solve my issue.
I have a monthly task(event) calendar with multiple tasks. The Calendar is the main component and some level deeper there are multiple TaskItem components. At the first render, the calendar and the tasks are rendered fine (In this case without employee names). In the Calendar component I trigger loading employees with a useEffect hook. I can see the network request on my console. Besides this, the console logs in the action, and in the reducer also show the employee list. And the Redux devtool also shows the loaded employees. Still the mapStateToProps on TaskItem shows a completly empty state.
What I'm doing wrong?
Here is my related code:
Calendar:
const Calendar = ({startDay, tasks, loadEmployeesAction}) => {
useEffect(()=>{
loadEmployeesAction();
},[]);
...
}
export default connect(null, {loadEmployeesAction})(Calendar);
TaskItem:
const TaskItem = ({task, onTextEdit, onTaskView, saveTask, employees }) => {
...
}
const mapStateToProps = (state) => {
console.log('Actual state is: ', state);
return {
employees: state.employees
}
}
export default connect(mapStateToProps)(TaskItem);
Reducer:
export const employeeReducer = (state = [], action) => {
switch (action.type) {
case actionType.EMPLOYEES_LOADED:
console.log('Reducer - Employees loaded:', action );
return action.payload.employees;
default :
return state;
}
}
Actions:
const employeesLoaded = (employees) => {
return {type: actionType.EMPLOYEES_LOADED, payload: {
employees
}
}
}
export const loadEmployeesAction = () => {
return (dispatch) => {
return employeeApi.getAllEmployees().then(emps => {
console.log('Action - Employees loaded: ', emps);
dispatch(employeesLoaded(emps));
})
}
}
Root reducer:
export const rootReduxReducer = combineReducers({
employees: employeeReducer
});
I found the error. It was a very clumsy mistake.
All of my posted code was fine, but I put the store creation in a component that was rerendered again and again so my store was recreated again and again.
The reducer code seems to be not as the redux pattern. So usually the state object is not directly replaced with a different object. Instead only the part of the state that needs to be changed is only with some non-mutating operation like spread operator.
So I think the reducer code should be changed like
export const employeeReducer = (state = [], action) => {
switch (action.type) {
case actionType.EMPLOYEES_LOADED:
return {...state,employees:action.payload.employees}
default :
return state;
}
}
if the response from the API is in the form
[{"employee_name":"name","employee_age":24},.....]

Redux trigger reducer if value not changing

I've got a reducer function like the following:
nextStep: (state) => {
if (state.currentStep < state.totalSteps && state.currentStepValid) {
state.currentStep += 1;
}
},
I'm listening to changes with the "useSelector" hook and I need to trigger a change even if the value doesn't change when dispatch is called. How is it possible to implement this?
Best regards!
At first you must create store and add your reducer to createStore method
import todoApp from './reducers'
const store = createStore(todoApp)
Then implement your reducer as function, which consume two params prevState and actions and return next state based on action, like this in basic example Basic tutorial
import { VisibilityFilters } from './actions'
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
function todoApp(state, action) {
if (typeof state === 'undefined') {
return initialState
}
// For now, don't handle any actions
// and just return the state given to us.
return state
}
In your code snippet, you aren't returning next state instead you are modify prev state.
You must write something like that:
nextStep: (state) => {
if (state.currentStep < state.totalSteps && state.currentStepValid) {
return {
...state,
currentStep: state.currentStep + 1
};
} else {
return state;
}
},

Redux async actions and view updates

I'm trying to implement a login form for a website, it's my first project on React so I'm quite a beginner.
To do so, I use socket.io-client inside my redux reducer.
The thing is, it doesn't update the local props correctly.
Here's the code of my view:
const mapStateToProps = state => {
return {
profile: state.profileReducer.profile
}
}
const mapDispatchToProps = dispatch => {
return {
dispatch: action => {
dispatch(action)
}
}
}
...
handleConnection = () => {
const { profile } = this.props
this.props.dispatch({ type: 'CONNECT_USER' })
}
...
export default connect(mapStateToProps, mapDispatchToProps)(LoginPage)
And here's the reducer's action:
import io from 'socket.io-client'
const host = [SERVER_URL]
const socketConnection = io.connect(host, {path: [PATH], secure: true})
const initialState = {
profile: {
token: null,
username: '',
password: ''
}
}
function profileReducer(state = initialState, action) {
switch(action.type) {
...
case 'CONNECT_USER':
let tempProfile = {...state.profile}
socketConnection.emit('login', tempProfile.username + ';' + tempProfile.password)
socketConnection.on('check', msg => {
if (msg !== null && msg !== '')
tempProfile.token = msg
return {
...state,
profile: tempProfile
}
})
return state
...
}
}
The 'check' socket action return a message containing the user connection token which I need to store to make sure the connection is done and allowed.
The thing is, it doesn't update the store value. If I update directly the reducer's state instead of the temporary profile, it partly works : the view props isn't properly updated but a 'console.log(profile)' in a 'setInterval' inside the 'handleConnection' function shows the token value (but the props inside the Chrome React Inspector isn't updated).
I really don't understand what's going on. I suppose the socket.io 'on' function isn't done before the 'return' of my action but I don't know how to handle it.
Does someone as any idea how I could solve this problem ?
Thanks !
Reducers are always synchronous in nature. If you want to perform an async operation (like the socket connection you are trying to establish) in your reducer then you need to use a middleware like Thunk or Saga to achieve the same.
In your case it is always returning the existing state from the last return statement.

How to get the value as props in a different component

In my react application, I have three parallel components. In my first component, I am doing an API call and based on the response I am routing the flow to Validated or Non-Validated Component.
Once the user is routed to validated component, there is a button on click of which user should be redirected to another component which should display the data in API response (first component) as key value pair. I am using Redux for state management.
The issue I am facing is the data is dispatched as an empty object from the store. I am not sure where I am going wrong but when I am debugging the app, I see the the action is not getting dispatched to the store and it's always returning me the initial state.
action.js-
export const setPoiData = (poiData) => dispatch => {
console.log('inside actions');
dispatch({
type: SET_POI_DATA,
payload: poiData
})
}
Reducer.js-
const initialState = {
poiData: {},
}
const reducerFunc = (state = initialState, action) => {
switch (action.type) {
case SET_POI_DATA:
console.log('inside poi reducers');
return {...state,poiData: action.payload}
default: return {...state}
}
}
Component 1-
//API call
Detail Component-
To get the data from store I am doing something like below-
componentDidMount() {
console.log(this.props.poiData)
}
function mapStateToProps(state) {
return {
poiData: state.poiData,
}
}
const mapDispatchToProps = dispatch => ({
setPoiData(data) {
dispatch(setPoiData(data));
}
})
I am not sure where I am going wrong. Can someone suggest me how to proceed ahead on this?
inside componentDidMount() you must call action like this this.props.setPoiData(<your data here>);

Redux change to nested object not triggering componentDidUpdate in component

I am struggling to figure out why a change to an object located in the store handled by a redux reducer is not triggering the componentDidUpdate method inside of my react component. I am using the react developer tools and can see the correct store after the state is reduced, and am also using redux logger and can see the correct after state after the reducer makes the change. But the component still never calls the update method.
action
export const GSAP_ANIMATION = 'GSAP_ANIMATION';
export const animateGsap = (key, next) => {
return {
type: GSAP_ANIMATION,
payload: {
key: key,
next: next
}
}
}
reducer
case GSAP_ANIMATION:
return Object.assign({}, state, {
...state,
gsap: {
...state.gsap,
[payload.key]: {
...state.gsap[payload.key],
next: {
...payload.next
}
}
}
});
component connection
const mapStateToProps = (state, ownProps) => {
return {
component: state.priorities.gsap[ownProps.id]
};
}
const mapDispatchToProps = (dispatch) => {
return {
addGsap: (key) => dispatch(actions.addGsap(key))
};
}
GsapComponent = connect(mapStateToProps, mapDispatchToProps)(GsapComponent);
In the GsapComponent I have the componentDidUpdate method, but this method is never called. However, I can see that the value of this.props.component should be correct when I view the component in the chrome extension.
edit
also doing { JSON.stringify(this.props.component) } correctly shows the updated prop values. Nothing in the react component update lifecycle is every triggered though.
I have also tried to use the immutibility-helper from react like so
return update(state, {
gsap: {
[payload.key]: {
$merge: { next: payload.next }
}
}
});
but it still doesn't call the lifecycle method.
GsapComponent source code.
Check this object assign documentation. Section Examples -> Warning for Deep Clone. I think that your reducer return object is === as state object so react can't detect change. Try json.parse(json.stringify) workaround or use immutable-js.

Resources