Problems with React Component Reusability with Redux Store as Global State - redux

I am coming across a reusability conundrum in my React/Redux app. I have coded reusable React components, such as dropdowns and sliders, and I am storing the state values in my Redux Store. It is dawning on me now that anytime any JSX instance of the component changes state, all other instances of that component also change state. They all begin to mimic each other, whereas I need them to operate independently. I also want to initialize state differently in the different instances of the component, and can't figure out a way to do this. For example, for a slider, I want the beginning and ending value of the slider range to be different in different instances of the component. This would be easy to do through React's normal props system, but I am getting confused on how to do it via Redux.
I've researched this a bit and it seems the solution is to add unique IDs to each instance of the component in the Redux store. But I need specifics on where to add the ID (the reducer? the action creator? the component?) and how to have everything flow so that the right component is getting their state changed.
This is my reducer:
const INITIAL_STATE = {
slots: 100,
step: 5,
start: 35,
end: 55,
};
const sliderReducer = (state=INITIAL_STATE, action ) => {
switch (action.type) {
case "MIN_SELECTED":
console.log("Min Selected");
return {
...state,
start: action.payload
};
case "MAX_SELECTED":
console.log("Max Selected");
return {
...state,
end: action.payload
};
default:
return state;
}
}
export default sliderReducer;
this is my action creator:
//Slider Minimum Value Selection Action Creator
export const selectMinValue = (value) => {
return (dispatch) => {
dispatch({
type: "MIN_SELECTED",
payload: value
});
};
};
//Slider Maximum Value Selection Action Creator
export const selectMaxValue = (value) => {
return (dispatch) => {
dispatch({
type: "MAX_SELECTED",
payload: value
});
};
};
the component code is too long to paste, but it's event handlers for onDrag events and JSX for the slider essentially.

For anyone here who runs into the same problem, there are many libraries which attempt to solve this, in my opinion, in too complex a way. The cleanest and simplest way to resolve the issue is to associate a namespace identifier with each instance of the component in the redux store. The best answer I found is here: having multiple instance of same reusable redux react components on the same page/route
Good luck!

Related

Caching into subcomponents

I'm building a site in nextjs but I came across a problem.
I have the cover of the site, where there is a list of products, and on the top menu the list of product categories.
The products are looking via getStaticProps (So that it is done by the servideor and is cached).
However, the categories are inside a separate component, where inside I need to load the category listing from my API.
getStaticProps does not work in this case as it is not a page but a component.
Fetching inside a useEffect is bad, as each access loads the api.
So the question remains, how can I do this server-side fetch and deliver the cached (json) return? (Simulating getStaticProps)
As your component is listed on every page, you could consider either using Context or local caching in the browser within the shared Category component.
Context provides a way to pass data through the component tree without
having to pass props down manually at every level.
But there are performance considerations using Context and may be overkill here. If you really don't want to hit the API, data is not changing often, and you don't need to pass functions through the component tree, then you could consider some basic browser caching using localStorage.
import React, { useState, useEffect } from 'react';
const mockAPI = () => {
return new Promise((resolve) => {
setTimeout(() => {
return resolve([
{
id: '1',
name: 'One'
},
{
id: '2',
name: 'Two'
}
]);
}, 1000);
});
};
const Component = () => {
const [categories, setCategories] = useState(null);
useEffect(() => {
(async () => {
if (categories === null) {
let data = window.localStorage.getItem('categories');
if (data === null) {
console.info('calling api...');
data = await mockAPI();
window.localStorage.setItem('categories', JSON.stringify(data));
}
setCategories(JSON.parse(data));
}
})();
}, []);
return <nav>{categories && categories.map((category) => <li key={category.id}>{category.name}</li>)}</nav>;
};
export default Component;
The caveat here is you need to know where to clear localStorage. There are many ways to implement this from using basic timers to looking at SWR
You could also consider Redux but is probably overkill for something elementary like this.

Dispatch actions with Redux Toolkit requires two arguments

