How component is getting data from redux store? - reactjs

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>

Related

How to set redux initial State with api call in react with functional component

I am using redux in react with typescript. Currently, I am setting my redux initial state via an external JSON file but now I want to set my initial state using API. can I directly create a function in the reducer and create an API call in it or is there any other way to achieve it.
Here is my reducer code:
type employee = {
id: number,
name: string,
age: number,
}
type department = {
[department: string]: employee[]
}
right now I am setting initialState via an external file like below:
const initial = require("./data.json");
I am using this simply in a slice:
const departmentSlice = createSlice ({
name: 'employee',
initialState: initial,
reducer: {
}
})
now I want to set initialState via an API call like below:
const initial = () => {
fetch('url').then((response) => {
return response.json();
}).catch((error) => {
throw new Error(error);
});
}
and then set it in initailState.
initialState: initial();
I am getting this Error:
Error: Reducer "employee" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state.
First of all, your createSlice method is synchronous, and your fetch method is async. This means when your slice is created, the fetch method doesn't complete yet. So you can't do this. You have to define an initially empty state, for example:
initialState: {data: [], loading: true}
Because you have a loading variable, you can show a spinner for your user, and when the fetch arrives, then you can change this variable back to false, and you can populate the data at the same time.
Other options can be: state hydration, but this is a more complex case when you don't have access on the backend side.
Note: Your first case is works with JSON, because the JSON import happens before the state initialization.

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.

Accessing JSON Array Data in React.js/Next.js Combo

