Redux set state only if state is different - reactjs

I am using Redux for state management, but I encountered a problem. My issue is I like to set state only if state is different. Let me clarify my problem through my code.
// MyComponent.jsx
const [query, setQuery] = useState('');
useEffect(() => {
if(query.length) {
let {search, cancel} = searchContent(query);
search.then(res =>
setSearchResult(res.data)
).catch(e => {
if(axios.isCancel(e)){
return;
}
})
return () => cancel();
}else{
setSearchResult(null);
}
}, [query, setSearchResult])
Above is my component that is supposed to set search state.
// action.js
export const SET_SEARCH_RESULT = 'SET_SEARCH_RESULT';
export const setSearchResult = (val) => ({
type: SET_SEARCH_RESULT,
searchResult: val,
});
//reducer.js
import { SET_SEARCH_RESULT } from './article.action';
const INITIAL_STATE = {
searchResult: null
}
const articleReducer = (state=INITIAL_STATE, action) => {
switch (action.type) {
case SET_SEARCH_RESULT:
return {
...state,
searchResult: action.searchResult
}
default:
return state
}
}
I am able to set state using redux and it works fine. However, my problem is even though initial state is null, when useEffect function runs initially my state sets to null.
My question is how can I use redux so that only it runs if state is different.
Thanks in advance.

Related

How to update a component according to the state?

I need to display a list of objects in my Explorer component.
In my app I use useReducer hook what wrapped by Context.
It works well when the data flow is in "input-mode" (when I update data in state). But it does not rerender the application after data was changed.
So, steps that I need to pass and get a positive result.
Press btn with file icon (or folder icon). This btn call hook that take a look into state and make a decision: where this file or folder should be placed in my simple fs.
Write file/folder name (this function doesn't exist yet).
Apply name by press enter or click mouse out of input (the same as 2 step).
Currently, I try to create file/folder with hardcoded name for testing 1-step. And I expect that dispatch function pass data to the state and it would be updated and rerendered. All process runs well except of rerender.
I explain the flow of 1-st step.
After I click btn, I call the func from hook for forming my instance.
Then, the new instance saving into local useState.
After local useState successfully was updated, I call dispatch in useEffect hook.
In reducer I modify my state and return it.
After this steps I expect that my app will automatically rerendered, but it isn't.
Code snippets, step by step.
First step.
const handleFileClick = () => {
formAnInstance('file');
console.log('file btn click')
};
Second step.
// in useInstancesInteraction hook
const { state, dispatch } = useStateContext();
const [instance, setInstance] = useState<IInstance>();
const formAnInstance = (mode: Omit<Mode, 'root'>) => {
if (
typeof state?.currentFolder === 'undefined' ||
state?.currentFolder === null
) {
const target =
mode === 'folder'
? (createInstance('folder', 'folder') as IInstance)
: (createInstance('file', 'file') as IInstance);
target['..'] = '/';
setInstance(target);
}
};
Third step.
// in useInstancesInteraction hook
useEffect(() => {
const updateData = () => {
if (dispatch && instance) {
dispatch(createRootInstance(instance));
}
};
updateData();
}, [instance]);
Fourth step.
export const initialState = {
root: createInstance('/', 'root') as IInstance,
currentFolder: null,
};
const reducer = (state = initialState, action: IAction) => {
const { type, payload } = action;
switch (type) {
case ACTION_TYPES.CREATE_ROOT_INSTANCE:
const myKey = payload['.'];
Object.assign(state.root, { [myKey]: payload });
console.log('Reducer', state?.root);
return state;
case ACTION_TYPES.CREATE_FILE:
break;
case ACTION_TYPES.UPLOAD_FILE:
break;
case ACTION_TYPES.RENAME_FILE:
break;
case ACTION_TYPES.DELETE_FILE:
break;
case ACTION_TYPES.CREATE_FOLDER:
break;
case ACTION_TYPES.RENAME_FOLDER:
break;
case ACTION_TYPES.DELETE_FOLDER:
break;
default:
return state;
}
};
Here how my context file look like:
import React, { useContext, useReducer } from 'react';
import { IContext } from './index.types';
import reducer, { initialState } from './reducer';
const StateContext = React.createContext<IContext>({
state: undefined,
dispatch: null,
});
const StateProvider = ({
children,
}: {
children: JSX.Element | JSX.Element[];
}) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={{ state, dispatch }}>
{children}
</StateContext.Provider>
);
};
export default StateProvider;
export const useStateContext = () => useContext(StateContext);