Using Redux Toolkit, I'm trying to dispatch an action without context of event or etc., so I get the following error:
error TS2554: Expected 2 arguments, but got 1. An argument for 'action' was not provided.
With following code:
const App = () => {
const dispatch = useDispatch();
useEffect(() => {
(async () => {
const result = await fetchConfig();
dispatch(setConfig({ ConfigReducerState: result })); // ---> Error is here <---
})();
}, [dispatch]);
};
The reducer:
export const configSlice = createSlice({
name: 'config',
initialState,
reducers: {
setConfig(state, action) {
const { server, map } = action.payload;
state.server = server;
state.map = map;
},
},
});
Usually I give one parameter to action creator functions - object representing the payload, no need to refer the state. But here I can't. What am I doing wrong?
I've seen this before and every time, it was... a bug in IntelliJ/WebStorm.
See https://youtrack.jetbrains.com/issue/WEB-46527 and https://youtrack.jetbrains.com/issue/WEB-42559 - essentially, WebStorm has their own "quick TypeScript interpolation that does not use the official tsserver for type checking, but something self-cooked, that guesses types just based on things having similar names - and regularly gets things wrong.
If I understand their system correctly, you should be able to see the correct types by hovering over while holding Ctrl down.
In the end, I can't really tell you how to fix this other than switching to an IDE that does not randomly guess, but actually uses TypeScript to evaluate TypeScript types.
How did you import setConfig? I had the same issue and it turned out that by mistake I used
import setConfig from './configSlice'
instead of
import { setConfig } from './configSlice'
It was importing the default export (whole slice reducer, aliased as setConfig) instead of just this one function...

useReducer and state managing

