I am new to react-redux, and trying to store all of my states inside of redux store. I have 2 components (having local states and handler functions inside them) and a redux-slice. I want to store all of my functions inside of the redux store to increase the efficiency of my app. Even though, i have tried storing one function inside the redux as mentioned into the code below.I need your advice on whether it is correct method, or is there any another work around?
Here's my component - Dice.tsx
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faDiceFive, faDiceFour, faDiceOne, faDiceSix, faDiceThree, faDiceTwo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { MenuBook, More, Search } from "@mui/icons-material";
import { AppBar, Box, Fab, IconButton, styled, Toolbar, Typography } from "@mui/material";
import { Fragment, useEffect, useState } from "react";
import { setIconType } from "../Redux/dashboardSlice";
import { useAppDispatch, useAppSelector } from "../Redux/store";
export default function Dice() {
const [count, setCount] = useState<number>();
const { iconType } = useAppSelector(state => state.dashboard);
const dispatch = useAppDispatch();
//const [iconType, setIconType] = useState<IconProp>(faDiceSix);
//const [playsound, setPlaySound] = useState();
const [addclass, setAddClass] = useState(false);
function randomNumberGenerate(min: any, max: any) {
return Math.floor(Math.random() * (max - min 1)) min;
}
function playDiceRollSound() {
const soundURL = '/sounds/diceRoll.mp3';
var audio = new Audio(soundURL);
return audio;
}
const handleDiceRoll = () => {
setTimeout(() => {
playDiceRollSound();
setCount(randomNumberGenerate(1, 6));
}, 100);
}
useEffect(()=> {
setAddClass(true);
}, [count]);
useEffect(()=> {
switch(count) {
case 1 : dispatch(setIconType(faDiceOne));
break;
case 2 : dispatch(setIconType(faDiceTwo));
break;
case 3 : dispatch(setIconType(faDiceThree));
break;
case 4 : dispatch(setIconType(faDiceFour));
break;
case 5 : dispatch(setIconType(faDiceFive));
break;
case 6 : dispatch(setIconType(faDiceSix));
break;
default : dispatch(setIconType(faDiceSix));
}
}, [count]);
//fab icon
const StyledFab = styled(Fab)({
position: 'absolute',
zIndex: 1,
top: -30,
left: 0,
right: 0,
margin: '0 auto',
width : '69px',
height : '69px',
border: '8px solid #fff',
boxShadow: 'none',
});
return (
<Fragment>
<AppBar position="fixed" color="secondary" sx={{ top: 'auto', bottom: 0 }}>
<Toolbar>
<IconButton
color="inherit"
aria-label="show list"
>
<MenuBook />
</IconButton>
<StyledFab
color="primary"
aria-label="roll dice"
onClick={handleDiceRoll}
type="button"
className="rotateFab"
>
<FontAwesomeIcon className={addclass ? "diceIcon" : ""} icon={iconType} style={{fontSize : '33px'}} color="inherit" />
</StyledFab>
<Box sx={{ flexGrow: 1 }} />
<Typography variant ="h6" color="inherit">{count! > 0 ? count : "roll die"}</Typography>
<IconButton color="inherit">
<Search />
</IconButton>
<IconButton color="inherit">
<More />
</IconButton>
</Toolbar>
</AppBar>
</Fragment>
)
}
and here's my redux slice
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faDiceSix } from "@fortawesome/free-solid-svg-icons";
import { createSlice } from "@reduxjs/toolkit";
export interface DashboardState {
count : number | undefined,
iconType : IconProp,
addClass : boolean,
};
const initialState : DashboardState = {
count : undefined,
iconType : faDiceSix,
addClass : false,
};
/*export function randomNumberGenerate(min: any, max: any) {
return Math.floor(Math.random() * (max - min 1)) min;
};*/
export const dashboardSlice = createSlice({
name : 'dashboard',
initialState,
reducers : {
setIconType : (state, action ) => {
state.iconType = action.payload;
},
}
});
export const { setIconType } = dashboardSlice.actions;
Below is my redux store configuration -
import { configureStore } from '@reduxjs/toolkit'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { dashboardSlice } from './dashboardSlice';
export const store = configureStore({
reducer: {
dashboard : dashboardSlice.reducer,
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppSelector : TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch=() => useDispatch<AppDispatch>();
please suggest some better solution/workAround...
I want to move the function randomNumberGenerate(min, max)
{
//code here
}
inside of the redux store, i have already tried storing {iconType} variable and its action(setIconType) inside the redux store, and then dipatched inside useEffect(()=> {}, []), based on the conditions(as specified by switch-case), is this method correct? Or is there any another work around!... how could i store the other functions (such as randomNumberGenerate(), handleDiceRoll()), inside of the redux store?
I just want to make my Dice component less boiler-platy ... and thus want to store all the functions and states inside of redux store (dashboardSlice.ts) only. please suggest better solutions.... Thank you & warm Regards!
CodePudding user response:
Regarding your setIconType
method, i'd say it looks good. If you want to store more reducer functions, you can just add them with the same pattern to reducer as you added the first function, like here. You can also use combinereducers
.
But you need to be careful with two other functions. They both are impure, the timeout function is async and math.random
is not predictable in resulting. If you want to move these functions into the reducer, you need to handle with asyncThunk
docs. As you can see in the docs its not that super easy, so maybe for the beginning you can leave impure functions and only implement those that are normal/pure functions.
One more thing for the correctness, I think redux-toolkit
(differently from the the original redux
) accepts impure function generally, because it contains the immer
package, but for async functions you would need the asyncThunk (math.random function would be ok to implement in the reducer).
CodePudding user response:
I think you are conflating Redux with a React context. Redux is a state management tool. In other words, Redux stores a global state object and exposes out the state and a dispatch function that is used to effect state updates. Redux is not a utility function container.
If I'm understanding the bedrock of your question though it seems you are really wanting to reduce the logic from the UI component. For this you could abstract the state and asynchronous logic into the state slice and actions.
Example:
import {
faDiceFive,
faDiceFour,
faDiceOne,
faDiceSix,
faDiceThree,
faDiceTwo
} from "@fortawesome/free-solid-svg-icons";
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
function randomNumberGenerate(min: number, max: number) {
return Math.floor(Math.random() * (max - min 1)) min;
};
const randomDieFace = () => randomNumberGenerate(1, 6);
function playDiceRollSound() {
const soundURL = '/sounds/diceRoll.mp3';
var audio = new Audio(soundURL);
return audio;
};
const rollDice = createAsyncThunk(
"dashboard/rollDice",
(_, { dispatch }) => {
setTimeout(() => {
playDiceRollSound();
dispatch(
dashboardSlice.actions.rollComplete(randomDieFace())
);
});
},
);
const face = {
1: faDiceOne,
2: faDiceTwo,
3: faDiceThree,
4: faDiceFour,
5: faDiceFive,
6: faDiceSix,
};
export interface DashboardState {
count: number,
iconType: IconProp,
addClass: boolean,
};
const initialState: DashboardState = {
count: 6,
iconType: face[6],
addClass: false,
};
export const dashboardSlice = createSlice({
name : 'dashboard',
initialState,
reducers : {
rollComplete: (state, action) => {
state.count = action.payload;
state.iconType = face[action.payload];
state.addClass = true;
},
...
}
});
export const { rollComplete, ..... } = dashboardSlice.actions;
export rollDice;
...
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { MenuBook, More, Search } from "@mui/icons-material";
import { AppBar, Box, Fab, IconButton, styled, Toolbar, Typography } from "@mui/material";
import { Fragment, useEffect, useState } from "react";
import { rollDice } from "../Redux/dashboardSlice";
import { useAppDispatch, useAppSelector } from "../Redux/store";
//fab icon
const StyledFab = styled(Fab)({
position: 'absolute',
zIndex: 1,
top: -30,
left: 0,
right: 0,
margin: '0 auto',
width : '69px',
height : '69px',
border: '8px solid #fff',
boxShadow: 'none',
});
export default function Dice() {
const { addClass, count, iconType } = useAppSelector(state => state.dashboard);
const dispatch = useAppDispatch();
const handleDiceRoll = () => {
dispatch(rollDice());
};
return (
<Fragment>
<AppBar ....>
<Toolbar>
<IconButton color="inherit" aria-label="show list">
<MenuBook />
</IconButton>
<StyledFab
color="primary"
aria-label="roll dice"
onClick={handleDiceRoll}
type="button"
className="rotateFab"
>
<FontAwesomeIcon
className={addClass ? "diceIcon" : ""}
icon={iconType}
style={{ fontSize: '33px' }}
color="inherit"
/>
</StyledFab>
<Box sx={{ flexGrow: 1 }} />
<Typography variant ="h6" color="inherit">
{count || "roll die"}
</Typography>
<IconButton color="inherit">
<Search />
</IconButton>
<IconButton color="inherit">
<More />
</IconButton>
</Toolbar>
</AppBar>
</Fragment>
)
}