Redux: Should i clear state on unmount - reactjs

Having a strange bug/issue with redux. I have a component in an app that displays data in a table. this table is used across numerous routes and i'm passing in a url for the end point.
When i click between the routes they work fine but some fields in the table have a button to open a slide out menu. when i do the redux actions is dispatched and it fires it for all routes i have been to and not the one i'm on.
Action
export const clearTableData = () => dispatch => {
dispatch({
type: TYPES.CLEAR_TABLE_DATA,
});
};
export const getTableData = (url, limit, skip, where, sort, current) => async dispatch => {
try {
dispatch({ type: TYPES.FETCH_TABLE_DATA_LOADING });
const response = await axios.post(url, {
limit,
skip,
where,
sort
});
await dispatch({
type: TYPES.FETCH_TABLE_DATA,
payload: {
url: url,
data: response.data,
limit: limit,
skip: skip,
where: where,
sort: sort,
pagination: {
total: response.data.meta.total,
current: current,
pageSizeOptions: ["10", "20", "50", "100"],
showSizeChanger: true,
showQuickJumper: true,
position: "both"
}
}
});
dispatch({ type: TYPES.FETCH_TABLE_DATA_FINISHED });
} catch (err) {
dispatch({ type: TYPES.INSERT_ERROR, payload: err.response });
}
};
Reducer
import * as TYPES from '../actions/types';
export default (state = { loading: true, data: [], pagination: [] }, action) => {
switch (action.type) {
case TYPES.FETCH_TABLE_DATA:
return { ...state, ...action.payload };
case TYPES.FETCH_TABLE_DATA_LOADING:
return { ...state, loading: true };
case TYPES.FETCH_TABLE_DATA_FINISHED:
return { ...state, loading: false };
case TYPES.CLEAR_TABLE_DATA:
return {};
default:
return state;
}
};
component
componentDidMount() {
this.fetch();
websocket(this.props.websocketRoute, this.props.websocketEvent, this.fetch);
}
fetch = () => {
// Fetch from redux store
this.props.getTableData(
this.props.apiUrl,
this.state.limit,
this.state.skip,
{ ...this.filters, ...this.props.defaultWhere },
`${this.state.sortField} ${this.state.sortOrder}`,
this.state.current)
}
const mapStateToProps = ({ tableData }) => ({
tableData,
});
const mapDispatchToProps = dispatch => (
bindActionCreators({ getTableData }, dispatch)
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchableTable);
Websocket
import socketIOClient from 'socket.io-client';
import sailsIOClient from 'sails.io.js';
export const websocket = (websocketRoute, websocketEvent, callback) => {
if (websocketRoute) {
let io;
if (socketIOClient.sails) {
io = socketIOClient;
} else {
io = sailsIOClient(socketIOClient);
}
io.sails.transports = ['websocket'];
io.sails.reconnection = true;
io.sails.url = process.env.REACT_APP_WEBSOCKECTS_URL
io.socket.on('connect', () => {
io.socket.get(websocketRoute, (data, jwres) => {
console.log("connect data sss", data)
console.log("connect jwres sss", jwres)
});
});
io.socket.on(websocketEvent, (data, jwres) => {
console.log("websocket", callback)
callback();
})
io.socket.on('disconnect', () => {
io.socket._raw.io._reconnection = true;
});
}
}
So for e.g if i'm on a route for cars i'll pass in api/cars as url, and for trucks api/trucks. if i've been to both these pages they get fired.
should i be doing something to unmount and reset state to blank?
edit to add render
render() {
const { filters, columns, expandedRowRender, rowClassName, style } = this.props;
return (
<Table
bordered
columns={columns}
rowKey={record => record.id}
dataSource={this.props.tableData.data.items}
pagination={this.props.tableData.pagination}
loading={this.props.tableData.loading}
onChange={this.handleTableChange}
expandedRowRender={expandedRowRender}
rowClassName={rowClassName} />
);

Basic idea is, define a new action type in reducer file to clear the table data, and before unmount dispatch that action.
In Component:
componentDidMount() {
this.fetch();
}
componentWillUnmount() {
this.props.clearTableData();
}
const mapDispatchToProps = dispatch => (
bindActionCreators({ getTableData, clearTableData }, dispatch)
)
Action:
export const clearTableData = () => {
return { type: TYPES.CLEAR_TABLE_DATA };
};
Reducer:
case TYPES.CLEAR_TABLE_DATA: {
// reset the table data here, and return
}

Related

Setting data in React for useContext

could you please help with setting state in useContext ?
I am trying to send video variable through useEffect to setMediaContent to update mediaContext.media object. My goal is to have several media(video,images,posters) objects in media object, e.g.
https://codesandbox.io/s/friendly-sunset-o67nvj?file=/src/context.js
Thanks in advance
Try using a reducer:
import { createContext, useReducer } from "react";
// default state
const contextDefaultValues = {
video: { url: "", title: "", shown: false },
openVideo: () => {},
closeVideo: () => {},
mediaContent: { media: {}, title: "most" },
setMediaContent: () => {},
};
const MainReducer = (state = contextDefaultValues, action) => {
const { type, payload } = action;
switch (type) {
case "setMediaContent": {
const { media, title } = payload;
return { ...state, media: { ...state.media, ...media }, title: title };
}
case "closeVideo": {
return { ...state, shown: false };
}
case "openVideo": {
const { url, title } = payload;
return { ...state, url, title, shown: true };
}
default: {
throw new Error(`Unhandled action type: ${type}`);
}
}
};
export const MainContext = createContext(contextDefaultValues);
// provider recuder
const MainProvider = ({ children }) => {
const [state, dispatch] = useReducer(MainReducer, contextDefaultValues);
const openVideo = (url, title) => {
dispatch({ type: "openVideo", payload: { url, title, shown: true } });
};
const closeVideo = () => {
dispatch({ type: "closeVideo", payload: { shown: false } });
};
const setMediaContent = (media, title) => {
dispatch({ type: "setMediaContent", payload: { media, title } });
};
return (
<MainContext.Provider
value={{ ...state, setMediaContent, closeVideo, openVideo }}
>
{children}
</MainContext.Provider>
);
};
export default MainProvider;
Based on the provided sandbox, You have the render of the provider wrapped in the setMediaContent function.
Look at the { and } at line 36 and 58.
Code screenshot with misplaced brackets

Why is my dispatch/console.log not firing when I call the action?

I was working on my final project for Flatiron and I came across a bug while working on my frontend. I tried many things, but always came back to this one issue. My callback function inside my dispatch is not firing. So while I may be interested to know whether my code should be refactored/fixed of bugs, the biggest problem is that I cannot run my dispatches through an action.
Here is my generic container:
import { useEffect } from "react"
import { connect } from "react-redux"
import * as actions from "../../actions/index"
import Component from "./Component"
const Container = (props) => {
useEffect(() => {
actions.menuItemsFetchRandom(8)
}, [])
const menuItemComponents = props.menuItems.menuItems.map((menuItem) => {
return (
<Component key={menuItem.key} menuItem={menuItem} />
)
})
return (
<div className="container">
{
props.menuItems.fetching
? "loading..."
: (
props.menuItems.error === ""
? menuItemComponents
: props.menuItems.error
)
}
</div>
)
}
const mapStateToProps = (state) => {
return {
menuItems: state.menuItems
}
}
export default connect(mapStateToProps)(Container)
And my actions.menuItemsFetchRandom() comes from /actions/menuItems.js:
import * as common from "./common"
import * as reducers from "../reducers/index"
const MENU_ITEMS_URL = common.API_URL + "menu_items/"
export const menuItemsFetchMany = (options) => {
return (dispatch) => {
dispatch({
type: reducers.MENU_ITEMS_FETCH_REQUEST
})
fetch(MENU_ITEMS_URL + options).then((response) => {
return response.json()
}).then((menuItems) => {
dispatch({
type: reducers.MENU_ITEMS_FETCH_SUCCESS,
payload: menuItems
})
}).catch((error) => {
dispatch({
type: reducers.MENU_ITEMS_FETCH_ERROR,
payload: error
})
})
}
}
export const menuItemsFetchRandom = (numberOfItems) => {
console.log("hi")
return (dispatch) => {
console.log("Hello")
dispatch({
type: reducers.MENU_ITEMS_FETCH_REQUEST
})
fetch(MENU_ITEMS_URL).then((response) => {
return response.json()
}).then((menuItems) => {
const length = menuItems.length
if (numberOfItems > length) {
numberOfItems = length
}
dispatch({
type: reducers.MENU_ITEMS_FETCH_SUCCESS,
payload: (() => {
const result = []
while (result.length !== length) {
const choice = menuItems[common.getRandomInt(length)]
if (result.includes(choice)) {
continue
}
result.push(choice)
}
})()
})
}).catch((error) => {
dispatch({
type: reducers.MENU_ITEMS_FETCH_ERROR,
payload: error
})
})
}
}
My /reducers/menuItems.js looks like this:
export const MENU_ITEMS_FETCH_REQUEST = "MENU_ITEMS_FETCH_REQUEST"
export const MENU_ITEMS_FETCH_SUCCESS = "MENU_ITEMS_FETCH_SUCCESS"
export const MENU_ITEMS_FETCH_ERROR = "MENU_ITEMS_FETCH_ERROR"
const initialState = {
menuItems: [],
error: "",
fetching: false
}
const menuItems = (state=initialState, action) => {
switch (action.type) {
case MENU_ITEMS_FETCH_REQUEST: {
return {
...state,
error: "",
fetching: true
}
}
case MENU_ITEMS_FETCH_SUCCESS: {
return {
...state,
menuItems: action.payload,
error: "",
fetching: false
}
}
case MENU_ITEMS_FETCH_ERROR: {
return {
...state,
error: action.payload,
fetching: false
}
}
default: {
return state
}
}
}
export default menuItems
But that doesn't seem to matter as the console.log inside the callback function in menuItemsFetchRandom() does not fire. I get the console.log("hi"), but not the console.log("Hello"). Which to me tells me its either something wrong with my code, or something wrong with redux-thunk.
You need to actually dispatch that action, not just call the action creator.
const dispatch = useDispatch();
useEffect(() => {
dispatch(actions.menuItemsFetchRandom(8))
}, [])
PS: also, there is no need to use connect in function components. Using useSelector and useDispatch is much easier and the official recommendation. Additionally, you are writing a pretty outdated style of redux that makes you write a multitude of the code that is required with modern redux. You are likely following very outdated tutorials.
Please see the official tutorials at https://redux.js.org/tutorials/index

React Redux - Loading state too slow - how to solve it

I'm trying to create a loading state for my Redux but it looks to "slow" to get updated.
First action fetchDB => setLoading: true => once over setLoading: false
Second action fetchCat => doesn't have the time to fire it that crashes
Really simple:
set loading action:
export const setLoading = () => {
return async (dispatch) => {
await dispatch({ type: SET_LOADING }); // no payload by default goes to true
};
};
set loading reducer:
import {
FETCH_DB,
SET_LOADING,
} from "../types"
const initalState = {
db: [],
loading: false,
}
export default (state = initalState, action) => {
switch (action.type) {
// this like the other cases sets loading to FALSE
case FETCH_DB:
return {
...state,
db: action.payload,
current: null,
loading: false,
}
case FETCH_CAT_FOOD:
return {
...state,
food: action.payload,
loading: false,
}
case FETCH_CAT_DESIGN:
return {
...state,
design: action.payload,
loading: false,
}
case SET_LOADING:
return {
...state,
loading: true,
}
default:
return state
}
}
then action I use that creates the problem:
export const fetchCat = kindof => {
return async dispatch => {
dispatch(setLoading()) // looks like that it doesn't get fired
const response = await axios
.get(`http://localhost:5000/api/categories/${kindof}`)
.then(results => results.data)
try {
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
and then the file (a custom component) that creates the problem.
It crashes cause categories.map is undefined.
It doesn't find loading: true so the loader doesn't stop.
import React, { useState, useEffect, Fragment } from "react"
import { Spinner } from "react-bootstrap"
import { connect, useDispatch, useSelector } from "react-redux"
import CatItem from "./CatItem" // custom component
import { fetchCat, setLoading } from "../../../store/actions/appActions"
const MapCat = ({ kindof, loading, categories }) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchCat(kindof)) // gives the category I want to fetch
// eslint-disable-next-line
}, [categories])
if (!loading) {
return (
<Spinner animation="border" role="status">
<span className="sr-only">Loading...</span>
</Spinner>
)
} else {
return (
<Fragment>
<div>
{categories.map(item => (
<CatItem item={item} />
))}
</div>
</Fragment>
)
}
}
const mapStateToProps = (state, kindof) =>
({
loading: state.appDb.loading,
categories: state.appDb[kindof],
})
export default connect(mapStateToProps, { fetchCat, setLoading })(MapCat)
I think that it is supposed to work like this:
loading: false (by default) => true => time to fetch => false
But doesn't look like working. Any idea?
Firstly setLoading needs to return a plain object with type and payload
export const setLoading = () => ({ type: SET_LOADING });
In fetchCat the then is not required. Also async await for dispatch is not required.
export const fetchCat = (kindof) => {
return (dispatch) => {
dispatch(setLoading()); //<---this should now be ok.
const response = await axios.get(`http://localhost:5000/api/categories/${kindof}`)
// .then((results) => results.data); //<----- not required as you are using await
try {
dispatch({ type: `FETCH_CAT_${kindof}`, payload: response.data }); //<--- use response.data ...also async/await for dispatch is not rquired.
} catch (error) {
console.log("await error", error);
}
};
};
The 2nd arg of mapStateToProps is ownProps which is an object
const mapStateToProps = (state, ownProps) =>
({
loading: state.appDb.loading,
categories: state.appDb[ownProps.kindof],
})
You have quite a bit different way of calling dispatch. Let me list them out
dispatch(fetchCat(kindof)) // gives the category I want to fetch
await dispatch({ type: `FETCH_CAT_${kindof}`, payload: response })
You can see, await or not basically is the way you use async operation. However dispatch takes type and payload to function, which means you have to make sure what you send to dispatch is with the right object. Of course Redux does accept custom format via plugins, so maybe if you throw it a async as input, the reducer might understand it as well?
Please double check each dispatch first, for example, write a function that only dispatch one type of action. Only after you make each call working, don't move to assemble them together into a bundled call.

