React hooks useState getting diferrent value from redux state - reactjs

I have react component look like this following code:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
import { createClient, getClients } from "../redux/actions/clients";
function UpdateClient(props) {
let params = useParams();
const { error, successSubmit, clients } = useSelector(
(state) => state.clients
);
const [client, setClient] = useState(clients[0]);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getClients({ id: params.id }));
}, []);
const submitClient = () => {
dispatch(createClient(client));
};
return (
<div>{client.name} {clients[0].name}</div>
);
}
export default UpdateClient;
And the result is different client.name return test1,
while clients[0].name return correct data based on route parameter id (in this example parameter id value is 7) which is test7
I need the local state for temporary saving form data. I don't know .. why it's become different?
Can you please help me guys? Thanks in advance

You are referencing a stale state which is a copy of the clients state.
If you want to see an updated state you should use useEffect for that.
useEffect(() => {
setClient(clients[0]);
}, [clients]);
Notice that duplicating state is not recommended.
There should be a single “source of truth” for any data that changes in a React application.

Related

Redux useSelector returns nothing in child component

I am new to React Native and Redux, and was hoping someone could help out in my issue? I have a parent component that fetches some user data (their location) and dispatches to a redux store:
Parent
import { useDispatch } from 'react-redux'
import { setLocation } from './store/locationSlice'
const App = () => {
const dispatch = useDispatch()
const getLocation = () => {
const location = await fetchLoc()
dispatch(setLocation(location))
}
useEffect(() => {
getLocation()
},[])
}
My child component is intended to retrieve this data using the useSelector hook
Child
import { useSelector } from 'react-redux'
const HomeScreen = () => {
const location = useSelector(state => state.location)
useEffect(() => {
if (location) {
getEntitiesBasedOnLocation(location)
}
},[location])
}
However, in my case, useSelector never retrieves the up-to-date information that i have dispatched in the parent, with location returning undefined. I'm fairly certain there's a simple oversight here, but i'm at a loss as to what this could be. I was under the impression that useSelector subscribes to state changes, so why is it that that my dispatched action that causes a change of state is ignored? Using my debugger, I can see that my state is definitely updated with the correct data, but the child component doesn't pick this up..
Here's my location slice:
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
location: {
id: null,
name: null,
latitude: null,
longitude: null
}
}
const locationSlice = createSlice({
name: 'location',
initialState,
reducers: {
setLocation: (state, action) => {
const { id, name, latitude, longitude } = action.payload
state.location = { id, name, latitude, longitude }
}
}
})
export const { setLocation } = locationSlice.actions
export default locationSlice.reducer
UPDATE
The store is configured by wrapping the App.js component in a Provider component, with the store passed as its props as follows:
Root.js
import { configureStore } from '#reduxjs/toolkit'
import { Provider } from 'react-redux'
import locationReducer from './src/store/locationSlice'
import App from './src/App'
const Root = () => {
const store = configureStore({ reducer: locationReducer })
return (
<Provider store={store)>
<App />
</Provider>
)
}
The issue is in your selector. You've created the slice called 'location' and within that slice you've got your state { location: {...}}. So from the perspective of the selector (which accesses your global state, not just the location slice) the path to your data would be state.location.location. But your selector is trying to read out of state.location which only has a location prop. Anything else you tried to read out would be undefined.
It is common to export a custom selection function from the slice configuration. Remember that the selector must take exactly the data that you want to share in your component tree (locationSlice.state.location in this case). This is not mandatory, it is just to facilitate development.
// locationSlice
import { createSlice } from '#reduxjs/toolkit'
//...
export const { setLocation } = locationSlice.actions
export const selectLocation = (state) => state.location.location
export default locationSlice.reducer
// Child
import { useSelector } from 'react-redux'
import {selectLocation} from './src/store/locationSlice'
const HomeScreen = () => {
const location = useSelector(selectLocation)
//...
}
My workaround was to move my getLocation() function in the parent to the child component. useSelector now gets the state as expected. I feel that this work-around defeats the object of having global state access though, and i could probably just use local state rather than Redux.

React router v6 history.listen

