When I try and destructure my data using useSelector, I get the error backgroundImage undefined.
I am setting my initial state in my reducer. How can I stop the error occurring?
component:
export const SampleScreen = (props) => {
const { navigation } = props;
const {
rescueMe: {
rescueMeSplashScreen: {
backgroundImage, // error undefined
buttonText,
informationText,
title: pageTitle,
},
},
} = useSelector((state) => state);
return (
<>
</>
);
};
Reducer:
const RescueMeReducer = (
state = {
rescueMeSplashScreen: {
backgroundImage: "",
buttonText: "",
informationText: "",
title: "",
},
lastKnownPosition: undefined,
error: undefined,
loading: false,
},
action
) => {
switch (action.type) {
...
case RESCUE_ME_CONTENT: {
return {
...state,
error: undefined,
rescueMeSplashScreen: undefined,
loading: true,
};
}
case RESCUE_ME_CONTENT_SUCCESS: {
return {
...state,
rescueMeSplashScreen: action.payload,
loading: false,
};
}
case RESCUE_ME_CONTENT_FAILED: {
return {
...state,
rescueMeSplashScreen: undefined,
error: {
errorMessage: action.payload,
},
loading: false,
};
}
default:
return state;
}
};
export default RescueMeReducer;
Related
Guys I want to clear redux state to initial values when component unmount but I got this error
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 a useEffect cleanup function.
Here's my code
signup.js
useEffect(() => {
if (cookies?.resentTimes?.num >= 3) {
setResentTimes(3);
setAttempts(3);
}
return () => {
signupCleanup();
console.log("cleaned up");
};
}, []);
signup.actions.js
export const signupCleanup = () => ({
type: Types.SIGNUP_CLEANUP,
});
signup.reducer.js
export default function signup(state = initialState.auth.signup, action) {
switch (action.type) {
case CHECK_DOMAIN:
return {
...state,
checkingDomainStart: true,
checkingDomainError: null,
};
case CHECK_DOMAIN_FAILURE:
return {
...state,
checkingDomainStart: false,
checkingDomainError: action.payload.error,
};
case CHECK_DOMAIN_SUCCESS:
return {
...state,
checkingDomainStart: false,
checkDomainRes: action.payload.res,
};
case CHECK_EMAIL:
return {
...state,
checkingEmailStart: true,
checkingEmailError: null,
};
case CHECK_EMAIL_FAILURE:
return {
...state,
checkingEmailStart: false,
checkingEmailError: action.payload.error,
};
case CHECK_EMAIL_SUCCESS:
return {
...state,
checkingEmailStart: false,
checkEmailRes: action.payload.res,
};
case VERIFY_CODE:
return {
...state,
verifyingCodeStart: true,
verifyingCodeError: null,
};
case VERIFY_CODE_FAILURE:
return {
...state,
verifyingCodeStart: false,
verifyingCodeError: action.payload.error,
}
case VERIFY_CODE_SUCCESS:
return {
...state,
verifyingCodeStart: false,
token: action.payload.token,
}
case SET_PASSWORD:
return {
...state,
settingPasswordStart: true,
settingPasswordError: null,
}
case SET_PASSWORD_FAILURE:
return {
...state,
settingPasswordStart: false,
settingPasswordError: action.payload.error,
}
case SET_PASSWORD_SUCCESS:
return {
...state,
settingPasswordStart: false,
settingPasswordResponse: action.payload.res,
}
case SIGNUP_CLEANUP:
return {
...state,
settingPasswordResponse: null,
settingPasswordError: null,
token: null,
verifyingCodeError: null,
checkingEmailError: null,
checkEmailRes: null,
checkDomainRes: null,
checkingDomainError: null,
}
default:
return state;
}
}
Store changes not immediately visible to component due to this error message not showing in component whenever request get failed. From reducer, state update take some time to return the update value to component. Hence, component always return as empty msg which is default value present in reducer
Api.js
export const createCategory = async (category, authtoken) => {
return await axios.post(
`${process.env.REACT_APP_API}/category/create`,
category,
{
headers: {
authtoken,
},
}
);
};
category.saga.js
export function* createCategoryAsync({ payload: { name, token } }) {
try {
yield delay(1000);
const response = yield call(createCategory, { name }, token);
yield delay(1000);
console.log("===response", response);
if (response.status === 200 && response.status < 300) {
yield put(createCategorySuccess(response.data.name));
}
console.log("===response", response);
} catch (error) {
yield put(createCategoryFail(error.response.data));
}
}
category.reducer.js
import CategoryActionTypes from "./category.types";
const INITIAL_STATE = {
categoryName: "",
categories: [],
error: false,
errorMsg: "",
};
const categoryReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case CategoryActionTypes.LOAD_CATEGORY_START:
return {
...state,
loading: true,
};
case CategoryActionTypes.LOAD_CATEGORY_SUCCESS:
return {
...state,
loading: false,
categories: action.payload,
};
case CategoryActionTypes.SET_CATEGORY_EMPTY:
return {
...state,
categoryName: "",
};
case CategoryActionTypes.CREATE_CATEGORY_START:
return {
...state,
loading: true,
};
case CategoryActionTypes.CREATE_CATEGORY_SUCCESS:
return {
...state,
loading: false,
categoryName: action.payload,
};
case CategoryActionTypes.LOAD_CATEGORY_FAIL:
case CategoryActionTypes.CREATE_CATEGORY_FAIL:
return {
...state,
loading: false,
error: true,
errorMsg: action.payload,
};
default:
return state;
}
};
export default categoryReducer;
Component.js
const Component = () => {
useEffect(() => {
loadCateories();
}, []);
const { categories, loading, categoryName, error, errorMsg } = useSelector(
(state) => ({
...state.category,
})
);
const loadCateories = () => {
dispatch(loadCategoryStart());
};
console.log("==errorMsg", errorMsg);
const {
user: { token },
} = useSelector((state) => ({ ...state }));
const handleSubmit = (e) => {
e.preventDefault();
// setLoading(true);
// dispatch(setCategoryEmpty());
dispatch(createCategoryStart({ name, token }));
if (categoryName) {
toast.success(`${name} is created`);
setName("");
loadCateories();
} else {
toast.error(errorMsg && errorMsg);
setName("");
}
};
}
I have built a fairly large react-redux application. In one component I have added an undo feature. Tracing the state all the way through it is definitely being updated and not mutated. It even re-renders the component and all child components. However on the page the component position is not modified until I click the component to either move it again or highlight it.
I have definitely verified that this is not a case of mutated state and have stepped through all the redux code to ensure that the shallow equality fails and I have added breakpoints on the main component and the child component which should be moved.
I will add code if you want to see it but my question is why a re-rendered component in React would not re-render in the updated position on the screen, even though the top and left coordinates have definitely changed?
Edit adding code
//layout.js
const mapLayoutDispatchToProps = (dispatch) => {
//add action creators here - by reference?
return {
Layout_Set_Current_Site: (siteId) => { dispatch(Layout_Set_Current_Site(siteId)) },
Layout_Get_Sites: () => { dispatch(Layout_Get_Sites()) },
Layout_Get_Map_Background: (siteId, callback) => { dispatch(Layout_Get_Map_Background(siteId, callback)) },
Layout_Get_UserImages: (deskId) => { dispatch(Layout_Get_UserImages(deskId)) },
Layout_Create_Desk: (type, siteId, height, width) => { dispatch(Layout_Create_Desk(type, siteId, height, width)) },
Layout_Restore_All: () => { dispatch(Layout_Restore_All()) },
Layout_Undo_Change: (callback) => { dispatch(Layout_Undo_Change(callback)) },
Layout_Redo_Change: () => { dispatch(Layout_Redo_Change()) },
Layout_Fetch_Desks: (siteId) => { dispatch(Layout_Fetch_Desks(siteId)) },
Layout_Get_Desk_Types: () => { dispatch(Layout_Get_Desk_Types()) },
Layout_Set_Current_Desk: (desk) => { dispatch(Layout_Set_Current_Desk(desk)) }
};
}
getDesks = () => {
console.log("Layout.getDesks");
// const d = this.props.layout_moveData.desks.present;
const desks = clone(this.props.layout_moveData.present);
return desks;
}
handleKeyPress = (e) => {
console.log("Layout.handleKeyPress");
if (this.state.edit) {
switch (e.code) {
case 'KeyZ':
if (e.ctrlKey) {
this.props.Layout_Undo_Change();
e.cancelBubble = true;
// this.forceUpdate();
}
break;
case 'KeyY':
if (e.ctrlKey) {
//this.props.Layout_Redo_Change();
UndoMove.redo();
e.cancelBubble = true;
}
break;
default:
break;
}
}
}
buildDesks = () => {
const desks = this.getDesks();
let ret = desks.map((desk, index) =>
<Desk
key={index}
desks={desks}
index={index}
deskTypes={this.props.layout.deskTypes}
scale={this.getScale()}
editable={this.state.edit}
/>
);
return ret;
}
render=()=>{
return (
<div>
<Row>
<Col sm={1}>
{this.showAdmin()}
</Col>
<Col sm={10}>
{this.state.details}
</Col>
</Row>
<Row>
<Col sm={1}>
<select onChange={(e) => this.changeMap(e.target)}>
{this.buildMapOptions()}
</select>
</Col>
<Col sm={10} id="Layout_Map_Container">
{this.buildMap()}
{this.buildDesks()}
</Col>
</Row >
{this.showStatus()}
</div>
);
}
}
//desks.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Draggable from '../../Elements/Draggable';
import {
Layout_Clear_Desk,
Layout_Delete_Desk,
Layout_Update_Desk_Data,
Layout_Set_Current_Desk
} from '../../../redux/Creators/Layout_Creator';
import '../../../shared/styles/layout.css';
const clone = require('rfdc')();
const mapDesksStateToProps = (state) => {
return {
layout: state.layout,
layout_moveData: state.layout_moveData
}
}
const mapDesksDispatchToProps = (dispatch) => {
return {
Layout_Clear_Desk: (deskId) => { dispatch(Layout_Clear_Desk(deskId)) },
Layout_Delete_Desk: (deskId) => { dispatch(Layout_Delete_Desk(deskId)) },
Layout_Update_Desk_Data: (desk, deskId) => { dispatch(Layout_Update_Desk_Data(desk, deskId)) },
Layout_Set_Current_Desk: (deskId) => { dispatch(Layout_Set_Current_Desk(deskId)) }
}
}
class Desk extends Component {
constructor(props) {
super(props);
this.state = {
desk: clone(props.desks[props.index]),
desks: clone(props.desks)
}
}
rightClick = (e, deskId) => {
if (this.props.editable) {
const desk = this.state.desk;
let rotation = parseInt(desk.rotation);
rotation += 90;
if (rotation >= 360) rotation -= 360;
desk.rotation = rotation;
this.props.Layout_Set_Current_Desk(desk);
this.props.Layout_Update_Desk_Data(desk);
}
}
updateProperties = (data) => {
let string = `Top: ${data.top}, Left:${data.left}`;
// data = this.state.details + ', ' + data
this.setState({ details: string });
}
mouseUp = (e, deskId, data) => {
console.log("Layout.mouseUp");
const desks = clone(this.state.desks);
// let desk = ;
if (data.dragged && this.props.editable) {
// this.clickDesk(e, deskId);
const scale = this.props.scale;
const newX = parseInt(data.left / scale);
const newY = parseInt(data.top / scale);
desks[deskId].x = newX + ""; //convert to strings
desks[deskId].y = newY + "";
console.log(this.state.desks);
console.log(desks);
this.props.Layout_Update_Desk_Data(desks, deskId);
}
else {
this.clickDesk(e, deskId);
}
}
clickDesk = (e, deskId) => {
if (deskId !== null && deskId !== undefined && deskId !== false) {
this.props.Layout_Set_Current_Desk(this.state.desk);
}
else {
this.props.Layout_Set_Current_Desk(null);
}
}
render() {
let deskImg = null;
// const desk = this.props.desks[this.props.index];
let desk = clone(this.state.desk);
try {
let dImg = this.props.deskTypes.find(
d => parseInt(d.deskType) === parseInt(desk.deskType)
);
deskImg = dImg.deskImage;
}
catch (ex) {
console.log(ex);
}
const userName = desk.UserLogon !== (null || '') ? desk.UserLogon : "Unassigned";
const top = Math.trunc(parseInt(parseInt(desk.y) * this.props.scale));
const left = Math.trunc(parseInt(parseInt(desk.x) * this.props.scale));
let imgStyle = {
width: `${parseInt(parseInt(desk.width) * this.props.scale)}px`,
height: `${parseInt((parseInt(desk.height) * this.props.scale))}px`,
position: 'absolute'
}
if (this.props.layout.currentDesk && desk.id === this.props.layout.currentDesk.id) {
imgStyle.border = '2px solid cyan';
}
const url = `data:image/jpeg;base64,${deskImg}`;
try {
//
return (
<Draggable key={desk.id}
index={this.props.index}
enabled={this.props.editable}
left={left}
top={top}
onMove={this.updateProperties}
onStop={this.mouseUp}
onRightClick={this.rightClick}
>
<div style={{
position: 'relative',
transform: `rotate(${parseInt(desk.rotation)}deg)`
}}
className='deskImg'>
<img style={imgStyle} alt={userName} src={url} />
</div>
</Draggable>
);
}
catch (ex) {
console.log(ex);
return null;
}
}//buildDesks
}
export default connect(mapDesksStateToProps, mapDesksDispatchToProps)(Desk);
//layout_creators.js
export const Layout_Undo_Change = () => (dispatch, getState) => {
const state = getState();
const desks = clone(state.layout_moveData);
console.log("1", state.layout_moveData.present)
//if no past to undo to
if (desks.past.length === 0) return;
const previous = clone(desks.past[desks.past.length - 1]);
desks.past.shift();
const undoPast = clone(desks.past);
// const undoPast = clone(desks.past).slice(0, desks.past.length - 1);
const undoFuture = [clone(desks.present), ...clone(desks.future)]
const undoDesks = { past: undoPast, present: previous, future: undoFuture };
dispatch({ type: ActionTypes.LAYOUT_UNDO_MOVES, payload: clone(undoDesks) });
console.log("2", state.layout_moveData.present)
let init = fetchInit();
init.method = "POST";
const deskData = { mode: 'UPDATEMANY', data: previous };
init.body = JSON.stringify(deskData);
let myReq = new Request(`/dataAPI/Layout/`, init);
fetch(myReq)
.then((response) => {
if (response.ok) {
return response;
}
else {
var error = new Error("Error " + response.statusText);
error.response = response;
throw error;
}
}, (error) => {
var err = new Error(error.message);
throw err;
})
.catch(err => {
return dispatch({
type: ActionTypes.LAYOUT_FAILED,
payload: err.message
});
});
}
//layout_reducer.js
import * as ActionTypes from '../ActionTypes';
export const layout = (state = {
isLoading: true,
isLoadingMap: false,
isLoadingDesks: false,
desksLoaded: false,
mapLoaded: false,
currentMap: null,
currentDesk: null,
maps: [],
deskTypes: [],
editMode: false,
errMess: null
}, action) => {
switch (action.type) {
case ActionTypes.LAYOUT_SITES_LOADING:
return { ...state, isLoading: true };
case ActionTypes.LAYOUT_DESKS_LOADING:
return { ...state, isLoadingDesks: true, desksLoaded: false };
case ActionTypes.LAYOUT_MAP_LOADING:
return {
...state, isLoadingMap: true, desks: [],
currentDesk: null, editMode: false, desksLoaded: false
};
case ActionTypes.LAYOUT_MAP_LOADED:
return { ...state, isLoadingMap: false, mapLoaded: true, maps: action.payload };
case ActionTypes.LAYOUT_MAPS_LOADED:
return { ...state, maps: action.payload, isLoading: false };
case ActionTypes.LAYOUT_DESKTYPES_LOADED:
return { ...state, deskTypes: action.payload };
case ActionTypes.LAYOUT_SET_CURRENT_DESK:
return { ...state, currentDesk: action.payload };
case ActionTypes.LAYOUT_SET_EDITMODE:
return { ...state, editMode: action.payload };
case ActionTypes.LAYOUT_DESK_DELETED:
return { ...state, currentDesk: null }
case ActionTypes.LAYOUT_DESKS_LOADED:
return { ...state, currentDesk: null, isLoadingDesks: false, desksLoaded: true }
case ActionTypes.LAYOUT_SET_ACTIVE_MAP:
return { ...state, currentMap: action.payload, desksLoaded: false };
case ActionTypes.LAYOUT_FAILED:
return {
...state, isLoadingMap: false, isLoadingDesks: false, desksLoaded: false,
errMess: action.payload, pageUsageData: []
};
case ActionTypes.LAYOUT_RESTORE_ALL:
return {
...state,
isLoading: true, isLoadingMap: false, mapLoaded: false, currentMap: null,
maps: [], desks: [], deskTypes: [], editMode: false,
errMess: null, desksLoaded: false
}
default:
return state;
}
}
export const layout_moveData = (state = {
past: [],
present: null,
future: []
}, action) => {
switch (action.type) {
case ActionTypes.LAYOUT_DESKS_LOADING:
return { ...state, present: [], past: [], future: [] };
case ActionTypes.LAYOUT_DESKS_LOADED:
return { ...state, present: action.payload, past: [], future: [] };
case ActionTypes.LAYOUT_DESK_DELETED:
return { ...state, present: action.payload.present, past: action.payload.past, future: action.payload.future };
case ActionTypes.LAYOUT_RESTORE_ALL:
return { ...state, present: [], past: [], future: [] };
case ActionTypes.LAYOUT_SET_MOVES:
return { ...state, present: action.payload.present, past: action.payload.past, future: action.payload.future };
case ActionTypes.LAYOUT_UNDO_MOVES:
return { ...state, present: action.payload.present, past: action.payload.past, future: action.payload.future };
case ActionTypes.LAYOUT_REDO_MOVES:
return { ...state, present: action.payload.present, past: action.payload.past, future: action.payload.future };
default:
return state
}
}
I have extrapolated all items in the page to separate components as in React redux state change does not cause update even after deep copy of all state data. This allowed for better stack handling.
Preface: I'm new to react.
I'm creating a project based on React, Redux.
I want to set a loading status when I press the register button on the register component.
I did a lot of research for a possible solution, but wasn't able to find anything useful for my situation.
What's the best way to fix this?
Register reducer
const initialState = {
pending: false,
users: [],
error: null,
showModal: false,
loading: false
}
export function userReducer(state = initialState, action) {
switch (action.type) {
case 'TOGGLE_LOADING': return {
...state,
loading: !state.loading
}
case 'USER_ADD':
{
debugger;
state.users = state.users.concat(action.payload);
return {
...state,
loading: false,
users: state.users
}
}
case FETCH_USERS_PENDING:
return {
...state,
pending: true,
loading: false
}
case FETCH_USERS_SUCCESS:
return {
...state,
pending: false,
loading: false,
users: action.payload
}
case FETCH_USERS_ERROR:
return {
...state,
pending: false,
loading: false,
error: action.error
}
default:
return state;
}
}
export default userReducer;
Register action
export const userRegisterFetch = user => {
user.Username = user.Mobile;
return dispatch => {
dispatch({ type: 'TOGGLE_LOAD' })
return fetch(`${baseUrl}/users/Register`,
{
method: 'POST', body: JSON.stringify(user), headers: {
'Content-Type': 'application/json',
}
}
)
.then(resp => resp.json())
.then(data => {
if (data.result == undefined) {
return alert('error');
}
if (!data.result) {
alert(data.message);
}
else {
const location = {
pathname: '/Users/Confirm',
state: { mobile: user.Mobile }
}
history.push(location);
}
})
}
}
Register.js component
const { loading } = this.props
return(
{loading ? <Loading /> :
<Form>
...my form
</Form>
}
)
I think you only need 3 reducers to be honest, FETCH_USERS_INIT, FETCH_USERS_SUCCESS and FETCH_USERS_FAIL.
Register reducer
const initialState = {
users: [],
loading: false,
error: null
};
function userReducer(state = initialState, action) {
switch (action.type) {
case 'FETCH_USERS_INIT':
return {
...state,
loading: true
};
case 'FETCH_USERS_SUCCESS':
return {
...state,
loading: false,
error: null,
users: action.payload.users
};
case 'FETCH_USERS_FAIL':
return {
...state,
loading: false,
error: action.payload.error
};
default:
return initialState;
}
}
export default userReducer;
export const userRegisterFetch = user => {
user.Username = user.Mobile;
return async dispatch => {
dispatch({ type: 'FETCH_USERS_INIT' });
fetch(`${baseUrl}/users/Register`, {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json'
}
}).then(response => {
/* dispatch 'FETCH_USERS_SUCCESS' with the list of users */
}).catch(error => {
/* dispatch 'FETCH_USERS_FAIL' with the corresponding error */
});
};
};
The action is not finished but i think it's clear how to finish it. Let me know if you have any doubt.
Would you mind helping me to be clear about mapDispatchToProps.
I have a example code like this:
// ----------------------- not use mapDispatchToProps -----------------------------
//var onSubmit = (event) => {
// event.preventDefault()
// var email = event.target.elements[0].value
// var password = event.target.elements[1].value
// // const path = `/repos/${userName}/${repo}`
// store.dispatch(action.requestLogin({username:email,password:password}))
// // store.dispatch(action.receiveLogin({user{username:email,password:password,objectId:1,sessionToken:"asdfg"}}))
// }
// ----------------------- use mapDispatchToProps -----------------------------
const mapDispatchToProps = (dispatch) => {
return {
onSubmit: (event) => {
event.preventDefault()
var email = event.target.elements[0].value
var password = event.target.elements[1].value
dispatch(action.requestLogin({username:email,password:password}))
}
}
}
const mapStateToProps = state => ({
// onSubmit: onSubmit,
error: state.login.error
});
var LoginPage = ({ onSubmit,error }) => {
return (
`<div className="row">
<div className="col-md-12">
<LoginFormComponent className="account-form text-center" title="Log in to Portal" error={error !== null ? error : ""} onSubmit={onSubmit}/>
</div>
</div>`
)
}
export default connect(mapStateToProps,mapDispatchToProps)(LoginPage)
//-----------------------------and this is the reducer -------------------------------------
export default function login(state = {
logedAt: null,
isLogging: false,
error: null,
data: {},
}, action) {
switch (action.type) {
case types.LOGIN_REQUEST:
return update(state, {
isLogging: { $set: true },
error: { $set: null }
});
case types.LOGIN_SUCCESS:
return update(state, {
data: { $set: action.body },
isLogging: { $set: false },
logedAt: { $set: action.logedAt },
});
case types.LOGIN_FAILURE:
return update(state, {
logedAt: { $set: null },
error: { $set: action.error },
});
default:
return state;
}
}
//-----------------------------and the middleware -------------------------------------
export function login({dispatch, getState}){
return next => action => {
return callLogin().then(
response => dispatch(Object.assign({},{
body: response,
logedAt: Date.now(),
type: LOGIN_SUCCESS,
isFetching: false,
isAuthenticated: true,
// callLogin: callLogin,
})),
error => dispatch(Object.assign({} ,{
error: error.response.text,
type: LOGIN_FAILURE,
isFetching: false,
isAuthenticated: false,
// callLogin: callLogin,
}))
);
}
}
When I don't use the mapDispatchToProps, I just can dispatch the action for type:LOGIN_REQUEST but not the LOGIN_SUCCESS,LOGIN_FAILURE, when use mapDispatchToProps, it work. could you explain for me
Thanks a lot.