In my application i have many part of the state that is significative only when the user is logged in.
When the user is logged in and navigate directly to a page, router display the page but have to make async call, so a piece of the state is not yet populated.
Now, suppose i have to show a button on navigation bar that have to take a part of the state, but this state is not populated since the async call finis.
More pratically, i have this selector:
export const getSelectedCustomer = state => state.systems.selectedCustomer;
With this, there are no problem, because selectedCustomer is part of the initialState reducer
The problem is in this selector:
export const getSelectedCustomerName = state => {
return state.systems.entities[getSelectedCustomer(state)].customer;
}
The part of the state entities in initialState is an empty array and will be populated on async call.
So, when the application start and i map (with connect - mapStateToProps) the selector getSelectedCustomerName i get the error because entities is empty and sure, customer field do not exist yet
A simple solution if to take in place some if-else, but i will have to do this in "every" part of the application.
I'm asking if there is a better solution to avoid this...
const NullCustomerEntities = {
customer: "Anonymous"
}
export const getSelectedCustomer = state => state.systems.selectedCustomer;
export const getCustomerEntities = state => {
return state.systems.entities[getSelectedCustomer(state)] || NullCustomerEntities;
}
export const getSelectedCustomerName = state => {
return getCustomerEntities(state).customer;
}
This is a classic example of a Null Object pattern
(hope you don't mind the code being in Ruby).
Related
I have a api call which takes in a varable from the state in my actions which goes to an axios get and it passes it there. I know this because if I console the variable in the axios get its there which goes to a route and to the controller but when I console loge the request in the controller its empty. I am trying to do a find() to a specific email. If I hard code it then it works perfectly which means my variable probably isn't passing there and I don't know why. I have a post that works perfectly
my action
export const getUser = (currentUser) => {
return(dispatch, getState) => {
API.getUserInfo({
emailaddress:currentUser.emailaddress,
password: currentUser.password
})
.then(res => {
dispatch({type:"USER_PROFILE",userPro:res.data})
})
.catch(err => console.log(err));
}
}
reducer
const initState ={
userProfile:[]
}
const userReducer = (state = initState,action)=>{
switch(action.type){
case "CREATE_USER" :
console.log("created User", action.newProfile)
return state;
case "USER_PROFILE":
console.log("User", action.userPro)
return {
userProfile: [...state.userProfile,action.userPro]
}
default:
return state;
}
}
root reducer
const rootReducer = combineReducers({
authR: authReducer,
userR:userReducer
})
mapstatetoprops
const mapStateToProps = (state)=>{
console.log(state)
return{
userInfo:state.userR.userProfile
}
}
export default connect( mapStateToProps ) (Layout);
Right, so you're now moving away from component state and into application state. This might end up being a long answer but in short I would suggest you read-up on the Redux and React-Redux documentation. https://redux.js.org/ https://react-redux.js.org/
Redux is all about persisting data in your application. Which is what you need if you want to take the data from one page and make it available for use in another page. It is essentially comprised of three parts:
Actions: Functions that are called to carry data from your components or APIs into a common place (reducers). The data is recognized as a "payload."
Reducers: Functions that are triggered by your actions. They use the "payload" to return a new app state (data).
Store: Just like the name suggests, it is an object that contains all your reducers; a collection of all your application states.
Now react-redux is simply middleware that let's your components communicate with your store.
There is some pretty standard mark-up to get this all to work. I'll show you examples with what I assume your code looks like.
So first let's define a reducer (a data maintainer for lack of better words) and lets store it in a file called authReducer.js
const authReducer = (state = {}, action) => {
switch(action.type){
CASE "SET_USER_CREDS":
return {
user: action.payload
}
default:
return state
}
}
export default authReducer
So digging into this code. We defined a function with two parameters, a state which we gave an initial value of {} and an action, which refers to the actions that get sent to this reducer. If there was an action with a type of "SET_USER_CREDS" then the reducer returns an object that will contain information on a user. As we can see, the only way it can get data is by consuming it from an action.
Now we need an action, a means to communicate with the reducer we just made. Let's create a file called authActions.js
export const recordUser = (userData) => {
return {
type: "SET_USER_CREDS":
payload: userData
}
}
Looks simple enough, we created a function that essentially is trying to meet the requirements of making our reducer to work. These action creators are actually used by our components, this is method in which we can get data from a component and keep it somewhere.
But wait, where do we keep this data? We talked about reducers, but where do they live? Well it's time to build our store.
store.js
import {createStore, combineReducers} from "redux"
import authReducer from "./authReducer"
const store = createStore(combineReducers({
auth: authReducer
}))
export default store
Alright we got a store now. Quick facts about the syntax. We used a few methods fromt the redux library. We used createStore() and we passed in combineReducers(), where the latter accepts an object. In the object we define a key-value pair for each reducer we want to put in our store. The key is typically the name/type of data the reducer is managing.
Cool, we've set up a store, a reducer and an action-creator. But as is, there is no way for React to communicate with your store. Well this is where react-redux comes in. In whereever you defined your react-router-dom routes, we'll need to make some modifications.
Let's just say this is your router file, you'll need to add the following.
import {Provider} from "react-redux"
import store from "./store"
<Provider store={store}>
//Routes
</Provider>
<Provider> is a component that accepts a redux-store as an argument. We can wrap our <Routes/> inside of it, thus providing the store to all our components.
Congrats, we're about 2 steps away from getting all this to work.
So now in your Header component, or wherever you're entering the user data you need to do a few things.
1) Bring in some dependencies.
import {connect} from "react-redux"
import {recordUser} from "./authActions"
2) Outside of your component. Define a function called mapDispatchToProps()
const mapDispatchToProps = (dispatch) => {
return {
recordUser: (userData) => {
dispatch(recordUser(userData))
}
}
}
In short, this is a function that will let us call your action-creators inside your component. The recordUser key is now an available prop inside your component.
3) Inside your component you need to define an event-handler to use our new prop. It will need to be triggered when the user is navigating to the other page.
handleRecordUser = () => {
const userData = {
email: this.state.email,
password: this.state.password
}
this.props.recordUser(userData)
}
So its doing as we promised, taking data from our component state and passing it off to an action creator. Remember, you need to call this event-handler to execute this action at the same time as the re-route to the new page. If you're using a <Link> to reroute, just do something like:
4) Modify the export of this component to use connect()
export default connect(null, mapDispatchToProps)(Header)
connect() gives our component access to methods like dispatch() which is a requirement to use your action-creators.
Last, but not least, consume your store.
In the component you routed to you need to do the following:
1) Bring in some dependencies.
import {connect} from "react-redux"
2) Outside your component, define a function called mapStateToProps()
const mapStateToProps = (state) => {
return {
auth: state.auth
}
}
mapStateToProps() let's you tap into the store state and enables you to choose which reducer data you want to bring into your component. state.auth should look familiar, since in our store.js file we defined a key-value pair of {auth: authReducer}. We're simply calling that key.
So by defining a key of auth, I'm now saying that I will have a prop in my component called this.props.auth and it's value will be the reducer ouput.
3) Lastly, connect your component
export default connect(mapStateToProps)(YourComponent)
Now you can utilize the data from your previous Login component by consuming the saved data in your redux store by making use of this.props.auth.
you can use redux over here or you can use the localstorage, cookie, sessions any one of these browser storage to set the values and when your component gets rendered you can retrieve this data from browser and make your API call. Though this is not the best approach but if you don't know how to use redux then you can apply this.
I have a react app (repo) that I want to use redux to store the state universally, so the root app can access it.
For example: one page has a GET API call that populates the page. That works fine and all, but I'm confused as to how to do a couple things.
How can I use variables in the redux action, to give the action say the ID of the model and have it return the model (API returns json).
How can I then pass that state up so that a higher ordered component (such as the base App.js) can access the state, so that I can use variables from the current page in the navigation.
What/when is the best way/time to update the redux state so that the changes reflect across anywhere using the redux state?
Specifically (in this project): If you are on localhost/spells/X with X being the model ID, how can I pass the state up from that page's container component (in this case LayoutSpellView) up to MaterialUIApp
index.js
|--App.js
|--MaterialUiApp
|--Router
|--LayoutSpellView (pass state up to MaterialUiApp)
With Redux you don't pass the state up or down. You update the global state with your action creators and reducers. Wherever you need to reach the state you connect your components to the state and use it. You have a store and it includes a global state. That global state may contain multiple different states.
You can use payload or any other name, variable with your action creator. In your reducer you can get those with action.payload, action.id, etc.
As I explained in the first paragraph, you update your state whenever you need. After that you connect any component to your state wherever you need.
There is no best time or best way to do that. This is up to your code and app logic.
Of course there are some best practices but we can't talk about them so broad. After you are getting involved with Redux you will see some of them around. For example I said "we don't pass up or down the state with Redux". This is true but sometimes to avoid so many connects around components we use container apps, connect that app to store (you reach state via store actually) and then pass the related state parts to the related components.
I recommend Redux's own documentation as starting point: https://redux.js.org/
To help you see the data flow, here's a sketch of how everything ties together. In my example code below, this is the data flow:
Clicking the "Load Comments" button dispatches a thunk with the parameter userId. (A thunk is an async action.)
The thunk uses the userId to make its async call, and then dispatches an action setComments(comments) with the received comments as its payload.
The Comments reducer catches that action and updates the Redux state with the comments array.
The Container to updates comments in mapStateToProps
The Component receives the updated comments, and displays them in the <ul>
// actions.js
export const SET_COMMENTS = "MyApp/setComments";
export const setComments = comments => ({
type: SET_COMMENTS,
payload: comments
});
// thunks.js
import { setComments } from './actions';
export const getCommentsAsync = id => dispatch => {
return axios
.get(`http://localhost:5000/comments/${id}`)
.then(comments => dispatch(setComments(comments)));
};
// reducer.js
import { SET_COMMENTS } from './actions';
const initialState = {
comments: []
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_COMMENTS:
const comments = action.payload;
return {
...state,
comments
};
default:
return state;
}
};
// components.js
export default function CommentsList({ comments, loadComments, userId }) {
return (
<div>
<ul>
{comments.map(comment => <li key={comment.id}>{comment.body}</li>)}
</ul>
<button onClick={() => loadComments(userId)}>Load Comments</button>
</div>
);
}
// containers.js
import { connect } from "react-redux";
import { getCommentsAsync } from "./thunks";
import CommentsList from "./components";
mapStateToProps = state => ({
comments: state.comments,
userId: state.user.id
});
mapDispatchToProps = {
loadComments: getCommentsAsync
};
export default connect(mapStateToProps, mapDispatchToProps)(CommentsList);
I'm building Multiple Select component for user to select the seasons on the post. So use can choose Spring and Fall. Here I'm using reselect to track selected objects.
My problem is that my reselect doesn't trigger one it renders at the first time. This question looks pretty long but it has many console.log() lines for clarification. So please bear with me! :)
('main.js') Here is my modal Component. Data for this.state.items is seasons = [{id: '100', value: 'All',}, { id: '101', value: 'Spring',}, { ... }]
return (
<SelectorModal
isSelectorVisible={this.state.isSelectorVisible}
multiple={this.state.multiple}
items={this.state.items}
hideSelector={this._hideSelector}
selectAction={this._selectAction}
seasonSelectAction={this._seasonSelectAction}
/>
('main.js') As you can see I pass _seasonSelectAction to handle selecting items. (It adds/removes an object to/from the array of this.state.selectedSeasonIds). selectedSeasonIds: [] is defined in the state. Let's look at the function.
_seasonSelectAction = (id) => {
let newSelectedSeasonIds = [...this.state.selectedSeasonIds, id];
this.setState({selectedSeasonIds : newSelectedSeasonIds});
console.log(this.state.selectedSeasonIds); <- FOR DEBUGGING
console.log(newSelectedSeasonIds); <- For DEBUGGING
}
I confirmed that it prints ids of selected Item. Probably providing code of SelectorModal.js is irrelevant to this question. So let's move on... :)
('main.js') Here is where I called createSelector
function mapStateToProps(state) {
return {
seasons: SelectedSeasonsSelector(state)
}
}
export default connect(mapStateToProps, null)(...);
(selected_seasons.js) Finally, here is the reselect file
import { createSelector } from 'reselect';
// creates select function to pick off the pieces of states
const seasonsSelector = state => state.seasons
const selectedSeasonsSelector = state => state.selectedSeasonIds
const getSeasons = (seasons, selectedSeasonIds) => {
const selectedSeasons = _.filter(
seasons,
season => _.contains(selectedSeasonIds, season.id)
);
console.log('----------------------');
console.log('getSeasons');
console.log(selectedSeasons); <<- You can see this output below
console.log('seaons');
console.log(seasons);
console.log('----------------------');
return selectedSeasons;
}
export default createSelector(
seasonsSelector,
selectedSeasonsSelector,
getSeasons
);
The output for system console is below
----------------------
getSeasons
Array []
seaons
undefined
----------------------
Thank you for reading this whole question and please let me know if you have any question on this problem.
UPDATE As Vlad recommended, I put SelectedSeasonsSelector inside of _renderSeasons but it prints empty result like above every time I select something. I think it can't get state.seasons, state.
_renderSeasons = () => {
console.log(this.state.seasons) // This prints as expected. But this becomes undefined
//when I pass this.state in to the SelectedSeasonsSelector
selectedSeasons = SelectedSeasonsSelector(this.state)
console.log('how work');
console.log(selectedSeasons);
let seasonList = selectedSeasons.map((season) => {
return ' '+season;
})
return seasonList
}
state.seasons and state.selectedSeasonsIds are getting undefined
Looks like you are assuming that this.setState will change redux store, but it won't.
In a _seasonSelectAction method you are calling this.setState that stores selected ids in container's local state.
However selectors are expect ids will be be stored in global redux store.
So you have to pass selected id's to redux store, instead of storing them in a local state. And this parts are looks missing:
dispatch action
use reducer to store this info into redux store.
add mapDispatchToProps handler to your connect
I'm guessing here, but it looks like confusing terms:component local state is not the same as redux store state. First one is local to your component and can be accessed through this.state attributive. Second is global data related to all of your application, stored in redux store and could be accessed by getState redux method.
I so you probably have to decide, whether to stay with redux stack or create pure react component. If pure react is your choice, than you dint have to use selectors, otherwise you have to dispatch action and more likely remove this.state.
A React component OilBarrel connected my redux store to create a container OilBarrelContainer:
// ---- component
class OilBarrel extends Component {
render() {
let data = this.props.data;
...
}
}
// ---- container
function mapStateToProps(state) {
let data = state.oilbarrel.data;
...
}
const OilBarrelContainer = connect(mapStateToProps)(OilBarrel)
// ---- reducer
const oilbarrel = (state = {}, action) => {
let data = state.data;
}
const storeFactory = (server = false, initialState = {}) => {
return applyMiddleware(...middleware(server))(createStore)(
combineReducers({oilbarrel, otherReducer1, otherReducer2}),
initialState
)
}
I find it strange that mapStateToProps() receives the top level state object (the entire state of the application), requiring me to traverse state.oilbarrel.data, when the reducer (conveniently) only receives the branch of the state that belongs to this component.
This limits the ability to reuse this container without knowing where it fits into the state hierarchy. Am I doing something wrong that my mapStateToProps() is receiving the full state?
That is the mapStateToProps behavior. You have to think redux state as a single source of truth (by the way, that is what it really is) independently of the components you have in project. There is no way out, you have to know the exactly hierarchy of you especific data in the state to pass it to your container component.
No this is intentional, because you may want to use other parts of the state inside your component. One option is to keep the selector (mapStateToProps) in a separate file from your component, which will help you reuse the selector, if you app is very large and complex you can also checkout libraries such as reselect which helps you make your selectors more efficient.
Dan Abramov offers a solution for this in his advanced redux course under Colocating Selectors with Reducers.
The idea is that for every reducer, there is a selector, and the selector is only aware of it's reducer structure. The selectors for higher level reducers, wrap the lower level reducer, with their part of the state, and so on.
The example was taken from the course's github:
In the todos reducer file:
export const getVisibleTodos = (state, filter) => {
switch (filter) {
case 'all':
return state;
case 'completed':
return state.filter(t => t.completed);
case 'active':
return state.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
In the main reducer file:
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
Now you can get every part of your state without knowing the structure. However, it adds a lot of boilerplate.
There are a few other hello world app questions with regard to react, but mine is specific to the reducer. Im not exactly sure what I should put in the reducer for my specific action.
Note*: I thought maybe i need to add a message: "" key value pair to my initial state and then declare a
var newState = Object.assign({}, state, {
message:"hello world"
});
into my if statement in the reducer, then dispatch it in the component, but that seems unneccesary since it should always print hello world, so hard coding seems more efficient. Hopefully there isn't too much clutter in this question as well.
Here is my component:
var HelloWorld = React.createClass({
helloWorld: function() {
this.props.dispatch(actions.printHello());
},
render: function() {
return (
<div className="HelloWorld">
<h1>Hello World!</h1>
</div>
);
}
});
var Container = connect()(HelloWorld);
module.exports = Container;
my action:
var $ = require("jquery")
var PRINT_HELLO = 'PRINT_HELLO';
var printHello = function() {
return {
type: GUESS_NUM
};
};
exports.PRINT_HELLO = PRINT_HELLO;
exports.printHello = printHello;
and reducer:
var actions = require('./actions');
var initialRepositoryState = {
type: null
};
var capstoneApp = function (state,action) {
state = state || initialRepositoryState;
if (action.type === actions.PRINT_HELLO) {
var newState = Object.assign({}, state, {
message:"hello world"
});
return newState;
}
};
I don't think you will need my index.js but I will provide if necessary.
Thanks in advance for input!
The reducer holds your state. So the way of thinking about it is "what are the parts of my program that can change?" And then from there, the next question is "What is the minimal amount of state needed to hold on to my program?"
From looking at your example, I get the impression that you are trying to build an application that sometimes displays "hello world" in the UI. Let me make a few more interactions to help describe all the pieces.
Initially, I'll help you create a program that has an empty label and a button. When you click on the button, it will display "hello world".
Okay, so to answer the first question: What can change? The app can either display "hello world" or nothing. We could store that in a couple of different ways. If the string is hard-coded, like you've alluded to above, then really you have a bool of show the message, or not.
And to the second question: one truthy value is pretty much the definition of a minimal state. So let's make a reducer:
var initialState = {
showMessage: false,
};
var reducer = function(state, action) {
var newState = Object.assign({}, state);
if (action.type === 'BUTTON_PRESS') {
newState.showMessage = !newState.showMessage;
}
return newState;
}
Okay, so now we've created a reducer that, when it gets the BUTTON_PRESS action, it flips the bit of its state.
Now, let's talk about how to hook that reducer up to redux. Right now, the reducer is a plain javascript function. We just need to pass that store into redux with the initial state. [createStore][1]
P.S. I normally write ES2015 code so there may be small typos in the commonJS import syntax
var redux = require('redux');
var store = redux.createStore(reducer, initialState);
The next part is to look at redux-react.
Redux-react is glue that works both ways in react. It connects data from the redux store to react props, and it connects react callbacks (such as a click) to redux actions.
So conceptually, it looks like this. You have a react component that has a button. When you click the button, we want to generate a 'BUTTON_PRESS' action. After this, your react component no longer cares what happens to BUTTON_PRESS. Its job is done. BUTTON_PRESS could do one of infinite things. Now, assume that redux-react does its job and gets the action passed to the reducer. The reducer computes its new logic and returns a new state. This new state has the value of showMessage. Then, redux-react does the other half of connecting and makes showMessage a prop for the component. Just to be clear, there is no explicit reason why the same react component has to respond to the state changed by your action. They could be different components.
To put it into bullet points, here is how the codeflow should work:
We create an initial store with showMessage = false
When creating the React component, we use connect to bind the showMessage to a prop, and to handle onClick of the button in the component to generate a 'BUTTON_PRESS' action.
Since showMessage is false, there is originally only a button present.
The user presses the button. React calls into the onClick handler
We use redux-react to dispatch a BUTTON_PRESS event
When redux gets an action, it calls the reducer with the current state and the action. The reducer is responsible for generating a new state in response to this action
The reducer sets showMessage to true
redux-react listens to store changes and when it changes it modifies the prop for the react component
The prop changes so react calls render()
Inside your render method you see that this.props.showMessage is true, so you display the message.
And here is how such a react component could be implemented. There are enough differences between React components in ES5 vs ES2015 that I'm just going to give you the ES2015 version and apologize again.
class HelloWorld extends Component {
render() {
const message = this.props.showMessage ? "Hello world!" : "";
return (
<div id="label">{message}</div>
<div id="button" onClick={this.props.handleOnClick}>Toggle the message </div>
);
}
}
HelloWorld.propTypes = {
showMessage: PropTypes.bool.isRequired,
handleOnClick: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
showMessage: state.showMessage,
});
const mapDispatchToProps = dispatch => ({
handleOnClick: dispatch({ type: 'BUTTON_PRESS', payload: '' })
});
export default connect(mapStateToProps, mapDispatchToProps)(HelloWorld);
You can keep reading the docs but I hope that helps explain what all the parts are. Let me know if anything isn't clear.
[1]: http://redux.js.org/docs/api/createStore.html