Redux: Reducer not mutating state in first attempt - reactjs

I am working on a Table in React based application using typescript. I am implementing search functionality for the table. There is a huge amount of data that can be displayed inside table so I am performing search, sorting, pagination all at back end.
I have a component for the table which receives data as props from a parent component. I am using react with redux and using sagas, I get the data from back end. I am using redux state to provide data to component. I am using reducers to mutate the state.
The problem I am facing is that when I reload the data, I get the data and using reducer I mutate the state but that is not being displayed at frontend. But when I try second time, it displays the data.
My code for reducer is below.
const dataEnteries: Reducer<any> = (
state = {},
{ type, detail, pagination }: DataResponse
) => {
switch (type) {
case actionTypes.DATA_LOAD_RESPONSE:
if (!detail) {
return {};
}
const data: any[] = [];
const tableData: any[] = [];
detail.forEach((o) => {
tableData.push(o.dataDetail);
Data.push(o)
})
const resultMap = new Map()
resultMap["data"] = data;
resultMap["tableData"] = tableData;
resultMap["pagination"] = pagination;
return resultMap;
default:
return state;
}
};
here is my map state to props function
const mapStateToProps = ({ data }: { data: DataState }): DataProps => ({
data: data.dataEnteries
});
dataEnteries is the innermost property
I am unable to figure out what is going wrong in my case as at second time, things works rightly.

You could try to use spread operator on return.
return ...resultMap
When you update a prop inside an object the state didn't recognise the change then the render will not be called to refresh the component.
Try this, if not work let me know.
For further referente check this : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Related

Where to calculate groups for fluentui detaillist in nextjs

