3

Newly using ARc (Atomic React) for a project (which is great and I think has a ton of future-proofing for what is ultimately going to be an enterprise-level product), BUT it's only future proof if I don't puzzle it together incorrectly and I currently can't get my head around how/when/where/why to use selectors in my app.

Have read this: How is state passed to selectors in a react-redux app? which is great, but I think need to see a few more examples to actually get it.

Current problem I'm trying to solve is getting a user object (which is accessible app-wide) and also, with that data, creating a view-friendly slice of state for an account settings page where the user can modify their own name, email, etc. I wouldn't want to just use the user store because don't want them changing anything until they actually hit submit, but do want to maintain form state in redux.

I load in my user which is all good (can access it via props on different containers where I want): Actions:

export const USER_GET_REQUEST = 'USER_GET_REQUEST'
export const USER_GET_REQUEST_SUCCESS = 'USER_GET_REQUEST_SUCCESS'
export const USER_GET_REQUEST_FAILURE = 'USER_GET_REQUEST_FAILURE'

export const getUserRequest = (params, resolve, reject) => ({
  type: USER_GET_REQUEST,
  params,
  resolve,
  reject,
})

export const getUserRequestSuccess = user => ({
  type: USER_GET_REQUEST_SUCCESS,
  user,
})

export const getUserRequestFailure = error => ({
  type: USER_GET_REQUEST_FAILURE,
  error,
})

Sagas:

import { take, put, call, fork } from 'redux-saga/effects'
import api from 'services/api'
import * as actions from './actions'

export function* readGetUser() {
  try {
    const data = yield call(api.get, '/user')
    yield put(actions.getUserRequestSuccess(data))
  } catch (e) {
    yield put(actions.getUserRequestFailure(e))
  }
}

export function* watchUserGetRequest() {
  while (true) {
    const { params } = yield take(actions.USER_GET_REQUEST)
    yield call(readGetUser, params)
  }
}

export default function* () {
  yield fork(watchUserGetRequest)
}

Reducer:

import { initialState } from './selectors'
import { USER_LOGIN_SUCCESS, USER_GET_REQUEST_SUCCESS } from './actions'

export default (state = initialState, action) => {
  switch (action.type) {
    case USER_LOGIN_SUCCESS:
      return state
    case USER_GET_REQUEST_SUCCESS:
      return {
        ...state,
        user: action.user,
      }
    default:
      return state
  }
}

Selector (my nonsense code commented, single line works as far as making user actually have the user data, but WHERE DOES COMPUTED DATA GO/HOW DOES IT GET THERE.

export const initialState = {
  user: {},
}

export const getUser = (state = initialState) => state.user || initialState.user

// export const getUser = (state = initialState) => {
//   if (state.user) {
//     return Object.assign({}, state, {
//       formData: {
//         firstName: state.user.firstName,
//         lastName: state.user.lastName,
//         email: state.user.email,
//         phone: state.user.phone,
//       },
//     })
//   }
//
//   return initialState.user
// }

Code help is amazing, clear explanations get my unwavering affection and gratitude + a mention in my autobiography.

Community
  • 1
  • 1
megkadams
  • 717
  • 1
  • 10
  • 19
  • Can you clarify the question a bit? Does it mostly boil down to "where and when do I use selector functions" ? – markerikson Apr 20 '17 at 20:28
  • Eesh, I know. I wrote and re-wrote this question 10 times trying to get to the root aside from 'general confusion.' Yes, when and where do I use selector functions and how would I use them in this specific instance of separating out a bit of form data? – megkadams Apr 20 '17 at 20:30
  • You need to Read every answer of this question - http://stackoverflow.com/q/35411423/1477051 – Sunny R Gupta Apr 23 '17 at 11:14

1 Answers1

3

Selector functions can and should be used anywhere you want to extract specific pieces of data from the store. Think of them as "queries into your state tree", kind of like an SQL query into a database.

They are most commonly used in your mapStateToProps functions to extract data needed by components, but also in places where you need to run conditional logic based on store contents (such as thunk action creators or sagas). You can even use them in reducers if you want.

Selectors are usually created using the reselect library, which memoizes inputs. That means that the final provided "output" function will only run if the inputs actually change. A simple example might be:

import {createSelector} from "reselect";

const selectUser = state => state.user;

const selectUserFirstName = createSelector(
    selectUser,
    user => user.firstName
);

const selectUserLastName = createSelector(
    selectUser,
    user => user.lastName
);

const selectUserFullName = createSelector(
    [selectUserFirstName, selectUserLastName],
    (firstName, lastName) => firstName + " " + lastName
);

In this example, the "firstName + lastName" selector would only run if one of the two inputs has actually changed. Otherwise, it would return the previous cached result value. This then might be used as:

const mapState = (state) => {
    const userFullName = selectUserFullName(state);

    return {userFullName};
}

For more information on selectors, please see the Redux Techniques#Selectors and Normalization section of my React/Redux links list. I also make use of selectors in my Practical Redux tutorial series. (As a related note, you may want to read Practical Redux Part 8: Form Draft Data Management, or some of the articles in the React and Forms section of my links list.)

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • SO appreciate the note and excited to read up. Not sure why ARc (https://github.com/diegohaz/arc/tree/redux) isn't using reselect out of the box - are there any reasons you can think of to not be including the library I should watch out for? – megkadams Apr 20 '17 at 21:16
  • Suppose it's just a question of what libs they chose to include with the boilerplate. Given that they include redux-saga, I'm just a bit surprised they didn't also include reselect. But, absolutely no reason to _not_ include it. It's a small lightweight lib, and widely used with Redux. Also, feel free to drop by the Reactiflux chat channels at https://www.reactiflux.com and ask more questions. I hang out there evenings US time (@acemarke), and there's always many others happy to answer questions as well. – markerikson Apr 20 '17 at 21:23
  • Will be checking out immediately! Thank you! :) – megkadams Apr 20 '17 at 21:37
  • Sure! (And since this _is_ SO, I am now legally required to ask you to accept my answer :) ) – markerikson Apr 20 '17 at 21:46
  • 1
    And I am legally required to give you a shout out in my autobiography. Let's hope I ever do something important enough to warrant writing one. – megkadams Apr 20 '17 at 21:50