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.
Related
I have a component which is a form which I use to create records in my database. I also want to be able to pass into this component values with which to populate the form, allowing me to then update my existing database records. Straightforward add/edit functionality from the same component.
The following code should explain how I am doing this. The media prop is an object containing the data. I have this data already in the parent element so setting the values here is fine and they pass thru without problem. However once the page is loaded the 3rd init argument of useReducer never re-triggers, and therefore my state cannot be overridden with the values passed down in the media prop. Is there a correct way to make the init function trigger when the props are updated, or is my issue architectural?
const MediaUploadForm = ({ media }) => {
const init = (initialState) => {
if (media) {
// ... here I extract the values I need and override the initialState where required
} else {
return initialState
}
}
const [formState, dispatch] = useReducer(
MediaFormReducer,
initialState,
init
)
So using the new React hooks features and keeping the component functional allows me to use useEffects() This is similar to using a componentDidUpdate type event. So the following code allows me to check for the status of a prop (media) and then dispatch an action that sets my redux state.
useEffect(() => {
if (media && id !== media.id) {
dispatch(loadMedia(media))
}
})
Thanks to #sliptype for pointing me in the right direction
Copying props into state is considered an anti pattern in React. Props changes do not trigger reinitialising state, as you have seen.
This is described in https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html.
From the recap it looks like the current suggested solution matches
Alternative 1: To reset only certain state fields, watch for changes in a special property (e.g. props.userID).
This is an alternative, rather than the recommendation.
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recap
Hope this link gives you more information around the topic, and the recommendations there help in future work.
I am refactoring some code and turning my class components into function components as a way of learning how to use Hooks and Effects. My code uses Redux for state management and axios for database requests with Thunk as middleware for handling asynchronicity. I'm having an issue in one component that does a get request to retrieve a list of customers on what used to be componentDidMount. No matter what I try, the useEffect function gets into an infinite loop and continues requesting the customer list.
The component in question, CustomersTable, gets a list of customers from the database and displays it in a table. The component is wrapped by a container component that uses Redux's connect to pass in the retrieved list of customers to the CustomersTable as a prop.
useEffect(() => {
loadCustomers(currentPage, itemsPerPage, sortProp, (ascending ? 'asc' : 'desc'), {});
}, []);
loadCustomers is a Redux action that uses axios to fetch the customer list. currentPage, itemsPerPage, sortProp and ascending are state variables that are initialized to specific values on 'component mount'
I would expect that because I use the empty array that this would run only once. Instead it runs continuously. I can't figure out why this is happening. My best guess is that when redux gets the list, it returns a new object for state and therefore the props change every time, which then triggers a re-render, which then fetches a new list. Am I using this wrong in that Redux isn't meant to be used with hooks like this?
I ended up getting this working by adding the following:
useEffect(() => {
if (!list.length) {
loadCustomers(currentPage, itemsPerPage, sortProp, (ascending ? 'asc' : 'desc'), {});
}
}, []);
I'm not sure this is the behavior I truly want though. If the list of customers was truly 0, then the code would continue to fetch the list. If the list were truly empty, then I would want it to fetch only once and then stop. Edit: Turns out this definitely doesn't work. It works for the initial load, but breaks the code for any delete or edit.
OK, providing more context here. The container component that wraps the CustomersTable is:
import { connect } from 'react-redux';
import loadCustomers from './actions/customersActions';
import { deleteCustomer } from './actions/customerActions';
import CustomersTable from './CustomersTableHooks';
function mapStateToProps(state) {
return {
customers: state.customers,
customer: state.customer
};
}
export default connect(mapStateToProps, { loadCustomers, deleteCustomer })(CustomersTable);
The action, loadCustomers is:
export default function loadCustomers(page = 1, itemsPerPage = 50, sortProp = 'id', sortOrder = 'asc', search = {}) {
return (dispatch) => {
dispatch(loadCustomersBegin());
return loadCustomersApi(page, itemsPerPage, sortProp, sortOrder, search)
.then(data => dispatch(loadCustomersSuccess(data)))
.catch(() => dispatch(loadCustomersFailure()));
};
}
the reducer for customers is:
export default function customersReducer(state = initialState, action) {
switch (action.type) {
case types.LOAD_CUSTOMERS_BEGIN:
return Object.assign({}, state, { isLoading: true, list: [], totalItems: 0 });
case types.LOAD_CUSTOMERS_SUCCESS:
return Object.assign({}, state, { isLoading: false, list: action.customers || [], totalItems: action.totalItems });
case types.LOAD_CUSTOMERS_FAILURE:
return Object.assign({}, state, { isLoading: false, list: [], totalItems: 0 });
default:
return state;
}
}
I unfortunately can't post much of the CustomersTable itself because things are named in a way that would tell you what company I'm working for.
So, if i understand your code correctly, you are dispatching the loadCustomers action in child component within useEffect but you read actual data in parents mapStateToProps.
That would, of course, create infinite loop as:
parent reads customers from store (or anything from the store, for that matter)
renders children
child fetches customers in useEffect
properties on parent change and cause re-render
whole story goes on forever
Moral of the story: don't dispatch from presentational components. or, in other words, dispatch an action from the same component you read those same properties from store.
On every render you get new customer object because mapStateToProps if doing shallow equal. You could use memoized selectors to get customers, and it won't rerender when is not needed to.
import { createSelectorCreator, defaultMemoize } from 'reselect';
const createDeepEqualSelector = createSelectorCreator(defaultMemoize, deepEqual);
const customerSelector = createDeepEqualSelector(
[state => state.customerReducer.customers],
customers => customers,
);
I disagree with the most voted answer here.
properties on parent change and cause re-render
whole story goes on forever
A re-render will not call the function argument of useEffect when the 2nd argument of dependencies is and empty array. This function will only be called the 1st time, similar to the life cycle method componentDidMount. And it seems like this topic was created because that correctly expected behavior wasn't occurring.
It seems like what is happening is that the component is being unmounted and then mounted again. I had this issue and it brought me here. Unfortunately, without the component code we can only guess the actual cause. I thought it was related to react-redux connect but it turns out it wasn't. In my case the issue was that someone had a component definition within a component and the component was being re-defined / recreated on every render. This seems like a similar issue. I ended up wrapping that nested definition in useCallback.
I'm having view with list of Steps. Each step can be "viewed" and "removed" from within the "view" screen.
My StepDetails component binds to redux store by fetching corresponding step from steps part of store with simple steps.find(...):
const mapStateToProps = (state, ownProps) => {
let stepId = ownProps.params.stepId;
let step = state.steps.find(s => s.id === stepId);
return {step};
};
Now when (from within "details") I hit "Delete step" I want this step to be removed from store and I want to navigate to list view.
There is redux action invoked on delete that returns new steps list without this one removed and redirect is called afterwards:
const deleteStep = (stepId) => {
return dispatch => {
return stepsApi.deleteStep(stepId).then(steps => {
dispatch(action(STEPS_LIST_CHANGED, {steps}));
// react-router-redux action to redirect
dispatch(redirectToList());
})
}
};
This is fine and does what I want, with one drawback: when STEPS_LIST_CHANGED action is called and step gets removed from the list, my component's mapStateToProps gets called before this redirect. Result is that mapStateToProps cannot obviously find this step anymore and my component gives me errors that step is undefined etc.
What I can do is to check if step provided to component is defined, and if not render nothing etc. But it's kind of defensive programming flavor I don't really like, as I don't want my component to know what to do if it gets wrong data.
I can also swap actions dispatch order: redirect first and then alter state, but then it doesn't feel right too (logically you first want to delete and then redirect).
How do you handle such cases?
EDIT:
What I ended up with was to put this null/undefined-check into container component (one that does redux wiring). With that approach I don't clutter my presentational components with unnecessary logic. It also can be abstracted out to higher order component (or ES7 decorator probably) to render null or <div></div> when some required props are not present.
I can think of two approaches:
Delegating the list changed to the redirect? For example:
dispatch(redirectToList(action(STEPS_LIST_CHANGED, {steps})));
Handling the null step component to ignore rendering:
shouldComponentUpdate: function() {
return this.state.steps != null;
}
You can change your return statement into an array
const mapStateToProps = (state, ownProps) => {
let stepId = ownProps.params.stepId;
let step = state.steps.find(s => s.id === stepId);
return step ? [{step}] : []
};
This way on your component you can do a step.map() and render it.
The problem you are facing is that you want an atomic action that will do both the deletion and redirect. This is not possible because the Redux store and URL are two separate states that cannot be updated in one run.
As a result, the user can see for a brief moment either:
an empty entity detail page before the redirect or
the deleted entity in the list after redirect.
One way to deal with that is to use React.memo (or shouldComponentUpdate) and prevent component re-render after entity has vanished:
const entityWasDeleted = (prevProps, nextProps) =>
prevProps.entity !== undefined && nextProps.entity === undefined
export default React.memo(EntityDetailComponent, entityWasDeleted)
It's a little bit hacky, because React.memo should not be used like this, but the component gets unmounted in the next step so who cares.
I've started using Redux with React and I absolutely love it. However, the problem I'm having currently is that besides state, I also have more information I need to store/use throughout my application.
In this specific case I have a model with state that's fetched from an API. This model also has some info about itself, e.g. how you display a property on the screen "name" => "Name of the blabla". I understand how to work with state using Redux, but I'm have trouble seeing what do with this other info that I still need propagated throughout the application but is not actually state.
According to Redux, the State is the only "source of truth". And it should not have duplication (which would lead to inconsistencies).
So your state should store the name, but not the computed label property.
Indeed, "Name of the blabla" is a function (in the mathematical sense) of your Name value, and if they differ (for example, if at some point name === 'foo' but the label is 'Name of the bar' instead of 'Name of the foo'), then you have a problem...
So what I would do, is just store the minimum in your state (name in that case), and compute the label directly in the Component, where you need it.
If you need that to be re-used, then create a Component that only does take your name as a prop, and render a string with "Name of the blablaba" (if name = blabla I suppose).
If you need more complex computation (say you have multiple labels, date calculations etc.), you can always create a function that takes your State in input, and spit out your "Model" in output with everything calculated.
Redux is very functional in nature, so you might as well embrace it :)
I know I'm kind of late to the party but I thought someone might use the answer. What has work for me this far after working with React for some years now is to have a structure that is sort of like this:
State: which sets the structure (or 'schemas') of my data.
Actions: make changes to this state. These actions can handle sync or async operations.
Sagas: they handle async actions.
Selectors: they handle the structure of the data that I need for the view/for the API.
Constants: other data that won't change through time and that makes no sense to add to my state.
So having said that the flow of my apps is something like this:
An ACTION is dispatched => If that action is async a SAGA is listening and it performs the fetch operation => This saga saves updates to the STATE => [React components layer from now on] => If my view needs the data from my state in a different format for whatever reason I send it through a SELECTOR which will change that format => Then I attach this new parsed data to my container component.
An other flow could be one in which you need static data that is not in your state. In that cause I would save it in an object in a separate file and would import it to my container component directly (I never import anything in my children/presentational components directly. Only other components. The data is handled in a separate layer than the components).
A third kind of flow I can think of right now is when you need to make a POST to your API and for whatever reason the data in your state needs some parsing before doing so. In that case I would do the same that in the first example but the other way around: dispatch an ACTION => that is handled by a SAGA => before doing the fetch I would bring my data already structured for my POST (sagas has a method called select to help you use selectors here) => then I would perform the async operation => update the state accordingly.
Just in case you don't know what I mean by selectors or sagas some links here:
Sagas: https://github.com/yelouafi/redux-saga
Selectors: https://github.com/reactjs/reselect
I think models are as necessary for a Redux based app as for any other system.
Models are the vocabulary of a system. Models bring sanity to the codebase. Without them a codebase looks like a series of insane distorted thoughts.
You can use state functions to fill in the need of models in ReactJS+Redux apps.
Just like models hold data and methods, these objects hold only the functions that can be applied to state.
Read here : https://medium.com/#nshnt/state-functions-for-modeling-with-redux-a9b9d452a631.
Here is the famous Redux TODO app example with state functions :
todo_reducer.js :
import TODO from './todo_state';
const todoListReducer = (state=TODO.initialState(), action)=>{
switch(action.type){
case 'ADD_TODO' :
return TODO.addTask(state, action.taskName);
case 'FINISHED_TODO':
return TODO.setFinished(state, action.taskID );
case 'PENDING_TODO':
return TODO.setPending(state, action.taskID );
default :
return state;
}
};
export default todoListReducer;
todo-state.js :
export default {
initialState: () => [],
addTask: (todoList, name)=> todoList.concat({id: todoList.length, name: name}),
setFinished: (todoList, taskId) => (
todoList.map(task=> task.id === taskId ? {...task, complete: true} : task)
),
setPending: (todoList, taskId) => (
todoList.map(task=> task.id === taskId ? {...task, complete: false} : task)
),
pending: todoList=> todoList.filter(task=> !task.complete)
};
I also use these state functions in component, if component need some manipulation of state.
todo_list.js :
import React from 'react';
import {connect} from 'react-redux';
import TODO from './todo_state';
const TodoList = ({tasks, showCompletedTasks, toggleTodo})=> {
const toListElement = (task) => (
<li key={task.id}>
<input type="checkbox" checked={task.complete} onChange={(e)=> toggleTodo(task)}/>
<label>{task.name} {task.complete ? "Complete" : "Pending"}</label>
</li>
);
const visibleTaskList =
(showCompletedTasks ? tasks
: TODO.pending(tasks)).map(toListElement);
return (
<ul className="todo-list">
{visibleTaskList}
</ul>
);
}
.....
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
Use Reselect
Reselect is a simple library that sits in your app. It's primary function is to aggregate data from your redux store.
Create a reselect function
Taken from https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c
import { createSelector } from 'reselect'
// selector
const getBar = (state) => state.foo.bar
// reselect function
export const getBarState = createSelector(
[ getBar ],
(bar) => bar
)
The idea is that you connect your component with redux-connect or map state to props but instead of using the store directly you pass the store to a selector. This selector will have a function that lets you aggregate data or transform it any way you like.
import React from 'react'
import { connect } from 'react-redux'
import { getBarState } from '../selectors'
const mapStateToProps = (state) => {
return {
bar: getBarState(state)
}
}
The advantage of this approach is that you can reuse a selector on any component quite easily. You manipulate your data before it ever reaches a component (Separation of concerns principal). This gives you 2 big advantages.
Firstly, your redux store can remain unpolluted with duplicate or calculated data.
Secondly, your components can be built to expect data structures that immediately make sense to them.
Conclusion
Reselect helps your React apps become more structured.
I am using react-router and redux in my latest app and I'm facing a couple of issues relating to state changes required based on the current url params and queries.
Basically I have a component that needs to update it's state every time the url changes. State is being passed in through props by redux with the decorator like so
#connect(state => ({
campaigngroups: state.jobresults.campaigngroups,
error: state.jobresults.error,
loading: state.jobresults.loading
}))
At the moment I am using the componentWillReceiveProps lifecycle method to respond to the url changes coming from react-router since react-router will pass new props to the handler when the url changes in this.props.params and this.props.query - the main issue with this approach is that I am firing an action in this method to update the state - which then goes and passes new props the component which will trigger the same lifecycle method again - so basically creating an endless loop, currently I am setting a state variable to stop this from happening.
componentWillReceiveProps(nextProps) {
if (this.state.shouldupdate) {
let { slug } = nextProps.params;
let { citizenships, discipline, workright, location } = nextProps.query;
const params = { slug, discipline, workright, location };
let filters = this._getFilters(params);
// set the state accroding to the filters in the url
this._setState(params);
// trigger the action to refill the stores
this.actions.loadCampaignGroups(filters);
}
}
Is there a standard approach to trigger actions base on route transitions OR can I have the state of the store directly connected to the state of the component instead of passing it in through props? I have tried to use willTransitionTo static method but I don't have access to the this.props.dispatch there.
Alright I eventually found an answer on the redux's github page so will post it here. Hope it saves somebody some pain.
#deowk There are two parts to this problem, I'd say. The first is that componentWillReceiveProps() is not an ideal way for responding to state changes — mostly because it forces you to think imperatively, instead of reactively like we do with Redux. The solution is to store your current router information (location, params, query) inside your store. Then all your state is in the same place, and you can subscribe to it using the same Redux API as the rest of your data.
The trick is to create an action type that fires whenever the router location changes. This is easy in the upcoming 1.0 version of React Router:
// routeLocationDidUpdate() is an action creator
// Only call it from here, nowhere else
BrowserHistory.listen(location => dispatch(routeLocationDidUpdate(location)));
Now your store state will always be in sync with the router state. That fixes the need to manually react to query param changes and setState() in your component above — just use Redux's Connector.
<Connector select={state => ({ filter: getFilters(store.router.params) })} />
The second part of the problem is you need a way to react to Redux state changes outside of the view layer, say to fire an action in response to a route change. You can continue to use componentWillReceiveProps for simple cases like the one you describe, if you wish.
For anything more complicated, though, I recommending using RxJS if you're open to it. This is exactly what observables are designed for — reactive data flow.
To do this in Redux, first create an observable sequence of store states. You can do this using rx's observableFromStore().
EDIT AS SUGGESTED BY CNP
import { Observable } from 'rx'
function observableFromStore(store) {
return Observable.create(observer =>
store.subscribe(() => observer.onNext(store.getState()))
)
}
Then it's just a matter of using observable operators to subscribe to specific state changes. Here's an example of re-directing from a login page after a successful login:
const didLogin$ = state$
.distinctUntilChanged(state => !state.loggedIn && state.router.path === '/login')
.filter(state => state.loggedIn && state.router.path === '/login');
didLogin$.subscribe({
router.transitionTo('/success');
});
This implementation is much simpler than the same functionality using imperative patterns like componentDidReceiveProps().
As mentioned before, the solution has two parts:
1) Link the routing information to the state
For that, all you have to do is to setup react-router-redux. Follow the instructions and you'll be fine.
After everything is set, you should have a routing state, like this:
2) Observe routing changes and trigger your actions
Somewhere in your code you should have something like this now:
// find this piece of code
export default function configureStore(initialState) {
// the logic for configuring your store goes here
let store = createStore(...);
// we need to bind the observer to the store <<here>>
}
What you want to do is to observe changes in the store, so you can dispatch actions when something changes.
As #deowk mentioned, you can use rx, or you can write your own observer:
reduxStoreObserver.js
var currentValue;
/**
* Observes changes in the Redux store and calls onChange when the state changes
* #param store The Redux store
* #param selector A function that should return what you are observing. Example: (state) => state.routing.locationBeforeTransitions;
* #param onChange A function called when the observable state changed. Params are store, previousValue and currentValue
*/
export default function observe(store, selector, onChange) {
if (!store) throw Error('\'store\' should be truthy');
if (!selector) throw Error('\'selector\' should be truthy');
store.subscribe(() => {
let previousValue = currentValue;
try {
currentValue = selector(store.getState());
}
catch(ex) {
// the selector could not get the value. Maybe because of a null reference. Let's assume undefined
currentValue = undefined;
}
if (previousValue !== currentValue) {
onChange(store, previousValue, currentValue);
}
});
}
Now, all you have to do is to use the reduxStoreObserver.js we just wrote to observe changes:
import observe from './reduxStoreObserver.js';
export default function configureStore(initialState) {
// the logic for configuring your store goes here
let store = createStore(...);
observe(store,
//if THIS changes, we the CALLBACK will be called
state => state.routing.locationBeforeTransitions.search,
(store, previousValue, currentValue) => console.log('Some property changed from ', previousValue, 'to', currentValue)
);
}
The above code makes our function to be called every time locationBeforeTransitions.search changes in the state (as a result of the user navigating). If you want, you can observe que query string and so forth.
If you want to trigger an action as a result of routing changes, all you have to do is store.dispatch(yourAction) inside the handler.