According to the Redux documentation, it seems to be a standard practice to set an initialState on your reducer. However this initialState needs to be maintained and if the state is being populated based on an API response, then you may have the initial state out of sync with the API response. This is especially true in cases where the state is made up of nested objects.
Is it to avoid null-checking (sometimes the initial state is set to null), are there any performance benefits? Does it improve code readability?
Taken from Redux docs:
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
Then in our reducer we may have an action which replaces the value in the state (for example based on an API response). Such as:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos
})
default:
return state
}
}
However the same behaviour may be achieved without using an initialState, by checking the state in the component (or selector).
const MyComponent = ({todos}) => {
if (!todos) { // if we do not have an initialState, todos will be undefined if SET_TODOS hasn't been called
return null;
}
return <div>{todos.map(n => ...)}</div>
}
If the API returns a new property (notes), we would need to update as follows:
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: [],
notes: [] // <-----
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos,
})
case SET_NOTES:
return Object.assign({}, state, {
notes: action.notes,
})
default:
return state
}
}
This is further complicated in cases when you have nested objects. If the todos has a child property subtasks: [], why are we not setting an initial state for it?
I think it's more of a convenience than anything else. I highly doubt there is any performance implications. Also while in my personal opinion it is cleaner to add all the properties you expect upfront, you are not really required to add it to the initial state. You can simply add it when returning the new state, obviously if you're using typescript this is a different story.
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
// notes: [] you don't necessarily need to add it to the initial state
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos,
notes: action.notes // <-----
})
default:
return state
}
}
Having an initial state helps to have "cleaner" code. Like Brian Thompson said in the comments, code is more predictable if your data structure stays consistent.
It is also recommended (by some ppl) to avoid having multiples returns in one function. It may not be the best perf-wise, but it might be the easiest to read.
So having to do an early return in your component might not be the cleanest way to achieve the behavior your want.
That being said, if your implementation works, why not use it ? Well i think it's best when working as a team to stick to the conventions as much as possible.
Related
I am working on an anime project. My problem is that when I click the input, I want to bring it to the forefront. So, I used "webkitRequestFullscreen()", but I cannot toggle my boolean to true in order to make it. I looked for some solutions, but I guess, the version I use for Redux is different from theirs and I didn't understand them.
Here is my initial state and reducer function:
const initialState = {
items: [],
fullScreen: false,
}
reducers: {
toggleInputScreen: (state, action) => {
const id = action.payload
state.fullScreen = !state.fullScreen
},
},
The main component:
let activeFullScreen = useSelector((state) => state.anime.fullScreen)
// console.log(activeFullScreen)
if (activeFullScreen) {
return inputRef.current.webkitRequestFullscreen()
}
<div
className="input-container"
onClick={() => dispatch(toggleInputScreen())}
id="input-div">
<input type="search" placeholder="Search..." ref={inputRef} />
</div>
I know the reducer function is a total disaster. How can I toggle the fullScreen boolean when I click the input? Thanks in advance.
The problem is probably the reducer function.
In Redux, the state is immutable, which means you never modify the state, instead, create a new copy. Another mistake is, that you have to return the new state in the reducer function.
Reducers are functions that take the current state and an action as arguments, and return a new state result. [...] They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values. - redux guide
You can solve your problem by following the just appointed rules:
toggleInputScreen: (state, action) => {
const id = action.payload
//create and return a new object, which is the new state
return {
items: state.items,
fullScreen: !state.fullScreen,
}
}
If the state is an object and grows in size, you can use the Object.assign() method to create a copy of the object and change values of it easily. Check it out here and here. For redux you would use the following schema: Object.assign({}, state, changes). In you case, that would look like this:
toggleInputScreen: (state, action) => {
const id = action.payload
//create and return a new object, which is the new state
return Object.assign({}, state, { fullscreen: !state.fullscreen });
}
I have a larger reducer, and I want to add another slice to it that will be fairly complicated unto itself. For example, here's a large, already complex reducer:
const initialState = {
bigchunk1: { ...someObject },
bigchunk2: true,
bigchunk3: [...somevalues],
bigchunk4: 'etc'
}
function mainReducer(state = initialState, action){
switch(action.type){
case Actions.CASE1:
return {
...state,
bigchunk1: {
...state.bigchunk1;
somevalue: action.payload.this_is_already_enough_nesting
}
}
case Actions.CASE2:
return {
...state,
bigchunk4: 'this goes on for awhile'
}
// lots more cases
default:
return state
}
}
I want to add a new slice to state, so the whole initialState would end up in the store looking like this:
const initialState = {
bigchunk1: { ...someObject },
bigchunk2: true,
bigchunk3: [...somevalues],
bigchunk4: 'etc',
newslice: { ...someComplicatedThing }
}
But rather than have to write all my new cases into my original reducer (along with a very messy level of nesting and spread operators), I want to write a new reducer that handles just the cases pertinent to newslice:
const initialState = { ...someComplicatedThing }
function newSliceReducer(state = initialState, action){
switch(action.type}{
// cases here
}
}
I am aware of combineReducers, but I'm having a hard time thinking of how to apply that in this scenario. combineReducers can take these two reducers and place them as siblings, but how can I combine these so that newSliceReducer and its associated state becomes a child of mainReducer under the name newslice? I feel like this should be simple, but its escaping me right now. I have been reading the redux docs, but I'm not seeing the answer clearly. I do not want to use redux-toolkit or any outside libraries.
In the default switch case, you can do
default:
let newSlice = newSliceReducer(state.newslice, action)
if (newSlice !== state.newslice) {
return {...state,newslice:newSlice}
}
return state
You can think of Redux as a coding pattern instead of library.
A reducer's job is to update the state and return new object if changed else return old object. If a new object is returned, react assumes something changed and a refresh is triggered.
Now, what you have is a "newSliceReducer" whose state is just a part of main reducer. So, I followed above rules and called the new reducer with state as smaller part of the original state and checked the result if it changed. If yes, I created a new object with updated state else return old state.
It is important to check if newSlice actually changed.
I'm using React and Redux in my web app.
In the login page, I have multiple fields (inputs).
The login page in composed from multiple components to pass the props to.
I was wondering how should I pass the props and update actions.
For example, lets assume I have 5 inputs in my login page.
LoginPage (container) -> AuthenticationForm (Component) -> SignupForm (Component)
In the LoginPage I map the state and dispatch to props,
and I see 2 options here:
mapStateToProps = (state) => ({
input1: state.input1,
...
input5: state.input5
})
mapDispatchToProps = (dispatch) => ({
changeInput1: (ev) => dispatch(updateInput1(ev.target.value))
...
changeInput5: (ev) => dispatch(updateInput5(ev.target.value))
})
In this solution, I need to pass a lot of props down the path (the dispatch actions and the state data).
Another way to do it is like this:
mapStateToProps = (state) => ({
values: {input1: state.input1, ..., input5: state.input5}
})
mapDispatchToProps = (dispatch) => ({
update: (name) => (ev) => dispatch(update(name, ev.target.value))
})
In this solution, I have to keep track and send the input name I want to update.
How should I engage this problem?
It seems like fundamental question, since a lot of forms have to handle it,
but I couldn't decide yet what would suit me now and for the long run.
What are the best practices?
I think best practice would be to handle all of this logic in the React component itself. You can use component's state to store input's data and use class methods to handle it. There is good explanation in React docs https://reactjs.org/docs/forms.html
You probably should pass data in Redux on submit. Ether storing whole state of the form as an object, or not store at all and just dispatching action with api call.
TL;DR. it's a more 'general' coding practice. But let's put it under a react-redux context.
Say if you go with your first approach, then you will probably have 5 actionCreators as:
function updateInput1({value}) { return {type: 'UPDATE_INPUT1', payload: {value}} }
...
function updateInput5({value}) { return {type: 'UPDATE_INPUT5', payload: {value}} }
Also if you have actionTypes, then:
const UPDATE_INPUT1 = 'UPDATE_INPUT1'
...
const UPDATE_INPUT5 = 'UPDATE_INPUT5'
The reducer will probably look like:
function handleInputUpdate(state = {}, {type, payload: {value}}) {
switch (type) {
case UPDATE_INPUT1: return {..., input1: value}
...
case UPDATE_INPUT5: return {..., input5: value}
default: return state
}
}
What's the problem? I don't think you're spreading too many props in mapStateToProps/mapDispatchToProps, Don't repeat yourself!
So naturally, you want a more generic function to avoid that:
const UPDATE_INPUT = 'UPDATE_INPUT'
function updateInput({name, value}) { return {type: UPDATE_INPUT, payload: {name, value}} }
function handleInputUpdate(state = {inputs: null}, {type, payload: {name, value}}) {
switch (type) {
case UPDATE_INPUT: return {inputs: {...state.inputs, [name]: value}}
default: return state
}
}
Finally, the "selector" part, based upon how the state was designed, get component's props from it would be fairly trivial:
function mapStateToProps(state) { return {inputs: state.inputs} }
function mapDispatchToProps(dispatch) { return {update(name, value) { dispatch(updateInput(name, value)) } }
In summary, it's not necessarily a redux/react problem, it's more how you design app state, redux just offers you utilities and poses some constraints to enable "time traveling" (state transitions are made explicit within a mutation handler based on a separate action).
Best practice to handle this problem is having a local state on your Form Component and managing it locally because I believe it's not a shared state. onSubmit you could dispatch your action passing down the state to the action which is required in making an API call or posting it to your server.
If you try to keep updating your store as the user types, it will keep dispatching the action which might cause problems in future. You read more here Handling multiple form inputs in react
I want to display quick flash animations on certain events (eg. a red border flash for each incorrect keystroke).
To do this with css animations, I need to remove and add the animation class each time I want to trigger the flash. (Unless there's another way to retrigger an animation?).
There are a few suggestions for doing this on this github thread: https://github.com/facebook/react/issues/7142
However, in my case the state that triggers the flash is the redux state. And in many cases the state hasn't actually changed, so it doesn't cause a rerender.
Here's the best solution I've got, which involves setting a random number to force a re-render. Is there a better way to do this?
reducer.js
//Reducer function to update redux state
function setError(state, action) {
state.hasError = true;
state.random = Math.random();
return state;
}
export default function allReducers(state = initialState, action) {
switch (action.type) {
case ActionTypes.SUBMIT_VALUE_BUTTON:
return Object.assign({}, state, setError(state, action));
default:
return state;
}
}
react component and container
const mapStateToProps = (state, ownProps) => {
return {
random: state.random,
hasError: state.hasError,
}
}
componentWillReceiveProps() {
this.setState({hasError: this.props.hasError});
setTimeout(() => {
this.setState({hasError: false});
}, 300)
}
render() {
return <div className = {`my-component ${this.state.hasError ? 'has-error':''}`} />;
}
Edit: It's worth noting that the redux documentation says that you shouldn't call non-pure functions like Math.random in a reducer method.
Things you should never do inside a reducer:
Call non-pure functions, e.g. Date.now() or Math.random().
Your code has a few problems in it, I'll go one by one...
You can't mutate the state object on the reducer. Here it is from the redux docs:
Note that:
We don't mutate the state. We create a copy with Object.assign().
Object.assign(state, { visibilityFilter: action.filter }) is also
wrong: it will mutate the first argument. You must supply an empty
object as the first parameter. You can also enable the object spread
operator proposal to write { ...state, ...newState } instead.
In your code setError receives the state as a prop and mutates it. setError should look like this:
function setError(state, action) {
let newState = Object.assign({}, state);
newState.hasError = true;
newState.random = Math.random();
return newState;
}
The second problem might be because there's some code missing but I cant see when your'e changing your state back to no errors so the props doesnt really change.
In your componentWillReceiveProps your referencing this.props instead of nextProps.
componentWillReceiveProps should look like this:
componentWillReceiveProps(nextProps) {
if (nextProps.hasError !== this.props.hasError && nextProps.hasError){
setTimeout(() => {
// Dispatch redux action to clear errors
}, 300)
}
}
And in your component you should check for props and not state as getting props should cause rerender (unless the render is stopped in componentShouldUpdate):
render() {
return <div className={`my-component ${this.props.hasError ? 'has-error':''}`} />;
}
In the react redux doc todo example, Dan passes the action with type TOGGLE_TODO to the todos which then passes it on to each individual todo. I notice that his logic was checking for the todo.id was in the todo reducer. Couldn't this logic have been done in the todos as well? To me, it would seem better to take care of the logic at a higher level as your iterating through each todo rather than passing the work to every todo and having them figure out if they need to toggle or now. Is there a reason why Dan did it this way?
const todo = (state = {}, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
}
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state
}
return Object.assign({}, state, {
completed: !state.completed
})
default:
return state
}
}
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
]
case 'TOGGLE_TODO':
return state.map(t =>
todo(t, action)
)
default:
return state
}
}
export default todos
I think you are right, if you take a look at todomvc example from redux source code repository, you'll see only one todos reducer.
The docs may be a bit outdated or such nested reducers may be just an example of its possibilities.
It's just one possible approach to structuring the reducer logic. In this case, it looks like Dan chose to define a function to handle the case of updating a single todo, and was then able to reuse that function for multiple cases by using it within higher-level iteration logic.