why my modal open multiple times with redux? - javascript

I'm doing a modal with react-boostrap and redux-toolkit my problems is when i open my modal the background looks black like this
I'm doing a modal with react-bootstrap and redux-toolkit my problems is when i open my modal the background looks black like this
my code looks like
hook
const modalState = () => {
const ModalState = useSelector((state) => state.modal.isOpen);
return ModalState;
};
modalSlice
const modalSlice = createSlice({
name: 'modal',
initialState: {isOpen: false},
reducers: {
openModal: (state, action) => {
state.isOpen = true;
},
closeModal: (state, action) => {
state.isOpen = false;
}
}
});
store.js
export default configureStore({
reducer: {
errors: errors,
resourcesByType,
resources,
loading,
chips,
catalogue,
switchView,
session,
modal
}
});
Modal
const componentProps = ModalHook();
<DeleteModal show={componentProps}/>

Related

Is there a way to separate reducer actions in a different file from createSclice in redux?

I have this redux slice setup in store/slices/sourcesSlice.ts
import { addInitialSources} from "store/slices/sourcesActions.ts";
export const sourcesSlice = createSlice({
name: "sourcesSlice",
initialState,
reducers: {
addSource: (state, action: PayloadAction<SourceInterface>) => {
state.sources.push(action.payload);
},
changeName: (state) => {
state.naming = "Bob Marley";
},
},
extraReducers: (builder) => {
builder
.addCase(addInitialSources.fulfilled, (state, { payload }) => {
state.loading = false;
state.sources = payload;
})
},
});
export const { addSource, changeName } = sourcesSlice.actions;
Note how I have a number of actions in store/slices/sourcesSlice.ts and a number of actions in store/slices/sourcesActions.ts
All addSource, changeName, addInitialSources are related actions. **Shouldn't they all be accessible from one endpoint? So when I import any of those, I think it's cleaner, easier to remember if I can do this
import { addInitialSources, addSource, changeName} from "store/slices/sourcesActions.ts";
Rather than this
import { addInitialSources} from "store/slices/sourcesActions.ts";
import { addSource, changeName} from "store/slices/sourcesSlice.ts";
Is such a thing possible? To leave export const sourcesSlice = createSlice... in sourcesSlice.ts but to call all the actions from sourcesActions.ts
AND IS IT RECOMMENDED

How to use single action type in different reducer function with createSlice method in redux-toolkit

Is it possible that multiple reducer (which is created with createSlice method) must respond to the same action?
import { createSlice } from '#reduxjs/toolkit';
const isAuthenticated = createSlice({
name: 'isAuthenticated',
initialState: false,
reducers: {
loginSuccess(state, action) {
return true;
},
logout(state, action) {
return false;
},
},
});
export const { loginSuccess, logout } = isAuthenticated.actions;
export default isAuthenticated.reducer;
const currentUser = createSlice({
name: 'currenUser',
initialState: 'jhon',
reducers: {
loginSuccess(state, action) {
return 'steve';
},
logout() {
return state;
},
},
});
export const currentUserReducer = currentUser.reducer;
As You can see action.type loginSuccess is in two different reducers since i am only exporting loginSuccess of isAuthenticated i can use only that action to dispatch.
i know i can export loginSuccess from currentUser as well but i want to dispatch only one action and change the state in two different states.
I know this can be done with vanilla redux and also redux recommend using it Here
In short i am trying to replicate this but with createSlice method in redux-tool-kit.
Thanks in advance for helping.
You are looking for extraReducers:
const isAuthenticated = createSlice({
name: 'isAuthenticated',
initialState: false,
reducers: {
loginSuccess(state, action) {
return true;
},
logout(state, action) {
return false;
},
},
});
export const { loginSuccess, logout } = isAuthenticated.actions;
export default isAuthenticated.reducer;
const currentUser = createSlice({
name: 'currenUser',
initialState: 'jhon',
reducers: {
logout() {
return state;
},
},
extraReducers: builder => {
builder.addCase(isAuthenticated.actions.loginSuccess, () => {
return 'steve'
})
}
});