react redux, Action from .then not dispatching

I'm trying to fetch props into my componentDidUpdate method.
Sadly second action from .then block doesn't dispatch.
I have the following:
My AddPerson.js which is a form to add Person. There I have following states:
constructor(props) {
super(props);
this.state = {
loading: false,
firstName: '',
secondName: '',
email: '',
date: '',
};
}
Whole is connected to redux:
function mapDispatchToProps(dispatch) {
return {
addPerson: data => dispatch(addPerson(data))
};
}
const mapStateToProps = state => {
return { data: state.data };
};
const Person = connect(
mapStateToProps,
mapDispatchToProps
)(AddPerson);
export default Person;
Then I have action dispatcher like that:
export const addPerson = (payload) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(PATH + '/attendant', {
payload,
})
.then(res => {
dispatch(addTodoSuccess(res));
})
.catch(err => {
dispatch(addTodoFailure(err));
});
};
};
const addTodoSuccess = payload => ({
type: ADD_PERSON,
data: {
payload
}
});
const addTodoStarted = () => ({
type: ADD_PERSON,
data:
"loading"
});
const addTodoFailure = error => ({
type: ADD_PERSON,
data: {
error
}
});
And my reducer:
function reducer(state = {} , action) {
switch (action.type) {
case ADD_PERSON:
return Object.assign({}, state, {
data: action.data,
})
default:
return state;
}
}
export default reducer;
When fetch happens in the action, there is firstly dispatched type of action loading then after promise solves I want to dispatch action that is in .then block. What am I missing?
EDIT:
My componentDidUpdate looks like that:
componentDidUpdate(prevProps) {
console.log(prevProps)
if (prevProps.data !== this.state.data) {
console.log(prevProps.data)
}
}