React - update state with timeout in reducer

Can anyone help me to update state with timeout in react reducer.
I don't have much experience even with pure javascript, so I can hardly find an answer myself at this moment.
In my first ever react app (with useContex and useReducer) i have simple BUTTON checkbox with onClick function to dispatch type in reducer:
<ToggleButton
className="mb-2"
id="Getdocs"
type="checkbox"
variant="outline-secondary"
size="sm"
checked={Getdocs}
onChange={(e) => Getdocsaction()}
>
Render documents
</ToggleButton>
In my context.js i have:
import React, { useContext, useReducer} from 'react'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
.
.
.
Showdocs: false,
.
.
.
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
...
const Getdocsaction = () => {
dispatch({ type: 'GET_DOCS' })
}
...
return (
<AppContext.Provider
value={{
...state,
Getdocsaction
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
In reducer.js i have:
const reducer = (state, action) => {
if (action.type === 'GET_DOCS') {
let newPassports = state.oldDocs.filter((doc) => doc.passport === true);
if (newPassports.length === 0) {
state.Passports = []
state.Showdocs = true
state.Getdocs = false /uncheck checkbox button
setTimeout(() => {
state.Showdocs = false //wont update this
console.log("setTimeout fired") //logs in console after 5 secs
}, 5000)
return { ...state }
}
if (newPassports.length !== 0) {
return { ...state, Passports: newPassports, Showdocs: true, Getdocs: !state.Getdocs }
}
return { ...state }
}
throw new Error('no matching action type')
}
export default reducer
Finally, in my App.js i check if Showdocs is true or false and return the rest (return the passports from updated array or bootstrap alert if there is no values in array (Passport.length === 0) )
What i am trying to achieve is that when i have empty Passports array i want set Showdocs: true (in order to show alert msg) and set it back to false after 5 secs (in order to remove msg ...)
Any help is welcome and even keywords by which i could research this issue.
Thank you.
Reducers are intended to be “pure” and synchronous, and they shouldn't mutate input arguments. Since mutating state after a delay is a side-effect, you should consider instead handling this in a useEffect hook separately.
E.g.:
const SomeComponent = () => {
const [state, dispatch] = useReducer(reducer)
const { hideDocsAfterDelay } = state
useEffect(() => {
if (!hideDocsAfterDelay) return
const timer = setTimeout(() => {
dispatch({ TYPE: "HIDE_DOCS" })
}, 5000)
return () => { clearTimeout(timer) }
}, [hideDocsAfterDelay])
// …
}
In this scenario, you would set a hideDocsAfterDelay property in your state to trigger the timer and another action handler that would set showDocs and hideDocsAfterDelay to false.
I think you should implement an action that basically updates the state with this state.Showdocs = false and then dispatch this action inside a setTimeout.
So basically change Getdocsaction to this:
const Getdocsaction = () => {
dispatch({ type: 'GET_DOCS' })
setTimeout(() => {dispatch({type: 'The action that sets Showdocs to false'})}, 5000);
}

Redux-toolkit state is not updated

I am creating a login and in the .then function, setting the state of user.
The data is set correctly but after redirect (through code) to the next page, I could not access the new state using useSelector function.
Login.js
const handleSubmit = useCallback((e) => {
async function getUser(){
return await login(username, password);
}
e.preventDefault();
getUser()
.then((userObj) => {
if(userObj.user._id !== undefined){
dispatch(setAuthUser(userObj.user));
message.current.innerHTML = '';
window.location = `/Shops/Near/`;
history.push('/Shops/Near/');
}
else{
message.current.innerHTML = 'Invalid username or password, try again!';
}
});
}, [username, password]);
Shops.js
import { useSelector } from 'react-redux';
const Shops = () => {
const [shops, setShops] = useState([]);
const [isPreferedPage, setIsPreferedPage] = useState(false);
const message = useRef(null);
const user = useSelector((state) => {
console.log(state.user);
return state.user;
}); //bug
userSlice.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
};
export const user = createSlice({
name: 'user',
initialState,
reducers: {
setAuthUser: (state, action) => {
state = action.payload;
},
I want to know what is the problem, I tried to remove the async call and even set the state in empty call but still it never been updated.
Thanks
You cannot do state = as that would not modify the object in state, but throw the object in state away and put a new one into the variable.
state.foo = would be okay, state = is not.
Do return action.payload instead.
See also https://redux-toolkit.js.org/usage/immer-reducers#resetting-and-replacing-state

useEffect not triggering re-render when it's being used with useReducer

I can't trigger re-render even the state has changed. PageContainer calls loadPage and loadPage updates state without any inssue.
But when the state has changed it dose not trigger re-rendering.
It only shows inital state(pageName.loginPage). My expected result is pageName.profilePage since token is already stored on browser. I've also checked debugger state is clearly changed to pageName.profilePage
function PageContainer() {
const { state, loadPage } = useContainer({
reducer: loginStatusCheckReducer,
});
useEffect(() => {
loadPage();
}, [state]); // or [state.page]
return (
<>
{state.page}
</>
);
}
And here is useContainer
function useContainer({ reducer }) {
const [state, dispatch] = useReducer(reducer, { page: pageName.loginPage });
const loadPage = () => {
dispatch({ type: actionType.loadPage, dispatch });
};
return { state, loadPage };
}
This is the reducer function
function loginStatusCheckReducer(state, action) {
if (action.type === actionType.loadPage) {
const token = localStorage.getItem("token");
if (token) {
state.api = api(token);
state.page = pageName.profilePage;
return state;
}
}
return state;
}
initial state:
after loadPage
Looking at the code, I will guess that is not triggering a re-render because your useEffect is passing an empty array, so it will not react to any state changes.
Try adding the variable/state that will change once loaded on the useEffect
function PageContainer() {
const { state, loadPage } = useContainer({
reducer: loginStatusCheckReducer,
});
useEffect(() => {
loadPage();
}, [state]); // here the effect will listen to state changes
console.log('state.page', state.page);
return (
<>
<h1>{state.page}</h1>
</>
);}
Following code should fix the issue. #lon has pointed out, state should not be directly mutated.
function loginStatusCheckReducer(state, action) {
if (action.type === actionType.loadPage) {
const token = localStorage.getItem("token");
if (token) {
//state.api = api(token);
//state.page = pageName.profilePage;
//return state;
return {
...state,
api: api(token),
page: pageName.profilePage,
};
}
}
}

Get the state variable after dispatch is finished in react redux and saga?

Hello I am fairly new to React, Redux and Saga. So I have a scenario where I have a .jsx file which is the view file then an action file used for dispatch and I am also using saga which updates the data in the reducers. Following are the file structurs:
Action file:
export const getAction = (requestor) => ({
type: GET_ACTION,
data: {
requestor,
},
});
Reducer file
export const Reducer = (currentState = {}, action) => {
const newState = { ...currentState };
switch (action.type) {
case GET_ACTION:
newState.data = action.data;
return newState;
}
};
Saga file
function* getData(action) {
const { requestor } = action.data;
try {
const data = yield call(callService);
if(success) {
yield put( {type: GET_ACTION, data} );
}
} catch (e)
{
}
}
function* getDataSaga() {
yield takeLatest(GET_ACTION, getData);
}
export {
getData,
};
export default [
getDataSaga,
];
jsx file
const [dummy, setDummy] = useState([]);
const data = useSelector(state => state.data, shallowEqual) || {};
There is a function in which dispatch function is called.
dispatch(getAction(requestor));
Now I need to access the updated state of data after dispatch has finished updating the data because after the data is updated I have to setDummy to set the dummy variable mentioned. Any way which I can be approaching to achieve that. I have tried to use dispatch.then but on UI it is saying .then is not a function for dispatch.
after the data is updated I have to setDummy
useEffect lets you do something upon a given prop changing
const [dummy, setDummy] = useState([]);
const data = useSelector(state => state.data, shallowEqual) || {};
// setDummy when `data` changes
useEffect(() => {
setDummy(data);
}, [data])

Resources