Redux Toolkit createSlice in React

I'm learning Redux-Saga and everything works well with { configureStore, getDefaultMiddleware, createAction, createReducer }. However, I cannot successfully implement createSlice.
My actions seem to be dispatched just fine (though I'm not sure since I have multiple Redux stores and placing console.log inside createSlice doesn't seem to work...). I just cannot get the store values - after dispatched action the relevant state value (initially '') becomes undefined. I did wrap my component inside Provider and all. Can someone enlighten me how does createSlice work? Thanks.
RESOLVED I had a bug somewhere else in my code, that's why the reducers weren't working proberly. BUT what I was asking about and what was causing my problems is this: actions passed to createSlice must be 'pure' functions, meaning: (state, action) -> state, nothing fancy. That's why I had to remove my fetching functions (getData1 and getData2) from this createSlice.
ComponentWrapper returns this
<Provider store={toolkitCreateSliceStore}>
<ReduxToolkitCreateSliceComponent />
</Provider>
Component (Buttons just dispatch actions)
class ReduxToolkitCreateSliceComponent extends React.Component {
render () {
return (
<>
<h2>
{this.props.data1}
{(this.props.data1!=='' && this.props.data2!=='') ? ', ' : ''}
{this.props.data2}
</h2><br/>
<h3>{this.props.message}</h3>
<Button1 />
<Button2 />
<Button3 />
</>
);
}
}
function mapStateToProps(state) {
return {
data1: state.toolkitCreateSliceReducer.data1,
data2: state.toolkitCreateSliceReducer.data2,
message: state.toolkitCreateSliceReducer.message
};
}
export default connect(mapStateToProps)(ReduxToolkitCreateSliceComponent);
Redux Toolkit slice
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialSliceState = {
data1: '',
data2: '',
message: ''
};
const slice = createSlice({
name: "slice",
initialState: initialSliceState,
reducers: {
getData1: (state, action) => {
return dispatch => {
dispatch(loading1());
return axios.get('http://localhost:8081/data1')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse1(response.data));
}
}).catch(error => dispatch(displayError1(error)));
};
},
getData2: (state, action) => {
return dispatch => {
dispatch(loading2());
return axios.get('http://localhost:8081/data2')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse2(response.data));
}
}).catch(error => dispatch(displayError2(error)));
};
},
setResponse1: (state, action) => {
state.data1 = action.payload;
state.message = 'success';
},
setResponse2: (state, action) => {
state.data2 = action.payload;
state.message = 'success';
},
reset: (state, action) => {
state.data1 = '';
state.data2 = '';
state.message = 'reset';
},
loading1: (state, action) => {
state.message = 'loading';
},
loading2: (state, action) => {
state.message = 'loading';
},
displayError1: (state, action) => {
state.message = action.payload;;
},
displayError2: (state, action) => {
state.message = action.payload;;
}
}
});
export const toolkitCreateSliceReducer = slice.reducer;
const { getData1, getData2, setResponse1, setResponse2, reset, loading1, loading2,
displayError1, displayError2} = slice.actions;
export default slice;
Redux Toolkit store
const middleware = [
...getDefaultMiddleware()
];
const toolkitCreateSliceStore = configureStore({
reducer: {
toolkitCreateSliceReducer
},
middleware
});
export default toolkitCreateSliceStore;
Your "reducers" are very wrong.
A reducer must never have any side effects like AJAX calls.
You've written some Redux "thunk" functions where your reducers should be:
getData1: (state, action) => {
return dispatch => {
dispatch(loading1());
return axios.get('http://localhost:8081/data1')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse1(response.data));
}
}).catch(error => dispatch(displayError1(error)));
};
},
This is a thunk, not a reducer.
A reducer would be something like:
getData(state, action) {
return action.payload;
}
I'd specifically recommend reading through our brand-new "Redux Essentials" core docs tutorial, which teaches beginners "how to use Redux, the right way", using our latest recommended tools and practices like Redux Toolkit. It specifically covers how reducers should work, how to write reducers with createSlice, and how to write and use thunks alongside createSlice:
https://redux.js.org/tutorials/essentials/part-1-overview-concepts
If you're interested in creating async actions, let me recommend you an npm package that I created and use. It is saga-toolkit that allows async functions to get resolved by sagas.
slice.js
import { createSlice } from '#reduxjs/toolkit'
import { createSagaAction } from 'saga-toolkit'
const name = 'example'
const initialState = {
result: null,
loading: false,
error: null,
}
export const fetchThings = createSagaAction(`${name}/fetchThings`)
const slice = createSlice({
name,
initialState,
extraReducers: {
[fetchThings.pending]: () => ({
loading: true,
}),
[fetchThings.fulfilled]: ({ payload }) => ({
result: payload,
loading: false,
}),
[fetchThings.rejected]: ({ error }) => ({
error,
loading: false,
}),
},
})
export default slice.reducer
sagas.js
import { call } from 'redux-saga/effects'
import { takeLatestAsync } from 'saga-toolkit'
import API from 'hyper-super-api'
import * as actions from './slice'
function* fetchThings() {
const result = yield call(() => API.get('/things'))
return result
}
export default [
takeLatestAsync(actions.fetchThings.type, fetchThings),
]

