Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialVince Brown
16,249 PointsHow to normalize nested data for React/Redux App
I have been trying to wrap my head around normalizing nested data for my redux store with https://github.com/paularmstrong/normalizr
Upon logging in a user I get back a response with their info and relationships to other data types.
{
"data": {
"id": "207",
"type": "users",
"attributes": {
"email": "roberta@nitzsche.biz",
"last-sign-in-at": null,
"username": "april.johns",
"first-name": "Audie",
"last-name": "Halvorson",
"short-bio": "Ut exercitationem ",
"bio": "Ut exercitationem totam perferendis consequatur dolorem veritatis dolorem.",
"location": null,
"gender": "male",
"birthday": "1986-10-07",
"email-digest": "daily_digest",
"email-notifications": "instantl_notifications",
"auth-token": "_HV-S6qrdobecr-rr6gs",
"avatar-large-2x": "/missing_avatar.png",
"avatar-large": "/missing_avatar.png",
"cover-desktop-2x": "/missing_cover.png",
"cover-desktop": "/missing_cover.png",
"cover-mobile-2x": "/missing_cover.png",
"cover-mobile": "/missing_cover.png",
"wp-id": null,
"created-at": "2016-10-07T23:16:13.565Z",
"updated-at": "2016-10-07T23:16:13.565Z"
},
"relationships": {
"websites": {
"data": [
{
"id": 11,
"url": "http://mohr.org/coy_rowe",
"user-id": 207,
"created-at": "2016-10-07T23:16:13.651Z",
"updated-at": "2016-10-07T23:16:13.651Z"
}
]
},
"books": {
"data": [
{
"id": 11,
"name": "Outdoors & Industrial",
"image-url": "https://robohash.org/sitveritatisab.png?size=300x300&set=set1",
"author": "Meggie Balistreri",
"created-at": "2016-10-07T23:16:13.629Z",
"updated-at": "2016-10-07T23:16:13.629Z"
}
]
},
"movies": {
"data": [
{
"id": 11,
"name": "Movies, Home & Electronics",
"image-url": "https://robohash.org/quiaarchitectoodit.png?size=300x300&set=set1",
"author": "Eveline Ziemann",
"created-at": "2016-10-07T23:16:13.642Z",
"updated-at": "2016-10-07T23:16:13.642Z"
}
]
},
"interests": {
"data": [
{
"id": 22,
"name": "Synergistic Aluminum Gloves",
"created-at": "2016-10-07T23:16:13.596Z",
"updated-at": "2016-10-07T23:16:13.596Z"
}
]
},
"virtues": {
"data": [
{
"id": 22,
"name": "Ergonomic Wool Gloves",
"created-at": "2016-10-07T23:16:13.582Z",
"updated-at": "2016-10-07T23:16:13.582Z"
}
]
},
"features": {
"data": [
]
},
"strengths": {
"data": [
{
"id": 22,
"name": "Ergonomic Wool Gloves",
"created-at": "2016-10-07T23:16:13.582Z",
"updated-at": "2016-10-07T23:16:13.582Z"
}
]
},
"teachers": {
"data": [
{
"id": 22,
"name": "Ergonomic Wool Gloves",
"created-at": "2016-10-07T23:16:13.582Z",
"updated-at": "2016-10-07T23:16:13.582Z"
}
]
}
}
}
After reading through a lot of tutorials I believe my data normalized would be best represented by this shape.
"currentUser": {
"lastUpdated": 0,
"userId": 207,
"attributes": {
"email": "roberta@nitzsche.biz",
"last-sign-in-at": null,
"username": "april.johns",
"first-name": "Audie",
"last-name": "Halvorson",
"short-bio": "Ut exercitationem ",
"bio": "Ut exercitationem totam perferendis consequatur",
// etc....
},
"relationships": {
"websites": [11],
"books": [22],
"movies": [33],
"interests": [21],
"virtues": [34],
"features": [22],
"strengths": [15],
"teachers": [45],
}
}
Does this mean I create Schemas for all the relationship types ? websites, books, movies, interests, virtues, features, strengths, teachers,
and then one relationshipSchema that gets defined with all those schemas nested ? For the relationship's all types will be referenced in different areas though out the site.
ex) page where they select interests and I get a response back of all interests.
[
{
"id": 54,
"name": "Fantastic Wooden Hat",
"created_at": "2016-10-12T18:54:01.669Z",
"updated_at": "2016-10-12T18:54:01.669Z"
},
{
"id": 55,
"name": "Fantastic Wooden Hat",
"created_at": "2016-10-12T18:54:01.669Z",
"updated_at": "2016-10-12T18:54:01.669Z"
},
{
"id": 56,
"name": "Fantastic Wooden Hat",
"created_at": "2016-10-12T18:54:01.669Z",
"updated_at": "2016-10-12T18:54:01.669Z"
},
{
"id": 57,
"name": "Fantastic Wooden Hat",
"created_at": "2016-10-12T18:54:01.669Z",
"updated_at": "2016-10-12T18:54:01.669Z"
}
]
Which I normalize to..
interests: {
54: {
"id": 54,
"name": "Fantastic Wooden Hat",
"created_at": "2016-10-12T18:54:01.669Z",
"updated_at": "2016-10-12T18:54:01.669Z"
},
55: {
"id": 55,
"name": "Fantastic Wooden Hat",
"created_at": "2016-10-12T18:54:01.669Z",
"updated_at": "2016-10-12T18:54:01.669Z"
},
56: {
"id": 56,
"name": "Fantastic Wooden Hat",
"created_at": "2016-10-12T18:54:01.669Z",
"updated_at": "2016-10-12T18:54:01.669Z"
},
57: {
"id": 57,
"name": "Fantastic Wooden Hat",
"created_at": "2016-10-12T18:54:01.669Z",
"updated_at": "2016-10-12T18:54:01.669Z"
}
}
Any help would be greatly appreciated, also articles, tutorials, video examples out there as well.
Thank you,
Vince Brown
16,249 PointsGeoshepherds HB thanks for helping out!
I have not not read the source of #normalizr yet (jumping into that now) but I have managed to get it working with a less nested response, what seems to be throwing me off with my current implementation is the extra data key in each relationship ex . ) relationships.interests.data
The project I am working on is in a private repo so I can't share but here is the actions/reducers for the users.
userSchema.js
import { Schema, arrayOf } from 'normalizr'
// Main Schema
const relationshipSchema = new Schema('relationships')
const website = new Schema('websites')
const book = new Schema('books')
const interest = new Schema('interests')
const virtue = new Schema('virtues')
const feature = new Schema('features')
const strength = new Schema('strengths')
const teacher = new Schema('teachers')
relationshipSchema.define({
websites: arrayOf(website),
books: arrayOf(book),
interests: arrayOf(interest),
virtues: arrayOf(virtue),
features: arrayOf(feature),
strengths: arrayOf(strength),
teacher: arrayOf(teacher),
})
export { relationshipSchema }
userActionTypes.js
export const LOGIN_USER_INIT = 'LOGIN_USER_INIT'
export const LOGIN_USER_ERROR = 'LOGIN_USER_FAILURE'
export const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS'
export const AUTH_USER = 'AUTH_USER'
export const UNAUTH_USER = 'UNAUTH_USER'
export const PASSWORD_RESET_INIT = 'PASSWORD_RESET_INIT'
export const PASSWORD_RESET_ERROR = 'PASSWORD_RESET_ERROR'
export const PASSWORD_RESET_SUCCESS = 'PASSWORD_RESET_SUCCESS'
userActions.js
// Packages
import { normalize } from 'normalizr'
// Schemas
import * as schema from './userSchema'
// Action Constants
import * as actions from './userActionTypes'
// Utils
import { startSession, resetPassword } from 'utils/users'
function authUser (authToken) {
return {
type: actions.AUTH_USER,
authToken,
}
}
function loginUserInit () {
return {
type: actions.LOGIN_USER_INIT,
}
}
function loginUserSuccess (userId, user, timestamp) {
return {
type: actions.LOGIN_USER_SUCCESS,
userId,
user,
timestamp,
}
}
export function loginUserError (error) {
console.warn(error)
return {
type: actions.LOGIN_USER_ERROR,
error: 'Sorry! Your username and password didn\'t match. Please try again.',
}
}
export function loginAndAuthUser (username, password) {
return function (dispatch) {
dispatch(loginUserInit())
return startSession(username, password)
.then((response) => {
console.log('RESPONSE', response.data.data)
const normalizedResponse = normalize(response.data.data.relationships, schema.relationshipSchema)
console.log('Normalized Response', normalizedResponse)
const userId = response.data.data.id
const userAttrs = response.data.data.attributes
const userAuthToken = userAttrs['auth-token']
dispatch(loginUserSuccess(userId, userAttrs, Date.now()))
dispatch(authUser(userAuthToken))
})
}
}
export function passwordResetInit () {
return {
type: actions.PASSWORD_RESET_INIT,
}
}
export function passwordResetSuccess () {
return {
type: actions.PASSWORD_RESET_SUCCESS,
}
}
export function passwordResetError () {
return {
type: actions.PASSWORD_RESET_ERROR,
}
}
export function sendPasswordReset (email) {
return function (dispatch) {
dispatch(passwordResetInit())
return resetPassword(email)
.then(() => dispatch(passwordResetSuccess()))
}
}
userReducers.js
import * as actions from './userActionTypes'
import { fromJS, Map } from 'immutable'
// Reducers
const initialUserState = fromJS({
lastUpdated: 0,
userId: '',
info: {},
relationships: {
websites: [],
books: [],
movies: [],
interests: [],
virtues: [],
features: [],
strengths: [],
teachers: [],
},
})
function user (state = initialUserState, action) {
switch (action.type) {
case actions.LOGIN_USER_SUCCESS :
return state.merge({
userId: action.userId,
info: action.user,
lastUpdated: action.timestamp,
})
default:
return state
}
}
const initialState = Map({
authToken: '',
isAuthed: false,
isFetching: false,
isSubmitting: false,
error: '',
currentUser: {},
})
export default function users (state = initialState, action) {
switch (action.type) {
case actions.LOGIN_USER_INIT :
case actions.PASSWORD_RESET_INIT :
return state.merge({
isSubmitting: true,
})
case actions.LOGIN_USER_ERROR :
return state.merge({
isSubmitting: false,
error: action.error,
})
case actions.LOGIN_USER_SUCCESS :
return state.merge({
isSubmitting: false,
error: '',
currentUser: user(state[action.userId], action),
})
case actions.AUTH_USER :
return state.merge({
isAuthed: true,
authToken: action.authToken,
})
case actions.UNAUTH_USER :
return state.merge({
isAuthed: false,
authToken: '',
})
case actions.PASSWORD_RESET_SUCCESS :
case actions.PASSWORD_RESET_ERROR :
return state.merge({
isSubmitting: false,
})
default :
return state
}
}
Currently I just console.log
the normalized response, everything is going to the correct key but the extra data
key I mentioned above throws it off and, the whole object of each interest (relationship) is being stored instead of just the ID.
Screenshot - https://cl.ly/091W213S0V0y
Thanks again man!
Angelica Hart Lindh
19,465 PointsAngelica Hart Lindh
19,465 PointsHi Vince,
Nice question! I personally have never used the normalizr library that you're talking about here. In applications that I've built using a web socket in Node.js as an example I normalised the data shape myself without the need for an additional library but that was quite time consuming. I did use immutable.js and followed some basic examples from stackoverflow such as this one to implement it using redux .
A key concept to what you're trying to achieve you seem to have grasped with your example. I'm assuming that have the normalizing data shape post in the redux.js documentation.
From reading a little into the normalizr it appears you would create a
Schema
for each of the 'websites, books, movies, interests, virtues, features, strengths, teachers' as you have assumed. From there, with the Schemas setup, you can then define your nesting rules using those predefined Schemas. The library seems to have some helpful functions from looking at the docs. I also noticed it has dependencies on some lodash functions to achieve it's aims. Have you checked out the source code, it seems quite small like max 500 lines in total?Do you have your code for this project up on github as a repo to take a look at in more detail?
Cheers