How to access nested Redux state in React components? - reactjs

What's an efficient/elegant way to access nested data from branches of the state that are only available part of the time?
I'm new to React and this problem keeps coming up. I have an ok grasp on Redux and using middleware like Thunk & Saga for the API stuff, but I still don't understand the proper way to write components so that they're not trying to grab non-existent state.
For example, when loading a user's profile photo into a header after the user signs in, the URL will be in the redux store as state.user.userData.photos.primary ...if I try to access that location when the user hasn't signed in, userData is still undefined and React will throw an error. So my solution has been to write assignments like:
let profilePhoto = props.user.userData ? props.user.userData.photos.primary : '';
... which seems cumbersome and inefficient, especially since it requires mapping the entire userData object in mapStateToProps only for the sake of accessing a tiny chunk of data.
I wish I could target it earlier. For example, in mapStateToProps:
profilePhoto : state.user.userData.photos.primary || '';
...but that results in "can't access 'photos' of undefined". So what's an efficient/elegant way to access nested data from branches of the state that are only available part of the time?

In your mapStateToProps access your state like this:
profilePhoto : state.user.userData ? state.user.userData.photos.primary : ''
and then use profilePhoto as props. This will not throw an error of undefined.

In my current application I initialize the redux state with all the data tree that I will use; so that I will unlikely end up with undefineds.
So a solution would be:
const initialState = {
userData: {
photos: {
primary: ""
}
}
}
This might look ugly in the first glance but it helps me keep track of what my data will look like, and can be simplified by defining parts of the object individually.
An alternative could be keeping a flag in the redux state like isUserPrimaryPhotoDefined which you would set to true when you are setting the primary variable; so you can use the flag in the ternary. However that might not guarantee getting rid of the possible errors completely.

With ES6 Destructuring Assignment, you can access nested keys from props in a cleaner way.
const { user: { userData: { photos: { primary = ''} = {}} = {}} = {}} = props;
console.log(primary); // prints primary value or '' if not defined

Related

Cloning an object within a redux reducer

I've recently learned React and Redux, and right when I thought I was getting the hang of it I've been hung up on this.
In my reducer I'm under the impression that the spread operator should create a new instance of the object at a difference memory location. However, using === to compare the objects returns true which I believe means the objects are the same instances.
const initState = {
loadcases: {
1: {
isActive: false,
profile: 'UB'
},
2: {
isActive: true,
profile: 'PFC'
}
}
}
const designInput = (state=initState, action={}) => {
let newState = {...state}; //clone design input state
console.log(newState.loadcases === state.loadcases)
// Prints: TRUE ---- WHYYYYYYY????????????
// goes on to modify newState before returning it.
}
The redux logger is showing my previous state to already equal the new state which has me thinking that this here is interfering with the store directly. I've read through some redux docs and other posts which make me believe I have set everything else up correctly.
Any resources to educate me more on this issue is also much appreciated.
UPDATE:
Jayce's answer was exactly what was happening.
This SO post outlines which method for a deep clone will be suitable. For me, it's likely I'll want to store dates and other data so I've chosen to use lodash cloneDeep method.
So now: console.log(cloneDeep(state) === state.loadcases); // false
Your assumption that the spread operator creates a new object is true, however it doesn't do a deep clone of objects. It copies the references of nested values over to the new object, so that's why it's printing that they're equal.
If you want to deeply clone something with nested values you can't use spread, you have to use another method. You could use a library like lodash, or something such as JSON.parse(JSON.stringify(obj)), there's multiple options just google for something like "Javascript deep clone object"

Giving React state a name in DevTools

When using React useState(), if you have something like the following const [cartItems, setCartItems] = useState([]) it shows in React DevTools as State: []. Other than changing the state from an array into an object containing an array such as useState({ cartItems: [] }) is there anything else I can do to show a key for the state? If not, is this bad practice to use an object or am I ok using this? Reason I want to have a key, is when debugging if you have a number of different items of state, the DevTools just shows as State: [], State: {[...]}, State: [] etc which is difficult to see what is what.
Not really since the useState hook uses array destructuring to assign variable names, i.e. the names are only used within the function, not in the react framework.
const [someSpecialAwesomestate, setSomeSpecialAwesomestate] = useState();
equates to
const stateObject = useState();
// stateObject[0] is the state
// stateObject[1] is the callback state mutator function
From what I understand, React further obfuscates this by simply storing the hooks themselves internally in an array.
It may be a bit more verbose to store objects with a single root key, and tedious to manage state updates, but if it helps your dev flow to see what each state object IS in the devtool, then why not do what helps you code better?

Best practice for only getting parts of the store/state in React component using redux

I implemented by using redux thunk that if one component is mounted, that the store is filled by calling an API.
The data which are requested have much more fields than I need in particalur components. So could you please give me a tip what the best practice would be to get only parts of the data. Because storing a reduced version of the data also (kind of duplication) wouldn't be a good practice, would it?
If I understood the question correctly, you can use the selectors
You can deconstruct data.
For example, when you connect your component to the redux state.
const mapStateToProps = state => {
relevantPartOfTheState: state.some.part.of.the.state
}
This is essentially what selectors do.
The data which are requested have much more fields than I need in particalur components
You are not required to save all of it. You can have something like this.
switch (action.type) {
case RECEIVE_USELESS_DATA:
return action.payload.onlyRelevantData
}
You can either use selectors as mentioned above or somethine like that:
const mapStateToProps = state => {
relevantPartOfTheState: fetchSliceOfReducer(state)
}
The fetchSliceOfReducer(state) can be a function from inside the reducer file where you take only part of the state. Obviously you have to import the function in your component file and it can be reused for other components. So if your state is something like that
state = {
value: true,
name:"Michael"
}
your function would be const fetchSliceOfReducer = state => state.value
Selectors would be more appropriate for a growing app, where as the example above is nice and neat for something smaller