I am learning react, and given this simple example of using SWR to fetch some items from an API and showing the items with groups using fluentui DetailedList - I am running into a problem with the groups.
Whenever I click a group in UI to collapse/uncollapse, that seems to trigger a rerender, and then the component will createGroups(data) again which resets the UI again back to original state as the groups object is recalculated.
Where am I supposed to actually store / calculate the groups information of my data? Initial it needs to be created, but from there it seems that it should only needs to be reevaluated whenvere the swr api returns new data - and then i still properly would want to merge in the current state from collapsed groups that the user might have changed in the UI.
Is it because i properly should not use SWR as it refreshes data live - and only do it on page refresh?
const SWR = ({ children, listid, onSuccess }: { children: ((args: SWRResponse<any, any>) => any), listid: string, onSuccess?: any }) => {
const url = `http://localhost:7071/api/Lists/${listid}`;
console.log(url);
const {data,error } = useSWR(url, { fetcher: fetcher, isPaused: () => listid === undefined, onSuccess });
const items = data.value;
const groups = createGroups(data)
return <... DetailsList group={groups} items={items} ... >; // ... left out a few details ...
};
What about adding a state for holding the groups and an useEffect for when data changes and insde the useEffect you should check if the content has changed before updating the groupState.
const hasChanged(data) => {
return data.notEquals(state.data)); // write your own logic for comparing the result
};
useEffect(() => { if (hasChanged(data)) {
setState(prev=> ({ ...prev, group: createGroup(data), data: data });
}}, [data]);
You dont actually need to store the group, you can just hold the data in your state, but the important part is to be able to check if any change actually took place before changing the state.
Another thing worth trying is the compare option in the useSWR hook. So instead of placing the "hasChanged" logic inside an useEffect hook, perhaps it could be in the compare function. Haven't had the chanse to test this myself though.
A third and final option would be to place the creation of groups inside your fetcher. Perhaps the most intuitive solution for this particular case, though I'm not completely sure it will prevent the unnecessary re-renders.
const fetcher = url => axios.get(url).then(res=> {
return {
items: res.data.value,
groups: createGroups(res.data),
};
});
const SWR = ({ children, listid, onSuccess }: { children: ((args: SWRResponse<any, any>) => any), listid: string, onSuccess?: any }) => {
const { data, error } = useSWR(url, fetcher, ...);
return <... DetailsList group={data.groups} items={data.items} ... >; // ... left out a few details ...
};

Redux useSelector with id field

I need your advice on filtering data with a selector. Suppose I have the following entities in my application. 1 organization has multiple devices which look the following in my state shape:
state {
devices: {
byId: [ 1 { name: device1 } ]
}
organizations: {
byId: [
1 { name: org1, devices: [1,2,3] }
]
}
}
Now I want to filter the devices inside the organization. This is something that I want to do with a selector. My selector looks like the following:
const selectDevice = (id: any) => (state: State) => state.devices.byId[id]
export const selectOrganizationDevices = (id: number) => (state: State) => {
const organization = state.organizations.byId[id] || []
return organization.devices.map(id => selectDevice(id)(state))
}
This should be working fine but my selector got called before I have dispatched the data from redux. I suppose that there's something wrong with my reducer or the component I've created.
My reducer looks like this:
return produce(state, draftState => {
switch (action.type) {
...
case OrganizationActionTypes.FETCH_DEVICES_SUCCESS: {
draftState.byId = action.payload.entities.organizations
draftState.allIds = Object.keys(action.payload.entities.organizations)
draftState.loading = false
break;
}
...
default: {
return state
}
}
})
My functional component looks like the following:
function Devices() {
const dispatch = useDispatch();
const devices = useSelector(selectOrganizationDevices(1))
useEffect(() => {
dispatch(fetchOrganizationDevices(1))
}, [])
const columns = []
return (
<Layout headerName={"Devices"}>
<Table dataSource={devices} columns={columns}/>
</Layout>
)
}
The error I get now is organization.devices is undefined which says that the array of devices in the state is empty. It seems that useSelector is called before dispatch. How can I prevent redux of doing this? Or what should be changed in my code?
Yes, the useEffect hook runs after the first render. useSelector will run during the first render. So, your component code needs to safely handle the case where that data doesn't exist yet.
Also, don't put a hardcoded array/object literal in a selector like that, as it will be a new reference every time and force your component to re-render every time an action is dispatched. Either extract that to a constant outside of the selector, or use a memoization library like Reselect to create the selector.
Finally, you should be using our official Redux Toolkit package, which includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once. It also has a new createEntityAdapter API that helps you manage normalized state in the store.

How component is getting data from redux store?

Following along the example async app from the Redux documentation, and I really don't understand how the component AsyncApp has access to the posts that are fetched async.
function mapStateToProps(state) {
const { selectedSubreddit, postsBySubreddit } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsBySubreddit[selectedSubreddit] || {
isFetching: true,
items: []
}
 
return {
selectedSubreddit,
posts,
isFetching,
lastUpdated
}
}
Questions:
1- what is this expression: postsBySubreddit[selectedSubreddit]? Accessing a function with brackets? Does this set up the component with the props posts from const { selectedSubreddit, posts, isFetching, lastUpdated } = this.props
2- They are able to send <Posts posts={posts} /> but where are they getting those posts from in the first place!? Why are they a part of the props!?
What is this expression: postsBySubreddit[selectedSubreddit]?
Accessing a function with brackets?
No, postsBySubreddit is not a function, its an object basically, and [] (bracket notation) is for accessing the object values by some dynamic key.
Check this snippet:
let obj = {a:1, b:2};
let k = 'b'; //k will have the property name
console.log('value of b = ', obj[k]);
As per MDN Doc:
Property accessors provide access to an object's properties by using
the dot notation or the bracket notation.
You are getting confused with Destucturing and Short-Circuit Evaluation, See what exactly is happening there:
const selectedSubreddit = state.selectedSubreddit;
const postsBySubreddit = state.postsBySubreddit;
const data = postsBySubreddit[selectedSubreddit] || { isFetching: true, items: []}
const isFetching = data.isFetching;
const lastUpdated = data.lastUpdated;
const lastUpdated = data.lastUpdated;
Where are they getting those posts from in the first place!?
Those values are getting passed from redux store. mapStateToProps will get the store as first argument and from there we can access any data. We use Provider that will provide the store data to all the connected component.
As per DOC:
Provider makes the Redux store available to the connect() calls in the
component hierarchy. Normally, you can’t use connect() without
wrapping a parent or ancestor component in <Provider>

React-Redux connect: Use mapStatetoProps to inject only component part of store

[React-Redux] Issue.
I'd like to have reusable encapsulated components to be used in any app, or in any level of the app's store.
When it comes to use 'mapStatetoProps' then making the component container (injecting the state into the component as props), you always receive the whole store. This might be a pain if you want to reuse components dynamically or in other projects.
The thing is if you use the same store entry but you want to use the same component as encapsulated module they will be sharing the same data.
And also, when you are encapsulating components and you reuse them and they are deep nested in the store, you will end up needing to know where they are.
A possible ugly solution would be to implement a script going through the state inside the mapStateToProps till it finds the key matching certain name. The issue here would be to make sure the state field you want to use is unique.
I'll be more than happy to know any proper solution to this problem in an elegant way.
Or maybe we are just thinking the wrong way when talking about react-redux-sagas apps.
For the sake of the example, I'll be talking about a reusable editor component, that edits documents and submits them to server.
In the code that is using the editor, I give each editor a unique id. E.g.
const Comment = (props) => {
return <div>
<h3>Add new comment</h3>
<Editor editorId={`new-comment-${props.commentId}`} />
</div>
}
In the redux state, I have one subreducer editor with objects keyed by the editorId, so the redux state is something like:
{
otherStuff: {...},
editor: {
"new-comment-123": { isActive: true, text: "Hello there" },
"new-comment-456": { isActive: false, text: "" },
...
},
...
}
Then in Editor mapStateToProps I use selectors to get the data for the correct instance:
const mapStateToProps = (state, ownProps) => {
return {
isActive: selectors.isActive(state, ownProps.editorId),
text: selectors.text(state, ownProps.editorId)
}
}
The selectors are built in reselect style, either manually or by actually using reselect. Example:
// Manual code
export const getEditor = (state, editorId) => state.editor[editorId] || {};
export const isActive = (state, editorId) => getEditor(state, editorId).
export const text = (state, editorId) => getEditor(state, editorId).text;
// Same in reselect
import { createSelector } from 'reselect'
export const getEditor = (state, editorId) => state.editor[editorId] || {};
export const isActive = createSelector([getEditor], (editorData) => editorData.isActive);
export const text = createSelector([getEditor], (editorData) => editorData.text);
If you want to extend this to be used in multiple apps, you need to export your component, reducer and sagas. For a working example, check out https://github.com/woltapp/redux-autoloader or even http://redux-form.com
If I understand your concern correctly, you could implement mapStateToProps as if it receives the part of state you need and call it, say, mapStateToYourComponentProps, and in actual mapStateToProps you just call mapStateToYourComponentProps and pass it appropriate part of state
I found a way to make the components totally independent from state and hierarchy within the app.
Basically, each component must expose a method to set the path within the state. Then you have to initialize it when either when you import it before using it. You could also implement it in another way so you receive it inline as a prop.
It makes uses of reselect to establish the selection.
Each component knows the name of its key in the state.
The root component will import other components and it will call the setPath method of each one passing the root component path.
Then each component will call the setPath of each subcomponent passing their own location in the state. "Each parent will init their children"
So each component will set a path in the store based on the naming "parent path + local path (component key name in the store)".
This way you would be defining a nested routing with 'createSelector' method from reselect, like this: ['rootKey','subComponent1Key','subsubComponent1Key].
With this, you have the store isolation completed. Redux actions will just change the bit needed so yo have this part also covered by the framework.
It worked like a charm for me, please let me know if its good before I mark it as good.
If you have some free time, try the npm package redux-livequery (https://www.npmjs.com/package/redux-livequery) I just wrote recently.
There is another way to manage your active list.
let selector0 = (state) => state.task.isComplete;
let selector1 = (state) => state.task.taskList;
this.unsub2 = rxQueryBasedOnObjectKeys([selector0, selector1], ['isActive', 'task'], (completeTaskList) => {
// equal SQL =>
// select * from isActive LEFT JOIN taskList on isActive.child_key == taskList.child_key
console.log("got latest completeTaskList", completeTaskList);
// you can do whatever you want here
// ex: filter, reduce, map
this.setState({ completeTaskList });
}, 0);
In the reducer:
case "MARK_ACTIVE_TASK": {
let { id } = action.meta;
return update(state, { isActive: { [id]: { $set: { active: Date.now() } } } });
}
case "UNMARK_ACTIVE_TASK": {
let { id } = action.meta;
return update(state, { isActive: { $apply: function (x) { let y = Object.assign({}, x); delete y[id]; return y; } } });
}
It lets you have simpler reducer. In addition, there is no more nested selector function or filter which is really expensive operation. Putting your all logic in the same place would be great.
And it can do even more complexity operation like how to get complete and active list.
let selector0 = (state) => state.task.isComplete;
let selector1 = (state) => state.task.isActive;
let selector2 = (state) => state.task.taskList;
this.unsub3 = rxQueryInnerJoin([selector0, selector1, selector2], ['isComplete', 'isActive', 'task'], (completeAndActiveTaskList) => {
// equal SQL =>
// select * from isComplete INNER JOIN isActive on isComplete.child_key == isActive.child_key
// INNER JOIN taskList on isActive.child_key == taskList.child_key
console.log("got latest completeAndActiveTaskList", completeAndActiveTaskList);
// you can do whatever you want here
// ex: filter, reduce, map
this.setState({ completeAndActiveTaskList });
}, 0);
If you would like to get complete or active list, it's also easy to get.
The more example, please refer to the sample code => https://github.com/jeffnian88/redux-livequery-todos-example

Refresh component after remove an object - ReactJS

I would like to ask for your opinion about this. Basically what I want now is to refresh the component list after I remove an item on the object lists. Currently I can successfully removed an item via deleteHeroes(list,index) function but my component is not refreshing at all to reflect the removed item. Can you should some light on how can I do it? here's my code below:
componentDidMount(){
// Fetch lists of heroes
this.props.getAllHeroes();
}
renderHeroesList(){
var listData = this.props.heroes.map((heroes,index) => (
<HeroesListComponent key={heroes.id} heroes={heroes} onDelete = { () => this.deleteHeroes(heroes,index)} />
));
return listData;
}
// Remove item on heroes list
deleteHeroes(list,index){
const heroesProps = this.props.heroes;
heroesProps.splice(heroesProps.indexOf(index), 1);
}
render(){
return(
{ this.renderHeroesList() }
);
function mapStateToProps(state){
return {
heroes: state.heroes.data
}
}}
function mapDispatchToProps(dispatch){
return bindActionCreators ({
getAllHeroes: getAllHeroes,
deleteHero: deleteHero,
}, dispatch);
}
You have a side effect, which should be avoided at all cost. In your case, it is that you are mutating the internal reference of heroes props. So typical plan to avoid this problem is to clone the props and then dispatch a new action with the new props data. So your code should look like:
deleteHeroes(list,index){
const clonedHeroesProps = this.props.heroes.slice(0); // clone the array
heroesProps.splice(heroesProps.indexOf(index), 1);
dispatch({type: 'SAVE_HEROES', heroes: clonedHeroesProps});
}
The better, more Reactish way would be by using Immutability Helpers:
deleteHeroes(list,index){
const clonedHeroesProps = update(heroesProps, {$splice: [[heroesProps.indexOf(index), 1]]});
dispatch({type: 'SAVE_HEROES', heroes: clonedHeroesProps});
}
Because you did not notify anything about your change.
You must dispatch an action after deleteHeroes, something like this
deleteHeroes(list,index){
const { heroesProps, dispatch }= this.props;
heroesProps.splice(heroesProps.indexOf(index), 1);
dispatch({type: 'SAVE_HEROES', heroes: heroesProps});
}
// and somewhere in reducer
case SAVE_HEROES:
return {...state, heroes: action.heroes}
and write the appropriate function to reducer.
But let component delete hereos (alter state), you broke the idea of redux.
Instead, the component should not directly modify heroes, you dispatch an action like 'DELETE_HEROES' and let reducer do the rest.
You have 2 main options to consider:
put the data (state) to shared common ancestor, that is standard strategy from react: https://facebook.github.io/react/docs/lifting-state-up.html
put all the state to Redux (https://github.com/reactjs/react-redux), then u connect your component and display based on the redux state. this case, u do not have to do any thing after deleting, redux framework will take care of the data flow and ui refresh

Resources