Initialize state before mapStateToProps? - javascript

I know my question comes from a misunderstanding of react-redux but I will describe my use case hoping someone will point me in the right direction.
I'm trying to store selected row keys (selectedRowKeys) from a table (ant design table) inside of a redux store. Everything works when the store structure is simple like this:
selectedRowKeys: []
But I want to store that state in a normalized form to handle multiple tables and multiple table properities:
tables: {
123fdfas-234dasf-234asdf-23rfa : { //table id
id: 123fdfas-234dasf-234asdf-23rfa,
selectedRowKesy: []
//... other properities
}
}
The problem is that this state doesn't exist when redux is trying to mapStateToProps like this:
const mapStateToProps = (state, ownProps) => {
if (!ownProps.id) {
ownProps.id = uuidv4();
}
return {
selectedRowKeys: state.tables[ownProps.id].selectRowKeys
};
};
state.tables[ownProps.id] is undefined so there is an error.
I thought that I need to initialize the state somehow but this led me to even more confusion. What I have figured out so far:
I can't initialize state in reducer like reducerName (state = initialState, action) because action is not dispatched and there is no action.id (action object has a payload with table id).
I can't dispatch an action INIT_TABLE in componentDidMount() because mapStateToProps executes first so state.tables[ownProps.id] is still undefined.
I feel like this use case is wierd and that is way I cannot find the solution although I have been googling and thinking about this for 3 days.
Please guide me, I'm in a crazy loop :)
This is my first SO question, pls let me know if something is unclear.

Who is responsible for creating a new table? That's the deciding factor on how to solve this problem.
The id definitely shouldnt be created in mapStateToProps and you shouldn't mutate ownProps. I'm surprised if that even works. Id should be created in action if using redux.
If your React-app has some mechanism which creates a new table (for example, user clicks button), then that's where you should dispatch initialization action. If you really can't find parent component which would be responsible for table initialization, then maybe it is responsibility of this component and you should dispatch the action in componentDidMount.
Regardless of which option you pick, your mapStateToProps should handle empty state gracefully ie. selectedRowKeys should be set to some default value if it's missing (empty array or null maybe?). And your component should handle missing values if there is no sane default available. It's common to have some null checks in render-function which return null until data is available.

From my understanding to the problem, you are being too specific!
In this case, it's not a good practice.
I advice the follow.
Supposing that you have the reducer tables:
const mapStateToProps = ({ tables }, ownProps) => {
return {
tables,
};
};
This will maket the reducers tables available at your component's this.props
Thus, you can do the follow wherever you want in the component to get your selected rows, define this method and use it in a proper place instead of directly doing it in mapStateToProps, which is not a good practice.
getSelectedRowKeys = (ownProps) => {
if (!ownProps.id) {
ownProps.id = uuidv4();
}
const selectedRowKeys = [];
const table = this.props.tables[ownProps.id];
const selectedRowKeys = table && table.selectRowKeys; //if table exists get the row keys.
return selectedRowKeys || []; //if selectedRowKeys are not there return an empty array.
}

Related

react/redux state and components useState become out of sync on fast clicking