Redux state's nested object is available but unaccessible within component

enter code hereI'm providing Redux Global State to my whole react app through a Provider wrapper in my app.js file.
I've no problem accessing any other piece of state other than "Current Profile".
Here is the component:
import React, { Fragment, useEffect } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { loadTargetProfiles, loadCurrentProfile } from "../../actions/profile";
const Friends = ({
loadCurrentProfile,
loadTargetProfiles,
profile: { currentProfile, targetProfiles, targetProfilesAreLoading },
}) => {
useEffect(() => {
loadTargetProfiles();
loadCurrentProfile();
}, []);
console.log(currentProfile);
return (
...
};
Friends.propTypes = {
loadTargetProfiles: PropTypes.func.isRequired,
loadCurrentProfile: PropTypes.func.isRequired,
profile: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
profile: state.profile,
});
export default connect(mapStateToProps, {
loadCurrentProfile,
loadTargetProfiles,
})(Friends);
here is the loadCurrentProfile action responsible for providing the currentProfile piece of state.
export const loadCurrentProfile = () => async (dispatch) => {
try {
const res = await api.get("/profile/current");
dispatch({
type: LOAD_CURRENT_PROFILE,
payload: res.data,
});
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status },
});
}
};
here is the relevant part of the Reducer
const initialState = {
currentProfile: null,
targetProfile: null,
targetProfiles: [],
currentProfileIsLoading: true,
targetProfileIsLoading: true,
targetProfilesAreLoading: true,
error: {},
};
//
// Export Reducer
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case LOAD_CURRENT_PROFILE:
return {
...state,
currentProfile: payload,
currentProfileIsLoading: false,
};
here is the API that's getting hit:
router.get("/current", auth, async (req, res) => {
try {
const profile = await Profile.findOne({
user: req.user.id,
}).populate("user", ["_id", "username", "registerdate"]);
if (!profile) {
return res
.status(400)
.json({ msg: "There is no profile for this user." });
}
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error...");
}
});
here is the console
here is the currentProfile piece of state expanded:
when i try to reach into the currentProfile piece of state, for example
const Friends = ({
loadCurrentProfile,
loadTargetProfiles,
profile: { currentProfile: {
avatar
}, targetProfiles, targetProfilesAreLoading },
}) => {
useEffect(() => {
loadTargetProfiles();
loadCurrentProfile();
}, []);
console.log(avatar);
It give me the error:
TypeError: Cannot read property 'avatar' of null
here is Redux Dev Tools screenshot:
Fix:
I (temporarily) got rid of the error by accessing the inner state after declaring the function.
const Friends= ({
profile: { currentProfileIsLoading },
currentProfile,
}) => {
currentProfile && console.log(currentProfile.avatar);
and it works for now but it certainly isn't the most elegant solution. Is there a way to add this guard in the function declaration in order to set the state in one place?
Issue: Both targetProfiles and targetProfilesAreLoading are truthy values in your state, but currentProfile is null until the GET resolves. You can't access the avatar property of a null object.
You can provide some default argument value for profile, this only works really though if profile is undefined, null counts as a defined value.
const Friends = ({
loadCurrentProfile,
loadTargetProfiles,
profile: {
currentProfile = {},
targetProfiles,
targetProfilesAreLoading
},
}) => {
useEffect(() => {
loadTargetProfiles();
loadCurrentProfile();
}, []);
console.log(currentProfile);
return (
...
};
You can also use a guard on the possibly undefined/null object
currentProfile && currentProfile.avatar
Another alternative is to use a state selector library like reselect that allows you to pull/augment/derive/etc... state values that get passed as props. This also allows you to set default/fallback values for state. It pairs with redux nicely.

Resources