Given an API which returns JSON data like:
["posts":
{"id":1,
"name":"example",
"date":"exampledate",
"content":"examplecontent",
"author":"exampleauthor"},
{"id":2,
..]
The length of the array is unknown.
I am fetching data via isomorphic-fetch like this:
displayPosts.getInitialProps = async function() {
const res = await fetch('.../post');
const data = await res.json();
return{
posts: data.posts
}
}
which is working (console.log.stringify(data)).
Now i want to display such posts on my displayPosts page.
Therefore i am using the following React Component.
class Posts extends React.Component {
stat = {
// here i don't know how to set the state
}
render() {
return (
// i want to render the data here
);
}
}
export default Posts;
Question: How do i set a state, so that i can neatly display every post in my displayPosts.js page with
<Posts posts={props.Posts}/>
?
class Posts extends React.Component {
state = {
posts: []
}
componentDidMount() {
this.savePosts();
}
componentDidUpdate() {
this.savePosts();
}
savePosts = () => {
if(this.props.posts){
//do any other processing here first if you like
this.setState({
posts: this.props.posts
});
}
}
You probably don't need to save the posts in state, since you could just pull them from props directly. But if you need to process or transform them somehow, it might make sense.
In either case, you just hook into the lifecycle methods to do this.
Note: This will set the state every time the component updates. Often you only want to update the state when that specific prop changes. If so, you can first compare the old and new props to see if it has changed in a way that means you want to update your state.

Redux: Reducer not mutating state in first attempt

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

What are selectors in redux?

I am trying to follow this code in redux-saga
export const getUser = (state, login) => state.entities.users[login]
export const getRepo = (state, fullName) => state.entities.repos[fullName]
Which is then used in the saga like this:
import { getUser } from '../reducers/selectors'
// load user unless it is cached
function* loadUser(login, requiredFields) {
const user = yield select(getUser, login)
if (!user || requiredFields.some(key => !user.hasOwnProperty(key))) {
yield call(fetchUser, login)
}
}
This getUser reducer (is it even a reducer) looks very different from what I would normally expect a reducer to look like.
Can anyone explain what a selector is and how getUser is a reducer and how it fits in with redux-saga?
getUser is not a reducer, it is indeed a selector, that is, a function that knows how to extract a specific piece of data from the store.
Selectors provide an additional layer such that if you altered your store structure and all of a sudden your users were no longer at state.entities.users but instead at state.users.objects.entities (or whatever) then you only need to update the getUser selector and not every place in your app where you were making a reference to the old location.
That makes them particularly handy when it comes to refactoring your Redux store.
Selectors are getters for the redux state. Like getters, selectors encapsulate the structure of the state, and are reusable. Selectors can also compute derived properties.
You can write selectors, such as the ones you saw in redux-saga. For example:
const getUsersNumber = ({ users }) => users.length;
const getUsersIds = ({ users }) => users.map(({ id }) => id);
etc...
You can also use reselect, which is a simple “selector” library for Redux, that memoize selectors to make them more efficient.
Selectors are functions that take Redux state as an argument and return some data to pass to the component.
const getUserData = state => state.user.data;
Why should it be used?
One of the main reasons is to avoid duplicated data in Redux.
Your data object shape keeps varying as your application grows, so rather than making changes in all the related component.It is much recommended/easier to change the data at one place.
Selectors should be near reducers because they operate on the same state. It is easier for data to keep in sync.
Using reselect helps to memoize data meaning when the same input is passed to the function, returns the previous result rather than recalculating again.So, this enhances your application performance.
function mapStateToProps (state) {
return {
user: state.user,
}
}
initialState of reducer by user store
const initialState = {
isAdmin:false,
isAuth:false,
access:[1,2,5]
};
class AppComp extends React.Component{
render(){
const {user: { access:access}} = this.props;
const rand = Math.floor(Math.random()*4000)
return (<div>
{`APP ${rand} `}
<input type="button" defaultValue="change auth" onClick={this.onChangeUserAuth} />
<p>TOTAL STATUS COUNT IS {access.length}</p>
</div>)
}
}}
but you can use selector
var getUser = function(state) {
return state.user
}
const getAuthProp = createSelector(
getUser,
(user) => user.access
);
function mapStateToProps (state) {
return {
// user: state.user,
access: getAuthProp(state)
}
}
Main Problem is this component use all user: state.user and any changes in user (etc isAdmin ,isAuth, access) runs rerender this component which need only part of this store - access!!!
In Redux, whenever an action is called anywhere in the application,
all mounted & connected components call their mapStateToProps
function. This is why Reselect is awesome. It will just return the
memoized result if nothing has changed.
In the real world, you will most likely need the same certain part of
your state object in multiple components.
https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c
The createSelector function provided by Reselect implements the most basic way to derive a selector from previous selectors. The simplest use case is to derive a selector from a single other selector. In this case, the parameters to createSelector are the input selector and a function transforming the result of that selector into the result of the new selector. For example
var getProducts = function(state) {
return state.products
}
import {getProducts} from '../app/selectors'
import {createSelector} from 'reselect'
export const getProductTitles = createSelector(
getProducts,
(products) => products.map((product) => product.get('title'))
)
This is equivalent to (ignoring memoization):
import {getProducts} from '../app/selectors'
export const getProductTitles = (state) => {
return getProducts(state).map((product) => product.get('title'))
}
The createSelector function can combine data from multiple selectors as well as from a single selector. We can pass any number of selectors to createSelector, and their results will be passed to the function passed as the final argument. For a (somewhat contrived) example:
const isInCheckout = createSelector(
getIsShippingPage,
getIsBillingPage,
getIsConfirmationPage,
(isShipping, isBilling, isConfirmation) =>
isShipping || isBilling || isConfirmation
)
is equivalent to
const isInCheckout = (state) => {
return (
getIsShippingPage(state) ||
getIsBilingPage(state) ||
getIsConfirmationPage(state)
)
}
common pattern when writing mapStateToProps functions with selectors is to return an object with each key storing the result of a particular selector. The createStructuredSelector helper function in Reselect lets us write this pattern with the minimum of boilerplate. For example, if we writ
const mapStateToProps = createStructuredSelector({
title: getProductTitle,
price: getProductPrice,
image: getProductImage
})
it is equivalent to
const mapStateToProps = (state) => {
return {
title: getProductTitle(state),
price: getProductPrice(state),
image: getProductImage(state)
}
}
https://docs.mobify.com/progressive-web/0.15.0/guides/reselect/

Resources