Reset state to initial with redux-toolkit

I need to reset current state to initial state. But
all my attempts were unsuccessful. How can I do it using redux-toolkit?
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState: {
returned: [],
},
reducers: {
reset(state) {
//here I need to reset state of current slice
},
},
});
Something like this:
const intialState = {
returned: []
}
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState,
reducers: {
reset: () => initialState
}
});
This worked for me (mid-late 2020). Formatted with your code context as an example.
const initialState = {
returned: [],
};
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState,
reducers: {
reset: () => initialState,
},
});
Replacing state with initialState directly did not work for me (mid 2020). What I finally got working was to copy each property over with Object.assign(). This worked:
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState: {
returned: []
},
reducers: {
reset(state) {
Object.assign(state, initialState)
}
}
});
When using multiple slices, all slices can be reverted to their initial state using extraReducers.
First, create an action that can be used by all slices:
export const revertAll = createAction('REVERT_ALL')
In every slice add an initialState, and an extraReducers reducer using the revertAll action:
const initialState = {};
export const someSlice = createSlice({
name: 'something',
initialState,
extraReducers: (builder) => builder.addCase(revertAll, () => initialState),
reducers: {}
});
The store can be created as usual:
export const store = configureStore({
reducer: {
someReducer: someSlice.reducer,
}
})
And in your react code you can call the revertAll action with the useDispatch hook:
export function SomeComponent() {
const dispatch = useDispatch();
return <span onClick={() => dispatch(revertAll())}>Reset</span>
}
In my case, as the previous answer, mid 2021, just setting the initial state DO NOT WORK, even if you use the toolkit adapter like :
reducers: {
// Other reducers
state = tasksAdapter.getInitialState({
status: 'idle',
error: null,
current: null
})
}
},
instead, you should use Object.assign(), guess that it's related with the internal immer library behavior
We do it like this guys.
Suppose you want to clear all the data at the point of logging out.
In your store.tsx file:
import { AnyAction, combineReducers, configureStore } from '#reduxjs/toolkit';
import authReducer from './slices/authSlice'
import messageReducer from './slices/messageSlice'
const appReducer = combineReducers({
auth: authReducer,
message: messageReducer,
});
const reducerProxy = (state: any, action: AnyAction) => {
if(action.type === 'logout/LOGOUT') {
return appReducer(undefined, action);
}
return appReducer(state, action);
}
export const store = configureStore({
reducer: reducerProxy,
});
Then you create a thunk like this:
export const logout = createAsyncThunk(
"auth/logout",
async function (_payload, thunkAPI) {
thunkAPI.dispatch({ type: 'logout/LOGOUT' });
console.log('logged out')
}
);
You can use spread opearator for initialState
const initialState: {
returned: unknown[] //set your type here
} = {
returned: []
}
const showOnReviewSlice = createSlice({
name: 'showOnReview',
initialState,
reducers: {
reset() {
return {
...initialState
}
}
}
});
Try this. In my case, I wanted to return all slices to initialState when a certain action is dispatched.
First, let's create action:
import { createAction } from '#reduxjs/toolkit';
export const resetPanelsAction = createAction('resetPanelsData');
When creating our store, we save a copy of the initialState in the middleware:
import { Middleware } from '#reduxjs/toolkit';
export const resetDataMiddleware: Middleware =
({ getState }) =>
(next) => {
// "Caching" our initial app state
const initialAppState = getState();
return (action) => {
// Let's add the condition that if the action is of
// type resetData, then add our cached state to its payload
if (action.type === 'resetData') {
const actionWithInitialAppState = {
...action,
payload: initialAppState,
};
return next(actionWithInitialAppState);
}
return next(action);
};
};
Almost done! Now let's change our root reducer a little by adding a wrapper that will check the action type, and if it is equal to resetData, then return combinedReducers with our initialState, which will be in payload.
import { AnyAction } from 'redux';
import { combineReducers } from '#reduxjs/toolkit';
export const combinedReducers = combineReducers({
/** Your reducers */
});
export const rootReducer = (
state: ReturnType<typeof combinedReducers> | undefined,
action: AnyAction,
) => {
if (action.type === 'resetPanelsData') {
return combinedReducers(action.payload, action);
}
return combinedReducers(state, action);
};

