I am trying to monitor the change in a redux store attribute by subscribing to the change using redux watch. For one attribute it works fine. For another, I am making an API request in the reducer (can't make async requests in the actions).
I have verified that the redux state does indeed get updated by creating a button to display the new information.
However, my component isn't actually subscribing to the change.
Does anyone know why this is?
import watch from 'redux-watch';
import {store} from '../index.js';
class ListResults extends Component {
constructor() {
super();
this.state = {
profiles: [],
};
}
componentDidMount() {
let w = watch(store.getState, 'searchResults');
store.subscribe(w((newVal, oldVal, objectPath) => {
// not getting called
console.log("Successfully got new searchResults")
}))
}
...
render() {
...
}
...
function mapStateToProps(state) {
return {
searchResults: state.searchResults,
};
}
export default connect(mapStateToProps)(ListResults);
Redux reducers must be pure functions!
... making an API request in the reducer (can't make async requests in the actions).
sounds very much like a incorrect reducer implementation.
In order to make async requests with redux you should look into redux middleware, like redux-thunk, redux-observables or redux-saga
Related
I have an app where after login, user details are acquired and some user profiles will have a metric setting and others will have a non-metric (imperial).
I want to set a boolean (isMetric) in React Context as there are stateless components all over that need to reference it and I want to avoid passing an isMetric prop all through the component hierarchy.
I've been following the React 16 examples at https://reactjs.org/docs/context.html and so I have a unit-context.js of:
export const UnitContext = React.createContext({
isMetric: true,
toggleUnits: () => {},
});
and an App.js of
import { UnitContext } from './unit-context';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleUnits = () => {
this.setState(state => ({
isMetric: !state.isMetric
}));
};
this.state = {
isMetric: true,
toggleUnits: this.toggleUnits,
};
}
render() {
return (
<UnitContext.Provider value={this.state}>
< ... />
</UnitContext.Provider>
);
}
}
The above is the dynamic pattern that provides toggleUnits in the context itself so that state (context) can be set.
With the above pattern, I've confirmed that in my components I can access the isMetric value fine.
But I use redux-saga to handle api calls and it is retrieving the user's metric / non-metric setting.
Once I've acquired the user details, how can I set Context? (redux-saga seems to have it's own context aspect but I'm referring to React Context).
One option is to import the UnitContext with
import { UnitContext } from '.unit-context';
and then access the Consumer to get the state and thus the function. But I'm unsure how to reference what would normally be a Component in a redux-saga saga which is imperative code.
Alternatively, could I move the App state to redux? That seems odd and unsound to have a function in a redux store. I know that redux-saga provides a select method to access redux store via selectors and I have used that in the past.
Update:
As the context is provided by <UnitContext.Provider value={this.state}> then conceivably I could replace that value property setting with a redux store value instead of using local state.
I'm working in a codebase that has a bunch of redux already working for managing and persisting state, as well as dispatching actions. My goal with the new Context API is to get rid of the prop-drilling that I have to do to deliver all these pieces of state to various components but keep the existing code for managing state in redux.
Now I've removed the excessive prop drilling code and replaced them with context Providers and Consumers, hooking them up in my components in all the right places. I'm still dispatching actions to redux and getting API responses populating my redux store, and I want to somehow notify the contexts to update when specific parts of the redux store update.
How do I get updates from the redux store delivered into my different Providers?
Since you are using the new Context Api, you can easily connect the component that uses the Context Provider with redux store and update the context values like
const Context = React.createContext();
class App extends React.Component {
state = {
user: ''
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.user !== prevState.user) {
return {user: nextProps.user}
}
return null;
}
render() {
return <Context.Provider value={this.state.user}>
<MyComponent />
</Context.Provider>
}
}
const mapStateToProps(state) {
return {
user: state.user
}
}
export default connect(mapStateToProps)(App);
I am facing some issue while using Dispatcher in ReactJS. So, I try to remove this dispatcher from the store and still store works well. Store properly hold my data and change event works well.
Now I am bit confusing to use dispatcher in our application.
Here is the code
MenuList is my component in which I call MenuStore.getMenuFromAPI() and after I also added onChange event of MenuStore.
class MenuList extends React.Component{
constructor(){
super();
this.state = {open:false, menuList:"",numbering:-1}
}
componentWillMount(){
var that = this;
MenuStore.getMenuFromAPI();
MenuStore.on("change", ()=> {
that.setState({menuList:MenuStore.getMenu()});
})
}
componentWillReceiveProps(nextProps){
if(nextProps.show!=this.props.show){
this.setState({open:nextProps.show});
}
}
render(){
const { classes } = this.props;
return (
<div>My MEnu</div>
)
}
}
MenuStore
class MenuStore extends EventEmitter {
constructor() {
super();
this.menu = null;
}
getMenu(){
return this.menu;
}
getMenuFromAPI(){
var that = this;
$.ajax({
type: "POST",
url: LinkConstants.GETMENU,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: "",
dataType :"json",
success: function(response) {
that.menu =response;
that.emit("change");
}.bind(this),
error: function(xhr, status, err) {
console.log(err);
}.bind(this)
});
}
// handleAction(action) {
// switch (action.type) {
// case ActionTypes.MENU: {
// this.getMenuFromAPI();
// break;
// }
// }
// }
}
const menuStore = new MenuStore;
//Dispatcher.register(menuStore.handleAction.bind(menuStore));
export default menuStore;
As you can see I commented out Dispatcher.register line and handleAction function.
Above code works properly fine but I wanted to know why to use Dispatcher over here ?
If I want to just store my data in the MenuStore and get it back from MenuStore on any of the component in the application. So it is necessary to use dispatchers and action or to just work with stores only.
Please clarify my doubts with proper example or case scenario (if possible) when to use dispatchers and action or when to work with stores only.
In your example your are not using Redux at all, you've just created a class that is used as a simple storage for the fetched data but your are not using any of Redux capabilities.
Redux is all about one store which is just a plain object which represents your application state tree. In order to change this state you dispatch actions. Actions are just simple objects which describe what happened. Each action has a type field which describes the action. Actions are treated by reducers which are functions that gets the current state and the dispatched action and decide on the next state of the application. This is Redux in few sentences.
Redux store has a method named dispatch which is used to dispatch actions. As mentioned in Redux documentation, this is the only way to trigger a state change.
Lets say we have a TODO list application. Our store may be represented as an array of strings (todo items).
To add a new item to the list we will define a simple action:
const addItemAction = (item = '') => ({
type: 'ADD_ITEM',
data: item,
});
Dispatching this action can be done from within one of your component's methods which will be attached to some keyboard/mouse event:
class TodoList extends React.Component {
...
// addNewItem is called with a value from a text field
addNewItem(item) {
store.dispatch(addItemAction(item));
}
...
}
As I mentioned above, state is changed by a reducer function. The reducer decides if to change the state and how to change it. If dispatched action shouldn't change the state it can just return the received state:
function todoReducer(state = [], action) {
switch(action.type) {
case 'ADD_ITEM':
return [...state, action.data];
break;
default:
return state;
}
}
Reducer is passed to createStore method:
import { createStore } from 'redux'
const store = createStore(todoReducer);
In the TodoList component you can subscribe to the store using store.subscribe method which accepts a callback function that will be called each time the store state changes. When detecting a change you can call setState of your component to set the list on the component state and to cause the component to rerender:
class TodoList extends React.Component {
....
componentDidMount() {
this.storeSubscription = store.subscribe((state) => {
// For the example I'm just setting the state (list of todos)
// without checking if it changed or not
this.setState({
todos: state,
});
});
}
render() {
return this.state.todos.map(todo => <div>{todo}</div>);
}
....
}
This is an almost complete example of using Redux. We used action to describe an event in our application, we used the store's dispatch method to dispatch the action to the store, Redux will invoke the reducer when it gets new actions, the reducer computes the new state and our component detects the change by using the store's subscribe method.
There are more things to consider and to take care of in a more complex application. You will probably have a more complex state tree so you will need additional reducers to take care of computing the state. Additionally in some step you would need to consider working with some helpers to reduce overhead of subscribing to state change and detecting changes.
In a more complex application you would probably connect your component to the store by a binding library such as react-redux so your component will receive the relevant parts of the store by props which will save the overhead of subscribing to the store changes and deciding on when to rerender the component.
I would recommend watching "Getting started with Redux" by Dan Abramov to get some more understanding of what is Redux and how to work with it.
Getting started with Redux
I'm still new to react/redux, after getting something like this to function
User.js
class User extends React.Component {
componentWillMount() {
this.props.fetchUser(.....);
}
render() {
return (
<Profile />
)
}
export default connect(null, {fetchUser})(User);
Profile.js
class Profile extends React.Component {
render() {
const { user } = this.props
return (
<h1>{user.profile.name}</h1>
)
}
const mapStateToProps = state => ({
user: state.store.user
});
export default connect(mapStateToProps, {})(Profile)
actions.js
export const fetchUser = (.....) => dispatch => {
fetch()
.....
}
reducers.js
case FETCH_USER:
return {
...state,
user: action.payload.user
};
As I understand it, the User component calls an action (fetchUser) from connect on componentWillMount(). That action calls an api, gets the data and the reducer adds that to the store within the state. The Profile component can then use connect to map the data from fetchUser in the store and display that data.
After reading some tutorials including https://github.com/reactjs/redux/blob/master/docs/basics/UsageWithReact.md
It looks like things can be simplified a bit without using classes.
If I were to change the User and Profile components to a more functional way, how would I do it?
eg.
const User = () => {
return (
<Profile />
)
}
how do I dispatch the fetchUser action and how do I simulate it to be called with the flow of componentWillMount()?
or am I just over complicating things?
There is also a way to support lifecycle methods in functional components.
https://www.npmjs.com/package/react-pure-lifecycle
import React from 'react';
import lifecycle from 'react-pure-lifecycle';
// create your lifecycle methods
const componentDidMount = (props) => {
console.log('I mounted! Here are my props: ', props);
};
// make them properties on a standard object
const methods = {
componentDidMount
};
const FunctionalComponent = ({children}) => {
return (
<div>
{children}
</div>
);
};
// decorate the component
export default lifecycle(methods)(FunctionalComponent);
I think you should keep using statefull components with redux...
https://medium.com/#antonkorzunov/2-things-about-purecomponent-you-probable-should-know-b04844a90d4
Redux connect — is a PureComponent.
Yes — a very important thing, a HoC for a molecule is a pure one. And works even inside other pure components. And gets store from a current context.
Same is working, for example, for styled-component — you can wrap it with PureComponent, but it will still react to Theme changes.
Solution is simple — bypass logic, use old school events bus, subcribe, wait and emit events.
Styled-componets:
componentWillMount() {
// subscribe to the event emitter. This
// is necessary due to pure components blocking
// context updates, this circumvents
// that by updating when an event is emitted.
const subscribe = this.context[CHANNEL];
this.unsubscribe = subscribe(nextTheme => { <----- MAGIC
React-redux:
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe =
this.store.subscribe(this.handleChange); <----- MAGIC
}
}
componentDidMount() {
this.trySubscribe();
}
Thus, even if parent Pure Component will block any update enables you to catch a change, store update, context variable change, or everything else.
So — something inside pure components is very soiled and absolutely impure. It is driven by side effects!
But this bypass straight logic flow, and works just differently from the rest of application.
So — just be careful. And don’t forget about magic.
Aaaand….
And this is a reason, why any redux store update will cause redraw in each connected component, and why you should use reselect just next to connect HoC —
to stop unnecessary change propagation.
But you should read this from another point of view:
redux-connect is a source of a change propagation.
redux connect is the end of a change propagation. It is still PureComponent.
And this leads to quite handy thing — you can control change propagation with redux-connect only. Just create a boundaries for a change. Lets talk about this in another article.
Conclusion
Pure components keep your application fast. Sometimes — more predictable, but often — less predictable, as long they change the way application works.
Stateless components are not pure, and may run slower than PureComponents by any kind.
But… if you very wish to create a fast application with good user experience — you have to use Pure Component.
No choice. But, now — you know hidden truth, and knew some magic…
React recommends that ajax request be made in componentDidMount(), rather than in componentWillMount(). For more info on this, read this post.
Since you want to make ajax requests in componentDidMount(), you need a class. There are two ways of writing component definitions: functional component and the class component. Functional components are more concise, but you don't get component lifecycle methods like componentDidMount(). Think of it as just a render function that takes props as inputs and outputs DOMs (in JSX). To override those lifecycle methods, you need to define them as a class.
If you want to use Redux, and want to make ajax requests in a Redux action, you should import the action creator function (fetchUser(..) in your case) that makes the ajax request, and dispatch(fetchUser(..)) in componentDidMount(). connect(..)ed components get dispatch(..) function passed to it by Redux store.
If you want to see how it's done in other redux apps, see the official example apps in the redux.js repo, paying attention to actions and containers: https://github.com/reactjs/redux/tree/master/examples
In Your case you can continue with statefull components no wrong in that
,If you need to go with functional way
There is a work arround
https://github.com/mobxjs/mobx/issues/162
Suggestion
Calling the api in componentDidMount will make sense than
componentWillMount , Because you can show the user something is
fetching.
I think,User component is designed nicely.It will act as a container for Profile to provide the Data.
Instead of making Profile component class oriented,it should be Stateless.
Lets User component pass the required data for Profile component.
You don't need to connect Profile component using redux-connect.Just render it as a Child component of User.
Profile
const Profile = (props) => {
const {user, likeProfile} = props;
//likeProfile()//call like this using dom event or programmatically.
return (
<h1>{user.profile.name}</h1>
)
}
You need to make some changes in User component.
Get the state for Profile component via mapStateToProps.
class User extends React.Component {
componentWillMount() {
this.props.fetchUser(.....);
}
render() {
const {user, likeProfile} = this.props;
return (
<Profile user= {user} likeProfile={likeProfile} /> //passed the user data to Profile component vua User
)
}
Map the user state for Profile in User connect.
const mapStateToProps = (state)=>{
return{
user : state.somereducerkey.user //this will be accessible in Profile via props { user}
}
}
export default connect(mapStateToProps, {fetchUser, likeProfile})(User);
I am using Redux to create my pagination. My problem is, in constructor I ran a method that will parse the url and check if there is anything about pagination. Then it runs an action that will put the data in my store. It all runs smoothly.
Then, I have the componentDidMount method when I run another action - fetching data. And there I use those props I have previously pushed. Unfortunately, the store is at its initial state.
My (simplified) code:
class NewsList extends React.Component {
constructor(props) {
super(props);
this.profile = document.getElementById('articles').getAttribute('data-profile');
this.parseHash();
}
parseHash() {
/* Method for parsing navigation hash
---------------------------------------- */
const hash = window.location.hash.replace('#', '').split('&');
const data = {
page: 1,
offset: 0
};
// hash parsing
this.props.setPagination(data);
}
componentDidMount() {
this.loadNews();
// If I use
// setTimeout(() => { this.loadNews(); })
// it works, but I feel this is hack-ish
}
loadNews() {
this.props.update({
current: {page: this.props.NewsListReducer.current.page, offset: this.props.NewsListReducer.current.offset},
next: {page: this.props.NewsListReducer.next.page, offset: this.props.NewsListReducer.next.offset}
});
}
}
When I console.log(this.props.NewsListReducer), I am getting undefined for both current and next object, but when I use Redux DevTools, the data is there. What can I do?
It seems like there's some asynchronicity somewhere in there. You're probably using react-redux right? I think the asynchronicity comes from the connected component, as it uses setState when the store state has changed. setState schedules an asychronous state update.
Therefore this.props.NewsListReducer isn't up to date in componentDidMount().
I guess this.props.update is an action that will fetch the news, right? Why is it necessary that you provide the paging information from the component to it? E.g. with redux-thunk you can access the store state before dispatching an action. This could be your chance for reading the (up to date) paging information.
E.g.
export function fetchNews() {
return (dispatch, getState) => {
getState().NewsListReducer.current.page; // Up to date
...
fetch(...).then(() =>
dispatch({...}));
}
}
Btw. it could be a good idea to not call your state *Reducer. It's the reducer managing the state, but the reducer isn't part of the state in that manner.