In React Router v5 there was a listen mehtode on the history object.
With the method I wrote a usePathname hook to rerender a component on a path change.
In React Router v6, this method no longer exists. Is there an alternative for something like this? I would hate to use useLocation because it also renders if the state changes, which I don't need in this case.
The hook is used with v5.
import React from "react";
import { useHistory } from "react-router";
export function usePathname(): string {
let [state, setState] = React.useState<string>(window.location.pathname);
const history = useHistory();
React.useLayoutEffect(
() =>
history.listen((locationListener) => setState(locationListener.pathname)),
[history]
);
return state;
}
As mentioned above, useLocation can be used to perform side effects whenever the current location changes.
Here's a simple typescript implementation of my location change "listener" hook. Should help you get the result you're looking for
function useLocationEffect(callback: (location?: Location) => any) {
const location = useLocation();
useEffect(() => {
callback(location);
}, [location, callback]);
}
// usage:
useLocationEffect((location: Location) =>
console.log('changed to ' + location.pathname));
I am using now this code
import { BrowserHistory } from "history";
import React, { useContext } from "react";
import { UNSAFE_NavigationContext } from "react-router-dom";
export default function usePathname(): string {
let [state, setState] = React.useState<string>(window.location.pathname);
const navigation = useContext(UNSAFE_NavigationContext)
.navigator as BrowserHistory;
React.useLayoutEffect(() => {
if (navigation) {
navigation.listen((locationListener) =>
setState(locationListener.location.pathname)
);
}
}, [navigation]);
return state;
}
It seems to work fine
I find using useNavigate and useLocation quite meaningless compared to useHistory in React Rrouter v5.
As a result of these changes, I made a thin custom hook to ease myself from any refactoring.
Just rename the import path to this hook and use the "old" api with v6. To answer or just give hints to your question - using this approach is should be easy to implement the listen function in the custom hook yourself.
export function useHistory() {
const navigate = useNavigate();
const location = useLocation();
const listen = ...; // implement the hook yourself
return {
push: navigate,
go: navigate,
goBack: () => navigate(-1),
goForward: () => navigate(1),
listen,
location,
};
}
Why not simply use const { pathname } = useLocation();? It will indeed renders if the state changes but it shouldn't be a big deal in most scenarii.
If you REALLY want to avoid such behaviour, you could create a context of your own to hold the pathname:
// PathnameProvider.js
import React, { createContext, useContext } from 'react';
import { useLocation } from 'react-router';
const PathnameContext = createContext();
const PathnameProvider = ({ children }) => {
const { pathname } = useLocation();
return (
<PathnameContext.Provider value={pathname}>
{children}
</PathnameContext.Provider>
);
}
const usePathname = () => useContext(PathnameContext);
export { PathnameProvider as default, usePathname };
Then you can use usePathname() in any component down the tree. It will render only if the pathname actually changed.
Given that #kryštof-Řeháček's recommendation (just above) is to implement your own useListen hook, but it might not be obvious how to do that, here's a version I've implemented for myself as a guide (nb: I havent't exhaustively unit tested this yet):
import { useState } from "react";
import { useLocation } from "react-router";
interface HistoryProps {
index: number;
isHistoricRoute: boolean;
key: string;
previousKey: string | null;
}
export const useHistory = (): HistoryProps => {
const { key } = useLocation();
const [history, setHistory] = useState<string[]>([]);
const [currentKey, setCurrentKey] = useState<string | null>(null);
const [previousKey, setPreviousKey] = useState<string | null>(null);
const contemporaneousHistory = history.includes(key)
? history
: [...history, key];
const index = contemporaneousHistory.indexOf(key);
const isHistoricRoute = index + 1 < contemporaneousHistory.length;
const state = { index, isHistoricRoute, key, previousKey };
if (history !== contemporaneousHistory) setHistory(contemporaneousHistory);
if (key !== currentKey) {
setPreviousKey(currentKey);
setCurrentKey(key);
}
return state;
}
I now have just created a new routing library for react where this is possible.
https://github.com/fast-router/fast-router
Server-Side rendering is not supported. The rest should work fine. The library is mainly inspired by wouter -> https://github.com/molefrog/wouter
There are hooks for example usePathname which only cause a new render if the actual pathname changes (ignoring the hash and search)
It is possible to select just a single property of the history.state and don't get a new render if any other values inside the state changes.

useEffect socket.on problem rerendering useState