Edit:formatting of code order:
okay. So I'm trying to track down why this is happening...
the situation is:
I have on component that lists a bunch of divs that are meant to light up green if they are selected... the collection of selected divs is stored in a LogicSlice.producitonMachineSelected[].
and yet if I click to fast - things get... weird.
the component in question is:
function MachineCard(props) {
const data = props.props;
const [checked, setChecked] = useState(false);
const [machineCardColor, setMachineCardColor] = useState("#7070ff");
const dispatch = useDispatch();
useEffect(()=>{
console.log("checkedChanged",checked);
checked? setMachineCardColor("#70ff70"):setMachineCardColor("#7070ff");
},[checked])
return(
<div className="MachineCard"
style={{backgroundColor:machineCardColor}}onClick={()=>{
setChecked(!checked);
dispatch(setProductionMachinesSelected(data.machineID))
}
}>
</div>
}
the reducer inside logic slice, that was made using CreateSlice from redux toolkit... which allows for immutable update... below is also the immutable code written out by hand... but it results in the same issue... so I'm reasonable sure this is not the source of the bug.
setProductionMachinesSelected:(state,action)=>{
const machineID = action.payload;
const found = state.productionMachinesSelected.find(x=>x===machineID);
if(found){
state.productionMachinesSelected = state.productionMachinesSelected.filter(x=>x.machineID===machineID)
}else{
state.productionMachinesSelected.push(machineID);
}
return state;
}
the problem with this code - is the setChecked happens for each dispatch - yet becomes out of sync if clicked too fast.
I have tried putting the dispatch in the useEffect of the machine card
//this does not work.
useEffect(()=>{
console.log("checkedChanged",checked);
checked? setMachineCardColor("#70ff70"):setMachineCardColor("#7070ff");
dispatch(setProductionMachinesSelected(data.machineID))
},[checked])
I've tried changing the order... but not only does it become out of sync... if clicking to fast... it seems to wipe the producitonMachinesSelected array entirely.
the redux toolkit inspector that manages state also is reflecting these wild changes...
really, I just want to be able to make sure the color of the div matches the state it is in in the redux store... why is this happening?
as for wild changes I mean.
the redux state change list
state1: [1]
state2:[1,4,5,6,7,8]
state3:[]
state4:[2,3,4]
state5:[3,4]
state6:[]
// the useState of the components are also doing their own thing independent of this.
which I believe should not happen - if each action is actually executed in the order I clicked them, then regardless of how fast I click - shouldn't they be only one state change at a time from the above code? - even if it takes longer to display the cascade of changes?
EDIT: per suggestion tried the following in the reducer:
to no avail: (it's in a create slice from redux toolkit)
const machineID = action.payload;
const found = state.productionMachinesSelected.find(x=>x===machineID);
const newState = found?
{...state,productionMachinesSelected:state.productionMachinesSelected.filter(x=>x.machineID===machineID)}
:
{...state,productionMachinesSelected:[...state.productionMachinesSelected, machineID]};
return newState;
so... what's happenning?
UPDATE:
revised the pattern to instead of use local state, to read the state from the redux store... this results in the staying in sync with the state... however///
it is still true that if you click fast enough... the state becomes damaged.
double clicking on a div erases the whole array and leaves just that one div in the array... this is the weird array skipping behavior... this happens regardless of if I write the reducer immutably or not.
okay... I'm a dumb dumb...
as it turns out... it's not fast clicking at all. it's any clicking to remove an item...
the problem stemmed from two things...
1:i was using two states when i really only needed the one...
2: there was an error in my code in the reducer... i had an array of numbers... but the filter was as if it was an machine object... not a list of MachineId's... thus:
const machineID = action.payload;
const found = state.productionMachinesSelected.find(x=>x===machineID);
if(found){
state.productionMachinesSelected = state.productionMachinesSelected.filter(x=>x!==found)//here was the error... before it was x.machineID===machineID... the x is a number and not an object
}else{
state.productionMachinesSelected.push(machineID);
}
state.productionMachinesSelected.sort((a,b)=>a-b);
return state;
now works flawlessly.

How to reduce the number of times useEffect is called?

Google's lighthouse tool gave my app an appalling performance score so I've been doing some investigating. I have a component called Home
inside Home I have useEffect (only one) that looks like this
useEffect(() => {
console.log('rendering in here?') // called 14 times...what?!
console.log(user.data, 'uvv') // called 13 times...again, What the heck?
}, [user.data])
I know that you put the second argument of , [] to make sure useEffect is only called once the data changes but this is the main part I don't get. when I console log user.data the first 4 console logs are empty arrays. the next 9 are arrays of length 9. so in my head, it should only have called it twice? once for [] and once for [].length(9) so what on earth is going on?
I seriously need to reduce it as it must be killing my performance. let me know if there's anything else I can do to dramatically reduce these calls
this is how I get user.data
const Home = ({ ui, user }) => { // I pass it in here as a prop
const mapState = ({ user }) => ({
user,
})
and then my component is connected so I just pass it in here
To overcome this scenario, React Hooks also provides functionality called useMemo.
You can use useMemo instead useEffect because useMemo cache the instance it renders and whenever it hit for render, it first check into cache to whether any related instance has been available for given deps.. If so, then rather than run entire function it will simply return it from cache.
This is not an answer but there is too much code to fit in a comment. First you can log all actions that change user.data by replacing original root reducer temporarlily:
let lastData = {};
const logRootReducer = (state, action) => {
const newState = rootReducer(state, action);
if (newState.user.data !== lastData) {
console.log(
'action changed data:',
action,
newState.user.data,
lastData
);
lastData = newState.user.data;
}
return newState;
};
Another thing causing user.data to keep changing is when you do something like this in the reducer:
if (action.type === SOME_TYPE) {
return {
...state,
user: {
...state.user,
//here data is set to a new array every time
data: [],
},
};
}
Instead you can do something like this:
const EMPTY_DATA = [];
//... other code
data: EMPTY_DATA,
Your selector is getting user out of state and creating a new object that would cause the component to re render but the dependency of the effect is user.data so the effect will only run if data actually changed.
Redux devtools also show differences in the wrong way, if you mutate something in state the devtools will show them as changes but React won't see them as changes. When you assign a new object to something data:[] then redux won't show them as changes but React will see it as a change.

Is it possible to use references as react component's prop or state?

I want to create react table component which values are derived from single array object. Is it possible to control the component from view side? My goal is that every user using this component in their web browsers share the same data via singleton view object.
Program modeling is like below.
Database - there are single database in server which contain extinct and independent values.
DataView - there are singleton View class which reflects Database's table and additional dependent data like (sum, average)
Table - I'll build react component which looks like table. And it will show View's data with supporting sorting, filtering, editing and deleting row(s) feature (and more). Also it dose not have actual data, only have reference of data from View(Via shallow copy -- This is my question, is it possible?)
My intentions are,
- When user changes value from table, it is queried to DB by View, and if succeed, View will refer updated data and change it's value to new value and notify to Table to redraw it's contents. -- I mean redraw, not updating value and redraw.
- When values in View are changed with DB interaction by user request, there are no need to update component's value cause the components actually dose not have values, only have references to values (Like C's pointer). So only View should do is just say to Component to redraw it's contents.
I heard that React's component prop should be immutable. (Otherwise, state is mutable) My goal is storing references to component's real value to it's props so that there are no additional operation for reflecting View's data into Table.
It is concept problems, and I wonder if it is possible. Since javascript dose not support pointer officially(Am I right?), I'm not sure if it is possible.
View class is like below,
const db_pool = require('instantiated-singleton-db-pool-interface')
class DataView {
constructor() {
this.sessions = ['user1', 'user2'] // Managing current user who see the table
this.data = [ // This is View's data
{id:1, name:'James', phone:'12345678', bank:2000, cash:300, total:2300,..},
{id:2, name:'Michael', phone:'56785678', bank:2500, cash:250, total:2300,..},
{id:3, name:'Tyson', phone:'23455432', bank:2000, cash:50, total:2300,..}
] // Note that 'total' is not in db, it is calculated --`dependent data`
}
notifySessionToUpdate(ids) {
// ids : list of data id need to be updated
this.sessions.forEach((session) => {
session.onNotifiedUpdateRow(ids) // Call each sessions's
})
}
requestUpdateRow(row, changed_value) {
// I didn't write async, exception related code in this function for simple to see.
update_result = db_pool.update('UPDATE myTable set bank=2500 where id=1')
if (update_result === 'fail') return; // Do Nothing
select_result = db_pool.select('SELECT * from myTable where id=1') // Retrieve updated single data which object scheme is identical with this.data's data
for (k in Object.keys(select_result)) {.ASSIGN_TO_row_IF_VALUE_ARE_DIFFERENT.} // I'm not sure if it is possible in shallow copy way either.
calc.reCalculateRow(row) // Return nothing just recalculate dependant value in this.data which is updated right above.
// Notify to session
this.notifySessionToUpdate([1]) // Each component will update table if user are singing id=1's data if not seeing, it will not. [1] means id:1 data.
return // Success
}
... // other View features
}
Regarding session part, I'm checking how to implement sessionizing(?) the each user and it's component who is communicating with server. So I cannot provide further codes about that. Sorry. I'm considering implementing another shallow copied UserView between React Component Table and DataView(And also I think it helps to do something with user contents infos like sorting preference and etc...)
Regarding DB code, it is class which nest it's pool and query interface.
My problem is that I'm not familiar with javascript. So I'm not sure shallow copy is actually implementable in all cases which I confront with.
I need to think about,
1. Dose javascript fully support shallowcopy in consistent way? I mean like pointer, guarantee check value is reference or not.
2. Dose react's component can be used like this way? Whether using props or state Can this be fullfilled?
Actually, I strongly it is not possible to do that. But I want to check your opinions. Seems it is so C language-like way of thinking.
Redraw mean re-render. You can expose setState() or dispatch() functions from Table component and call them on View level using refs:
function View() {
const ref = useRef();
const onDbResponse = data => ref.current.update(data);
return (
<Table ref={ ref } />
);
}
const Table = React.forwardRef((props, ref) => {
const [ data, setData ] = useState([]);
useImperativeHandler(ref, {
update: setData
});
...
});
Anyway i don't think it's a good practice to update like that. Why can't you just put your data in some global context and use there?
const Context = React.createContext({ value: null, query: () => {} });
const Provider = ({ children }) => {
const [ value, setValue ] = useState();
const query = useCallback(async (request) => {
setValue(await DB.request(request));
}, [ DB ]);
const context = { value, query };
return <Context.Provider value={ context }>{ children }</Context.Provider>;
}
const useDB = () => useContext(Context);
const View = () => {
const { request } = useDB();
request(...);
}
const Table = () => {
const { value } = useDB();
...
}

Updating redux state does not trigger re-render (nor does it update the state)

I'm pretty new to Redux and i'm encountering some problems with it.
I'm creating a list of items in a component, sending it to redux state and then i want to read from that redux state and display the items in a different list component.
The creation part works as i can console.log and getState() without problems (i am seeing the changes in Redux State).
My problem is that my component state does not change, nor does it re-render.
And now some code ->
this.state = {
initialItems: this.props.SharepointItems,
}
And at the end
const mapStateToProps = (state) => {
return {
SharepointItems: state.listItems,
}
}
export default connect(mapStateToProps)(SharePointList);
I even tried something like this in my componentDidMount() ->
store.subscribe(() => {
this.setState({ initialItems: this.props.SharepointItems });
console.log("updating state");
});
From what i've read i shouldnt need to update the state manually while using redux, or am i wrong?
EDIT: Since my list doesnt throw an error if i console.log i can see that the array is empty (which is what i defined in the Redux state). Is there something that i should be doing to get the new state ? Seems like its getting the immutable state or something like that (the empty array).
Edit2: Found the problem (or part of it). It appears as my state is 1 event behind. So redux contains the array with 4 items, the component state is empty. If i do a dispatch from the browser i get 5 items (as expected) in redux, but 4 in state (it finally shows not empty).
Also my code was a bit bugged (i was passing the entire array instead of items in the array).
I've changed it to
result.map((item) => {
store.dispatch(addListItem(item));
});
And it started rendering. The problem is it displays items from 0 to 2 (4 in array), but the last one is left behind. Once again if i do another dispatch from the browser i get item 3 rendered, but 4 (the last one added) is only in redux state and does not update the list state.
Also...is it a good idea to do it like this? My list might have 1000 items in the future and i'm not sure that dispatch is a good solution (i need to make an API call to get the items first, which is why i'm using dispatch to populate redux).
Updated with reducer ->
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_LISTITEM:
return { ...state, listItems: [...state.listItems, action.payload] };
case ADD_SPOTOKEN:
return { ...state, spoToken: action.payload };
default:
return state;
}
};
export default rootReducer;
Found something else thats a bit weird. I am also using React Router. As i said my list displays only 3 of the 4 items in my Redux State array. If i navigate to a different page and then back to the list page it actually renders all 4 of the items.
I believe your problem stems from the fact that you have "forked" SharepointItems off of props and set it to the component's local this.state. Unless you really need to, I'd recommend not doing that. Just use this.props. Dan Abramov (author of Redux) recommends this as a general principle too.
By forking props onto state, you create two sources of truth regarding the state of SharepointItems, namely, this.state.initialItems and this.props.SharepointItems. It then becomes your responsibility to keep this.state and this.props in sync by implementing componentDidUpdate (that is why you're not seeing it update). You can avoid all the extra work by just using the data that flows in from props.
The connect function will re-render your component with new props whenever you update the redux store. So in your render method, just refer to this.props.SharepointItems and not this.state.initialItems. Then you should be good to go, that is, assuming you've implemented your reducer(s) and store configuration properly.
I actually fixed this by mistake. I was planning on leaving it for the end and find a workaround for it, but i somehow fixed it.
I was importing the list component (which had the redux state props) in a Page (react-router). The page wasn't connected since it wasn't ready yet. Apparently after connecting the page (parent component which holds the list) everything works fine and i can see all 4 of my items (instead of seeing 3 without having the parent connected).
I wonder if this is intended...

Calling state changing functions from mapStateToProps

I am new to react-redux and I was surprised to see an example where a function, in this case being getVisiblieTodos, is called inside mapStateToProps. This function should be called in a reducer since it changes state? Is the code breaking "good form" for the sake of brevity? Is it okay to do this in general?
I am looking at code from this link
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
In redux we want the store to hold the minimal data needed for the app. Everything that is derived from the base data, should be computed on the fly, to prevent cloning pieces of the store, and the need to recompute all derived data when something changes in the store.
Since the visible todos list is not part of the store, but computed using the list of todos, and the visibilityFilter, the getVisibleTodos() doesn't change the store's state. It produces the derived computed data from the those two properties.
A function that is used to get data from the store, and compute derived data is known as a selector. Using selectors, the derived data is not part of the store, and computed when needed. In addition, we can use memoized selectors, to save the computation overhead.
You may see getVisibleTodos as a reducer because it includes "switch .. case" block or/and because it has 2 arguments . However, it is not a rule.
A redux reducer ( by definition) changes store state according to dispatched action , and that's why it takes two arguments ( store state + dispatched action ) and it returns new state for the store without mutation.
getVisibleTodos here is a helper function which filter an array according to string (filter).
Also , filter is not a redux-action, it is just string that decides todos to be rendered.
I may agree with you it is something weird , and if we can see the whole application (reducers, actions,... ) we can decide if it is best practices or not .
todos in this component is a calculated property based on the state of the reducer, and it is not changing any state.
It's okay to transform properties comming from recuders that are used only by one component (they are called selectors). Imagine that you use todos in other components, you will not want to make changes in one component like filtering and seeing that in the other components. If this is the case, it's fine to do it.
Also, it is a good property of your reducer to store only the needed data. More state is more complexity in the app, and more overhead to calculate new states.
It seems to me that a function should do what its name says, nothing less, nothing more.
mapStateToProps() should just do that, ie "map", and should normally not call other functions.

Categories