Correct way to share one query result throughout the app

Let's say at the top of the app, we retrieve some basic information about the app or user before rendering the rest of the application:
const getUser = gql`
query getUser(id: Int!) {
user(id: $id) {
id
name
}
}
`)
function App({ data }) {
return (
<div>
{!data.loading && !data.error && (
// the application
)}
</div>
)
}
export default graphql(getUser, {
options: (props) => ({ variables: { id: props.id }})
})(App)
Now anywhere in the application, it is safe to assume that the user has been loaded and is stored. What is the proper way for another deeply nested component to the retrieve the user data without having to redo the querying and loading logic?
This is the very basic use of a store-based library like Redux. This is not the purpose to guide every step of the way here but you are looking for a single source of truth as described here: http://redux.js.org/docs/introduction/ThreePrinciples.html
In short:
Receiving getUser response should trigger a 'LOGGED_IN' action dispatching user Data, this would be catched by a reducer updating the user object in your store (as much nested as you want), a container would then connect to this user in the store and have all its data using connect()
As of now, I'm not certain there is a proper way, but these are the options I think are reasonable
Manually pass down data via props
Wrap your deeply nested component with the same query
Manual pass down ensures your components rerender correctly, but it can be a pain to refactor. Wrapping your nested component would just hit the cache. Yes, you probably need to redo the loading logic, but that's not a show stopper.
My advice is to manually pass down props for shallow nested components and rewrap deeply nested components. Unfortunately, react-apollo doesn't provide a convenient way to access the apollo-store for nested components the same way that redux's connect container does.

Sending error message from reducer to user

I'm new to React and Redux and I'm trying to write a simple application where a person can submit a URL for an image and it will show up on the page. Note that there is no backend to the application as of yet.
export const addImage = (url) => {
return {
type: ADD_IMAGE,
key: Guid.create().toString(),
payload: url
}
}
Adding an image creates an action of type ADD_IMAGE and my reducer updates the state consequently. However I also check if the URL is already in the list.
switch (action.type) {
case ADD_IMAGE:
if (state.find(image => image.url === action.payload)) {
return state;
} else {
return(
[
...state,
{key: action.key, url: action.payload}
]
);
}
break;
default:
}
The problem is that when I deny a post because the URL is already in the state I also want to convey that message to the user by showing it in a div next to the form. From what I've read I think I'm not supposed to try to access React state from reducers (if that is even possible) and... well.. I'm just stuck. I've been trying to find a simple guide on how to do this but I find nothing I can quite understand. After adding a database I guess I will have to do this as part of the async process but as I have it now I guess there should be some kind of simple solution.
You are starting to introduce logic into your reducer and this will inevitably lead to situation where you need to process some state outside of the reducer's scope.
The solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar package). This allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to dispatch an error action under certain conditions is an excellent use-case for redux-thunk.
Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Thunk example
export const addImage = (url) => {
return (dispatch, getState) => {
const key = Guid.create().toString()
dispatch({
type: ADD_IMAGE,
key,
payload: url
})
const state = getState()
// you would want to use a `selector` here to locate the existing image
// within the state tree
const exists = selectors.images.exists(state, url)
if (exists) {
dispatch(actions.ERROR_IMAGE_EXISTS({key, url}))
}
}
}
A note on selectors
You will see that I am using a selector to determine if the image exists. In the same way that thunks are the place to put your dispatch logic, a selector is the place to put your state-traversal logic. They are used to return portions of the state-tree or provide simple state-utilities such as the exists function shown above. Packages are available to help, for example reselect.
Follow on questions from comments
Are selectors not a built-in thing in Redux?
No they are not. Selectors are an idea that builds on top of redux and the concept exists as a place to put your state searching, caching, reading logic. This extracts the sometimes complex state traversal logic out of your thunks and components and into a nice tidy, structured collection of selectors.
Why use a selector instead of state.images.find(i => i.url === url)?
If you use a selector package then you get far more benefit than just a good separation of concerns, you get a big performance improvement (see usage example below).
Here are the headlines from the popular reselect package:
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
Why doesn't actions.ERROR_IMAGE_EXISTS(url) work for me
Because I just made that up for the example. The point is that you can dispatch actions from within the thunk, how you declare or get access to the action is up to you. I tend to centralise all my shared actions into an actions object that I import.
Selector usage example
Here is an example from my real-life code that shows how I use selectors to passing portions of the state as props to a react component:
const mapStateToProps = (state) => ({
model: services.model.selector.getSelected(state),
build: services.build.selector.getLastBuild(state),
recommendations: services.recommend.selector.getRecommendations(state)
})
Each of these selectors is finding the correct portion of the state tree and delivering it back ready for use. Nice and tidy, and if you use reselector, very efficient.

Resources