I'm looking for some help using socket.io-client. I'm having some issues trying to get all messages from the chat server. I'm supposed to get an individual message and I'll need to push to a state. I'm not getting rerender on each message. Is there any way to handle the state and update it? Thanks
import React, {useState, useEffect} from 'react'
import {withRouter} from 'react-router-dom'
import Button from '../core/Button'
import Field from '../core/Field'
import Label from '../core/Label'
import {Container, ScrollContainer} from './StyledComponents'
import useLocalStorageState from '../../hooks/useLocalStorage'
import io from 'socket.io-client'
import {SOCKET_SERVER_CLIENT} from '../../utils/const'
import Profile from '../Profile/Profile'
let socket
const Chat = ({location}) => {
const [listMessages, setListMessages] = useState([])
const [message, setMessage] = useState('')
useEffect(() => {
const username = location.search.split('=')[1]
socket = io(`${SOCKET_SERVER_CLIENT}/?username=${username}`)
return () => {
socket.disconnect()
}
}, [])
useEffect(() => {
socket.on('message', message => {
const auxArr = listMessages
auxArr.push(message)
setListMessages(auxArr)
})
})
return (
<Container>
{console.log('rerender')}
<ScrollContainer>
{listMessages &&
listMessages.map(infoMessage => (
<Profile key={infoMessage.time} {...infoMessage} />
))}
</ScrollContainer>
</Container>
)
}
export default withRouter(Chat)
const auxArr = listMessages
auxArr.push(message)
setListMessages(auxArr)
This code is mutating the old array, and then setting state with that same array. Function components do a check when setting state to make sure it changed. Since it's the same array before and after, the render gets skipped.
Instead of mutating the state, you will need to copy it:
const auxArr = [...listMessages];
auxArr.push(message);
setListMessages(auxArr);
Since you're basing the new state on the old one, you should also use the callback form of setting state, to eliminate any bugs that might happen if multiple set states happen at right about the same time:
setListMessage(prevMessages => {
const auxArr = [...prevMessages];
auxArr.push(message);
return auxArr;
})

Passing String value through Props returned from useSelector Hook

I am working on ReactJS modal dialog and bind the values from redux slice through the useSelector hook. Currently I have two functions which are already dispatching using useDispatch hook and setting the props with 2 functions(onCancelHandler, submitHandler). Here I need to keep one more field which is string value(userName) and tried to keep that and usig the string value approvedUser in DeleteUserModalContent through the props. Initially I am able to get the value from props in DeleteUserModalContent
component but when submitHandler is executed the following error is occured.
Can't read property 'userName' which is undefined
Error at this line:
const approvedUser: string = selectedUser.userName;
Can any one tell me what is wrong here?
Thanks in Advance
Code Snippet:
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Modal } from '#material-ui/core';
import { AppState } from 'store/rootReducer';
import { hideModal } from 'store/common/modalSlice';
import { submitAction } from 'store/user-actions';
import { DeleteUserModalContent } from './DeleteUserModalContent';
export const DeleteUserModal: React.FC<{}> = () => {
const dispatch = useDispatch();
const selectedUser = useSelector((state: AppState) => {
const selectedUserId =
state.selectUserSlice.selectedUsers[0];
return state.userState[selectedUserId];
});
const onCancelHandler = () => {
dispatch(hideModal());
};
const submitHandler = () => {
dispatch(
submitAction(selectedUser.userName)
);
};
const approvedUser: string = selectedUser.userName;
console.log(selectedUser.userName);
const props = {
onResetHandler,
submitHandler,
approvedUser
};
return (
<Modal>
<>
<DeleteUserModalContent {...props} />
</>
</Modal>
);
};
When we use the returned value from the useSelector hook and use the same in other component DeleteUserModalContent by setting into props. Here we are able to use the approvedUser value initially but when submitHandler function is dispatched selectedUser.userName value becomes undefined, So we can put the condition check below:
const approvedUser: string = selectedUser?.userName
to avoid the above mentioned error.

transitioning to use redux with hooks

figuring out how to use redux with hooks using this way but not sure its the correct way
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getPins } from "../../../actions/pins";
function MainStory() {
const pins = useSelector(state => state.pins.pins);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPins(pins));
}, []);
console.log(pins);
return(<div> test </div>
would the above way be the right way to go despite missing dependencies?
React Hook useEffect has missing dependencies: 'dispatch' and 'pins'. Either include them or remove the dependency array
with components (the way i had it before)
import { getPins } from "../../actions/pins";
import { connect } from "react-redux";
import PropTypes from "prop-types";
export class Pins extends Component {
static propTypes = {
pins: PropTypes.array.isRequired,
getPins: PropTypes.func.isRequired
};
componentDidMount() {
this.props.getPins();
}
render() {
console.log(this.props.pins);
return <div>test</div>;
}
}
const mapStateToProps = state => ({
pins: state.pins.pins
});
export default connect(mapStateToProps, { getPins })(Pins);
the plan is to list each pin
You can add dispatch to the dependencies list, since it won't change. If you'll add the pins to dependancies list of the useEffect block, you might cause infinite loop, if the response to the action changes the state (call to server that returns an array).
However, according to the class component example, the getPins() action creator doesn't require the value of pins, so you can just add dispatch to the list of dependancies:
function MainStory() {
const pins = useSelector(state => state.pins.pins);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPins());
}, [dispatch]);
console.log(pins);
return(<div> test </div>

Resources