hey guys im learning the useReducer hook and for the most part it seems to be quite similar to redux (minus the action being sent to the store etc)
the thing i seem to ALWAYS have problems with when i get more complex state management situations is trying to alter my state in the ways i would like to. in my code i am essentially trying to have a user select a track and add it to a list of favorite songs. my code seems to be replacing the state and not adding to it
here is my initial state and my useReducer and then lastly my add function (which when a button is pressed down below it sends in a track to be added to the list
const initialState = {
name:'',
duration:''
};
const [favorites, dispatch]=useReducer(reducer, initialState)
const add=(song)=>{
dispatch({type:'ADD', payload:song})
console.log(favorites)
}
THIS is the part that is confusing me. in my reducer i have this
export const reducer=(song, action)=>{
switch(action.type){
case 'ADD':
return {...song, name:action.payload}
}
WHICH is essentially adding a new object everytime called name: trackname BUT i do not want to overwrite the last item. i feel like i am using spread wrong and also returning the incorrect payload maybe?
my final state keeps looking like this
{name: "deep end", duration: ""}
when i want it to look something like this
``
[{name: "deep end", duration: ""}
{name: "ok", duration: ""}
{name: "testtrack", duration: ""}
]`
i have tried setting the initial state to somethingm like this
const initialState = {
favorites:{
[ADD TRACKS HERE]
}
};
BUT CANT seem to overwrite the state correctly so that it ADDS to the array. it keeps overwritting the last one
Redux's guide to Immutable Update Patterns is a great resource on how to update nested data in a way that doesn't mutate your state.
With an array there are two main ways to immutably add an element.
With spread syntax:
const newArray = [...songs, newSong];
With .concat(), which returns a new array that contains the additional items (that is different from .push() which mutates the array and just returns the length).
const newArray = songs.concat(newSong);
You can decide what you want the shape of your state to be. Storing the array to a property favorites is fine, but adds another layer of complexity to your updates.
export const reducer = (state, action) => {
switch (action.type) {
case "ADD":
return {
...state,
favorites: [...state.favorites, action.payload]
};
default:
return state;
}
};
const initialState = {
favorites: [] // initial state has an empty array
};
const [state, dispatch] = useReducer(reducer, initialState);
// here favorites is just a property of state, not the whole state
const favorites = state.favorites;
I would recommend that that state should just be the array of favorites itself.
export const reducer = (favorites, action) => {
switch (action.type) {
case "ADD":
return [...favorites, action.payload]
default:
return favorites;
}
};
// favorites is the whole state
// initial state is an empty array
const [favorites, dispatch] = useReducer(reducer, []);
In either case, we are expecting the action.payload to be a complete song object, not just the name.
dispatch({ type: "ADD", payload: { name: "Deep End", duration: "3:22" } });
You could extract that into a helper function. In Redux terms we call this function an Action Creator.
const addFavorite = (name, duration) => ({
type: "ADD",
payload: { name, duration }
});
dispatch(addFavorite("Deep End", "3:22"));

Redux - I don't understand "Task-Based Updates" example

In link: Task-Based Updates
I don't understand below code:
import posts from "./postsReducer"; // missing this code??
import comments from "./commentsReducer"; // missing this code??
and why should do that?
const combinedReducer = combineReducers({
posts,
comments
});
const rootReducer = reduceReducers(
combinedReducer,
featureReducers
);
only featureReducers is okie? not need combindedReducer? anh what is postsReducer code, commentsReducer code?
Thanks for helps!
Unfortunately that example is a little confusing (one of the few places in the normally solid Redux docs). If you go here and check out the 'third approach', you'll see this concept explained a little better.
Essentially, postReducer and commentReducer are there to handle actions that modify only posts or only comments--that is, things that do not require changes to multiple tables (e.g posts AND comments). featureReducer is the task-based reducer that handles actions that require updates to multiple tables.
A very simplified example:
const postReducer = (state = {}, action) => {
return state
}
const commentReducer = (state = {}, action) => {
return state
}
const combined = combineReducers({
posts: postReducer,
comments: commentReducer
})
const createComment = (state, action) => {
switch(action.type) {
case 'CREATE_COMMENT':
// update multiple tables (linked example actually does an ok job ok demonstrating this)
return updatedState
default:
return state;
}
}
const rootReducer = reduceReducers( combined, createComment )
In this example, the first two reducers just create the state shape. The third, which depends on the first two to set the state up for it, updates multiple tables across the redux store.
Your state shape will look like this:
{
posts: {},
comments: {}
}
If you're confused about reduceReducers, just try to think of it like combineReducers, only it doesn't affect state shape.

Simplest Redux-React app in ES5: why aren't props being passed down?

I'm trying to build the most trivial possible Redux app. I have an initial state, I make a Redux store, I pass the store to ReactRedux.Provider, and I have my app as a child of the Provider.
However, my APP view, written as a stateless functional component, is not receiving any props. (The same is true if I write my APP view using React.createClass and checking for this.props in the render method.)
What am I doing wrong?
var initialState = {
counter: 0
};
var rootReducer = function(state, action) {
if (!state) state = initialState;
switch (action.type) {
default: // don't do anything yet
return state;
}
};
var store = Redux.createStore(rootReducer, initialState);
var APP = function(props) {
return React.createElement(
'div',
{},
props.counter // props is not defined
);
};
ReactDOM.render(
React.createElement(
ReactRedux.Provider,
{store: store},
React.createElement(APP, null)
),
document.getElementById('app')
);
You need to use the connect() function provided by React-Redux to create a wrapped version of your "APP" component that is actually hooked up to the store. See http://redux.js.org/docs/basics/UsageWithReact.html .
You can write the equivalent logic yourself for subscribing to the store and passing updated props to a component, but generally there's not a good reason to do so.
For future reference, I am going to add here an example of a working case in codepen not using babel neither the integrated version of jsx.
https://codepen.io/kanekotic/pen/LxbJNJ
Solution ;TL;DR
As commented before there is missing the redux connect
var mapstateToProps = function(state) {
return{
counter: state.counter
}
}
function mapDispatchToProps(dispatch){
return Redux.bindActionCreators(ActionCreators, dispatch);
}
var connectedApp = ReactRedux.connect(mapstateToProps,mapDispatchToProps)(APP)
and use then in the component connectedApp and no APP

Resources