React-redux, value from store get reset - javascript

I'm setting a new value in the store by this action triggered with a click on a button:
onClick={() => { this.handleChange(values)
The function is:
handleChange = (values) => {
const { setComponentSelected } = this.props;
//some code
setComponentSelected(values);
//some code
}
With that, I can see in the Redux Dev Tools that my state is changed:
and in the dom I can see this changes ok.
But, another component is re-rendering this component where I have the problem. In this componente, I'm rendering like this way:
render() {
const { getComponentSelected } = this.props;
console.log('getComponentSelected', getComponentSelected)
And this is how I use the store with redux:
const mapStateToProps = state => ({
getComponentSelected: state.displayReportRecordReducers.setComponentSelected_data
});
const mapDispatchToProps = {
setComponentSelected: displayReportRecordActions.setComponentSelected,
}
I can see the changes from the this.handleChange at first, but everytime that the component gets mounted again, the values for this console.logis the old one, and not the same as I have in the store. There's no actions made with the remounting, the action that changes the store is only triggered with the this.handleChange so I don't know why is changes the value, and not showing what I have in the store.
Any idea?
EDIT:
My action is:
const setComponentSelected = (value: any) => {
return async (dispatch: any) => {
try {
dispatch(success(value));
}
catch (err) {
console.log('error:', err);
};
}
function success(value) {
return { type: displayReportRecordConstants.SET_COMPONENT_SELECTED, payload: { value } };
}
};
My reducer is:
case displayReportRecordConstants.SET_COMPONENT_SELECTED:
return update(state, {
setComponentSelected_data: { $set: payload.value },
});
EDIT 2: this scrennshots show the consistence between the store and the data that I render, at first click:
But if the components get re-rendered by another one, I lost the data in the console logs, whichs it's supossed to be get from the store. But the Redux dev tools show no changes!

Related

Websocket event receiving old redux state in React app

I am building a Chat application using Reactjs and Redux. I have 2 components called ChatHeads and ChatBox which get mounted side-by-side at the same time.
In the ChatHeads component, the selection of User (to whom you want to chat with) is possible and this selection is stored in the redux store as chatInfo.
ChatHeads Component:
function ChatHeads(props) {
const {
dispatch,
userInfo,
userId
} = props;
const [chatHeads, setChatHeads] = useState([]);
const handleChatHeadSelect = (chatHead, newChat = false) => {
dispatch(
chatActions.selectChat({
isNewChat: newChat,
chatId: chatHead.chat._id,
chatUser: chatHead.user
})
);
};
const loadChatHeads = async () => {
const response = await services.getRecentChats(userId, userInfo);
setChatHeads(response.chats);
};
useEffect(() => loadChatHeads(), [userInfo]);
return (
// LOOPING THOUGH ChatHeads AND RENDERING EACH ITEM
// ON SELECT OF AN ITEM, handleChatHeadSelect WILL BE CALLED
);
}
export default connect(
(state) => {
return {
userInfo: state.userInfo,
userId: (state.userInfo && state.userInfo.user && state.userInfo.user._id) || null,
selectedChat: (state.chatInfo && state.chatInfo.chat && state.chatInfo.chat._id) || null
};
},
null,
)(ChatHeads);
Chat Actions & Reducers:
const initialState = {
isNewChat: false,
chatId: '',
chatUser: {},
};
const chatReducer = (state = initialState, action) => {
let newState;
switch (action.type) {
case actions.CHAT_SELECT:
newState = { ...action.payload };
break;
default:
newState = state;
break;
}
return newState;
};
export const selectChat = (payload) => ({
type: actions.CHAT_SELECT,
payload,
});
In the ChatBox component, I am establishing a socket connection to the server and based on chatInfo object from the global store & ws events, I perform some operations.
ChatBox Component:
let socket;
function ChatBox(props) {
const { chatInfo } = props;
const onWSMessageEvent = (event) => {
console.log('onWSMessageEvent => chatInfo', chatInfo);
// handling event
};
useEffect(() => {
socket = services.establishSocketConnection(userId);
socket.addEventListener('message', onWSMessageEvent);
return () => {
socket.close();
};
}, []);
return (
// IF selectedChatId
// THEN RENDER CHAT
// ELSE
// BLANK SCREEN
);
}
export default connect((state) => {
return {
chatInfo: state.chatInfo
};
}, null)(ChatBox);
Steps:
After both the components are rendered, I am selecting a user in the ChatHeads components.
Using the Redux DevTools, I was able to observe that the chatInfo object has been populated properly.
chatInfo: {
isNewChat: false,
chatId: '603326f141ee33ee7cac02f4',
chatUser: {
_id: '602a9e589abf272613f36925',
email: 'user2#mail.com',
firstName: 'user',
lastName: '2',
createdOn: '2021-02-15T16:16:24.100Z',
updatedOn: '2021-02-15T16:16:24.100Z'
}
}
Now, whenever the 'message' event gets triggered in the ChatBox component, my expectation is that the chatInfo property should have the latest values. But, I am always getting the initialState instead of the updated ones.
chatInfo: {
isNewChat: false,
chatId: '',
chatUser: {}
}
What am I missing here? Please suggest...
The reason for this behaviour is that when you declare your callback
const { chatInfo } = props;
const onWSMessageEvent = (event) => {
console.log('onWSMessageEvent => chatInfo', chatInfo);
// handling event
};
it remembers what chatInfo is right at this moment of declaration (which is the initial render). It doesn't matter to the callback that the value is updated inside the store and inside the component render scope, what matters is the callback scope and what chatInfo is referring to when you declare the callback.
If you want to create a callback that can always read the latest state/props, you can instead keep the chatInfo inside a mutable reference.
const { chatInfo } = props;
// 1. create the ref, set the initial value
const chatInfoRef = useRef(chatInfo);
// 2. update the current ref value when your prop is updated
useEffect(() => chatInfoRef.current = chatInfo, [chatInfo]);
// 3. define your callback that can now access the current prop value
const onWSMessageEvent = (event) => {
console.log('onWSMessageEvent => chatInfo', chatInfoRef.current);
};
You can check this codesandbox to see the difference between using ref and using the prop directly.
You can consult the docs about stale props and useRef docs
Broadly speaking, the issue is that you're trying to manage a global subscription (socket connection) inside a much more narrow-scope component.
Another solution without useRef would look like
useEffect(() => {
socket = services.establishSocketConnection(userId);
socket.addEventListener('message', (message) => handleMessage(message, chatInfo));
return () => {
socket.close();
};
}, [chatInfo]);
In this case the message event handler is passed the necessary information through arguments, and the useEffect hook re-runs every time we get a new chatInfo.
However, this probably doesn't align with your goals unless you want to open a separate socket for each chat and close the socket every time you switch to a different chat.
Thus, the "proper" solution would entail moving the socket interaction up in your project. One hint is that you are using userId to open the socket, which means that it's supposed to run once you know your userId, not once the user selects a chat.
To move the interaction up, you could store incoming messages in a redux store and pass the messages to the ChatBox component through props. Or you could create connect to the socket in ChatHeads component and pass the messages down to the ChatBox. Something like
function ChatHeads(props) {
const {
dispatch,
userInfo,
userId
} = props;
const [chatHeads, setChatHeads] = useState([]);
const loadChatHeads = async () => {
const response = await services.getRecentChats(userId, userInfo);
setChatHeads(response.chats);
};
useEffect(() => loadChatHeads(), [userInfo]);
const [messages, setMessages] = useState([]);
useEffect(() => {
socket = services.establishSocketConnection(userId);
socket.addEventListener('message', (msg) => setMessages(messages.concat(msg)));
}, [userId]);
return () => socket.close();
}
return (
// render your current chat and pass the messages as props
)
Or you could create a reducer and dispatch a chatActions.newMessage event and then the messages get to the current chat using redux.
The main point is that if you need chatInfo to open the socket, then every time chatInfo changes, you might have to open a new socket, so it makes sense to add the dependency to the useEffect hook. If it only depends on userId, then move it up to where you get the userId and connect to the socket there.

React-redux component not re-rendering on store props change

My react component is not re-rendering despite its props being updated and I don't understand why.
Here's my component
import { fetchLocations } from 'state/locations/actions';
class Event extends React.Component {
componentDidMount() {
this.props.fetchLocations();
}
render() {
const { locations } = this.props;
return <span>{locations.map((l) => {return <span>{l}</span>;})}</span>;
}
}
const mapStateToProps = (state) => ({
locations: state.locations
})
export default connect(
mapStateToProps,
{ fetchLocations },
)(Event);
Here is my locations action file
export const fetchLocations = () = (dispatch) => {
axios.get('/api/locations')
.then(response => {
const locations = response.data;
dispatch({ type: FETCH_LOCATIONS_SUCCESS, payload: locations });
});
}
And my entities reducer
function entities(state = { locations: {} }, action) {
switch (action.type) {
case FETCH_LOCATIONS_SUCCESS:
return Object.assign({}, state, {
locations: action.payload
})
default:
return state
}
}
After this, my Event component should re-render. It doesn't. Using the react dev tools chrome extension I see that the locations are indeed there as props, but they do not show on the UI.
If I unmount the component by going to a different page and re-mount it, the locations show up properly.
It looks like everything works fine except the re-render is not triggering. componentDidUpdate is never fired.
If I manually do a setTimeout to forceUpdate an arbitrary second later, they show up.
Why isn't my component re-rendering?
Please, try to add key prop to span element of the render method. locations.map((l,key)=> <span key={key} >{l} </span>

Arrow functions only have access to initial state created by useReducer, not updated state

I am trying to access the updated state from useReducer inside of an arrow function in a functional component. However, when I call the state, I'm only getting the initial state object.
The reducer function
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE':
return {
...state,
[action.progress.request]: action.progress
}
case 'REMOVE':
const { [action.progress.request]: value, ...rest } = state
return rest
default:
return state
}
}
The react component
const ProgressProvider = ({ children }: Props) => {
const [state, dispatch] = useReducer(reducer, {})
const start = (request) => {
console.log(state) // expected to see updated state, but instead see initial value
// ... do more things
}
const end = (request) => {
console.log(state)
// ...do more things
}
return (
<ProgressContext.Provider value={{ state, start, end }}>
{children}
</ProgressContext.Provider>
)
}
could be used in an api request like this:
const progress = useContext(ProgressContext)
const getData = async params => {
const url = '/my/endpoint'
progress.start(url)
try {
await axios.get(url, { params })
} catch (error) {
console.error(error)
} finally {
progress.end(request)
}
}
I expect in the start and end functions to be able to see an updated state, but I actually see the initial state {}
In order for state to change, you need to dispatch an action. This can either be done via a click of a button or something else entirely. You should update your start function to be along the lines of the following
const start = request => {
const action = {
type: 'UPDATE',
request
};
dispatch(action);
}
The dispatch(action) will cause an update to the state that will be available on render.

React doesn't recognize state change in reducer

I have a component that makes an API call and then updates the state through a reducer. The problem is, this doesn't work so well cause the data don't get updated in the component, it's like the react didn't notice a state change a never re-rendered the component, but I'm not sure if that's the real issue here. So the component looks like this:
class MyComponent extends Component {
componentDidMount() {
// ajax call
this.props.loadData(1);
}
render() {
return (
<Grid>
<MySecondComponent
currentData={this.props.currentData}
/>
</Grid>
);
}
}
const mapStateToProps = state => ({
reducer state.myReducer,
currentData: state.myReducer.currentData
});
const mapDispatchToProps = dispatch => {
return {
loadData: () => {
HttpClient.getData(id, (data) => {
dispatch(
action_loadCurrentData(
data
)
);
});
},
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent);
I am doing 2 things here: issuing an API call as soon as component is mounted, and then after data is fetched, dispatching action_loadCurrentData
This action looks like this:
//Action
export function action_loadCurrentData(
data
) {
return {
type: 'LOAD_CURRENT_DATA',
payload: {
currentData: data,
}
};
}
and the reducer:
//Reducer
const defaultState = {
};
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
state = {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
export default myReducer;
So the issue here is that the this.props.currentData that I'm passing to MySecondComponent will end up empty, as if I didn't set the data at all. However, If I stop the execution in the debugger and give it a few seconds, the data will be populated correctly, so I'm not sure what I'm doing wrong here?
Don't reassign state, return the newly created object instead
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOAD_CURRENT_DATA':
return {
...state,
currentData: {
myData: {
...state.currentData.myData,
0: action.payload.currentData
}
}
};
}
};
Your reducer needs to return the new state object, which needs to be a different instance from the previous state to trigger components update.
According to redux documentation:
The reducer is a pure function that takes the previous state and an action, and returns the next state.
And
Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().

redux way of doing doesn't work for my sessionStorage

I am trying to learn redux.
I am trying to add favorites functionality through Redux.
so I created actions addFavoriteSPORTSs, reducers SPORTSReducer, and then dispatched in tab-demo.js where i am doing mapDispatchToProps and
mapStateToProps
when I click the heart icon I am adding favorites in session storage window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(action.payload)
);
but the problem is after the refresh the color is not staying in the heart.
I debugged in componentDidMount and I am able to print the favotites get item value but still colr not maintaining.
can you tell me how to fix it.
so that in future I will fix itmyself.
providing my code snippet below
https://codesandbox.io/s/5x02vjjlqp
actions/index.js
import {
ADD_SPORTS,
DELETE_SPORTS,
DELETE_ALL_SPORTS,
ADD_ALL_SPORTSS
} from "./types";
export const addFavoriteSPORTSs = data => ({
type: ADD_ALL_SPORTSS,
payload: data
});
actions/types.js
export const ADD_ALL_SPORTSS = "ADD_ALL_SPORTSS";
tab-demo.js
import { deleteAllPosts, addFavoriteSPORTSs } from "./actions/index";
componentDidMount() {
let favorites = window.sessionStorage.getItem("favoriteValues");
console.log("componentDidMount favorites--->", favorites);
if (favorites) {
this.props.addFavoriteSPORTSs(JSON.parse(favorites));
}
// debugger;
}
const mapDispatchToProps = dispatch => {
return {
onDeleteAllSPORTS: () => {
// console.log("called");
dispatch(deleteAllPosts());
},
addFavoriteSPORTSs: data => {
dispatch(addFavoriteSPORTSs(data));
}
};
};
const mapStateToProps = state => {
return {
SPORTSs: state.SPORTSs
};
};
export default withStyles(styles)(
connect(
mapStateToProps,
mapDispatchToProps
)(ScrollableTabsButtonForce)
);
SPORTSReducer.js
switch (action.type) {
case ADD_ALL_SPORTSS:
window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(action.payload)
);
return action.payload;
case ADD_SPORTS:
state = state.filter(comment => comment.id !== action.payload.id);
value = [...state, action.payload];
console.log("ADD_SPORTS state--->", state);
console.log("ADD_SPORTS value--->", value);
//return [...state, action.payload];
// state = state.filter(SPORTS => SPORTS.SPORTSID !== action.payload.SPORTSID);
// value = [...state, action.payload]
window.sessionStorage.setItem("favoriteValues", JSON.stringify(value));
console.log("JSON.stringify(value)--->", JSON.stringify(value));
console.log("state--->", state);
return state;
When the component mounts you retrieve your favourties and set the redux state via calling your prop method. Your component will receive this new state via mapStateToProps, but it won't update without a suitable lifecycle method like componentDidUpdate or componentWillReceiveProps.
You can check out the lifecycle methods here.
Also, you are mutating your state in redux which is something you want to avoid. See this line:
state = state.filter(comment => comment.id !== action.payload.id);
I would also recommend Redux middleware for these tasks. You can set up middleware that will write to session storage whenever a specific action occurs and you can then rehyrdate Redux from that as well.

Categories

Resources