Failed prop type. Required prop types are undefined in my redux store

So I'm learning react and redux and I get this error in the console whenever I load my page. I'm not sure what it means since my store should be connected properly (as far as I know, but obviously it's not).
Warning: Failed prop type: The prop open is marked as required in >Navigation, but its value is undefined.
This is component (shortened to the key parts I think)
Navigation.propTypes = {
open: PropTypes.bool.isRequired,
};
const mapStateToProps = (state: any) => ({
open: state.open,
})
export default connect(mapStateToProps, { shiftContent })(Navigation);
My action
export const shiftContent = (open: boolean) => {
return {
type: ContentTypes.SHIFT_CONTENT,
payload: open
}
}
My Reducer:
const initialState = {
open: false,
};
export default function(state = initialState, action: IAction) {
switch(action.type) {
case ContentTypes.SHIFT_CONTENT:
console.log("shifting Open = " + action.payload);
return {
...state,
open: action.payload
};
default:
return state;
}
}
My combined reducer:
import ContentReducer from './ContentReducer';
const rootReducer = combineReducers({
content: ContentReducer
});
And where I'm initializing my store:
import rootReducer from './Reducers';
const store = createStore(rootReducer);
I tried setting up an initial state for the store like:
const initialStore = {
open: false
}
const store = createStore(rootReducer, initialStore);
But that gave me an error as well.
const mapStateToProps = (state: any) => ({
open: state.open,
})
in this function, state is the root state. Ie, the one produced by the rootReducer. This state looks like:
{
content: {
open: // some boolean
}
}
So to access it, you need to do:
const mapStateToProps = (state: any) => ({
open: state.content.open,
})
PS, since you're using typescript, you should be able to do better than any for the type. At the very least, you could do this:
// in the file with the root reducer:
const rootReducer = combineReducers({
content: ContentReducer
});
export type RootState = ReturnType<typeof rootReducer>;
// And then use it elsewhere like:
const mapStateToProps = (state: RootState) => ({
open: state.content.open,
})
mapStateToProps gets your root reducer state - so use state.content.open instead of state.open.

Categories