Redux dispatch before rendering - reactjs

I want to dispatch an action before the component is rendered.
But the action is an async action integrated with redux-saga.
I have to know when is the async action is done, if it is done, then render the component.
To make this work, I have a unique id for each container and after the action done, the attribute { loaded: true } would be saved into store.
I am thinking of this way
#preload('uniqueId', (dispatch) => new Promise((resolve, reject) => {
dispatch(MyAction(resolve, reject));
})
#connect(....)
class MyComponent extends React.Component {
....
}
#preload is a function trigger the specified action in componenWillMount (For server side) or componenDidMount (For client side) and when the action call resolve(), the state.preloadState.uniqueId.loaded will set to true. It also wrap the component so that only render the component when state.preloadState.uniqueId.loaded === true.
#connect connect the data I wanna preloaded into redux store in the specified action.
I wonder if it is common practice to do data preload for redux, redux-saga app and also for server-side rendering (I have used redux-async-connect before, but I wanna make all nested component able to do the data preload so I bind to componentWillMount instead of some static function).

Yes you can't really stop a component from rendering (without having its parent simply not render it), but you can conditionally render nothing. Something like:
render() {
if (!state.data) { return null; }
return <div>{state.data}</div>
}

Related

How should I mock state in a functional stateful component with state hooks when testing?

If I have a stateful functional component that uses state hooks, how can I manipulate the state from outside the component? I want to be able change the state in my tests so that I can test how the dom changes.
I want to be able to change the value of something like this from outside of the component:
const [sent, setSent] = useState(false);
I would not test the internal state of the component but rather what this state might represent. The usage of the state is an implementation detail and you should test if the specification is implemented correctly and not how it is implemented.
In your case I would use the following approach:
Call the functionality that should set the sent state (button click,
form submit)
Test if the sent status is handled correctly in your
component (a success message is shown?)
Testing loading states of api calls can be achieved with mocks. If you don't use some fancy library but just do normal await apiCall() then you can use following approach:
Mock your api call (you probably already doing it)
Return a Promise() from the mock that will not be resolved
Example with enzyme:
import { apiCall } from '../api';
jest.mock('../api');
// ...
it('Contains a <Loading /> on loading', () => {
// The promise won't be resolved so the loading state will persist
apiCall.mockReturnValue(new Promise(() => null));
// await act... and wrapper.update() might not be needed
// depending on your implementation
await act(async () => {
wrapper.find(SubmitButton).simulate('click');
});
wrapper.update();
expect(wrapper.find(Loading)).toHaveLength(1);
});

Where to Put Code that should run First ReactJs + Mobx State Tree

I have some code that grabs the users ipAddres. I do this right now in my componentDidMount in my app.js
async componentDidMount() {
await eventTrackingStore.getIpAddress();
}
So I did it in my app.js as it is my root component and I only want to set this once. This works fine if the user starts from the home page and navigates through the site.
However some pages can be loaded directly(ie you type in the url in your browser and it goes straight to that page).
Since the react lifecycle starts with most immediate component, which calls a method that expects the ipAddress code to be set but it does not get set till it hits the app.js
Now I could put the above code in each method but that gets tedious. Is there some sort of method in reactjs, or mbox or mbox state tree that would fire first?
If you use mobx-state-tree and you have a global store, then that global store can make the API call in the afterCreate method
const AppModel = types.model({
ips: types.array(types.string)
}).actions(self => ({
afterCreate() {
flow(function*() {
const ips = yield eventTrackingStore.getIpAddress();
self.setIps(ips)
})()
},
setIps(ips: string[]) {
self.ips = ips
}
}))
OR
The same thing you can do in a wrapped react component that wrappes every page of your app.
class App extends React.Component {
componentDidMount() {
eventTrackingStore.getIpAddress().then(res => {
// set the ips into a store or any logic you want in order to pass them down to children
})
}
render() {
return this.props.children
}
}
I can think of two solutions:
You can use react context
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Use context to share the store between all components and if the data is not loaded, initialize loading right there in that nested component.
If the data is already there then just take it,
getIpAddress method should return a promise, so in case when data is already there it will be immediately resolved.

React/Redux - How to call an action after component has rendered, based on a check - Getting error

I have, what should be a simple issue in which, when a user navigates to a specific route, the component fires an initial function call to grab a user by ID via a redux-observable in an epic. However, if the user navigates away from the page and then comes back, I need to be able to reload the page, based on a route parameter.
I have a component that utilizes an HOC to run the render() method, but it looks like a dumb component:
const ProfilePage = props => {
const { actions, user, loading } = props;
// Note: This if statement results in an error
if (user && user.id !== props.params.id) {
actions.initAction(props.params.id);
}
return (<div>Test</div>);
};
ProfilePage.propTypes = {
actions: PropTypes.object,
user: PropTypes.object,
loading: PropTypes.bool,
};
export default connect(
state => ({
user: selectUser(state),
loading: selectLoading(state),
}),
dispatch => ({ actions: bindActionCreators(Actions, dispatch) })
)(
PureRender(
onMount(props => props.actions.initAction(props.params.id))(ProfilePage)
)
);
This results in an error:
react-dom.development.js?61bb:506 Warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
^ This happens because of my if statement that checks the user id against the params id.
Does this component need to be converted into a class in order to utilize other life cycle methods that could prevent this error from happening and run my functionality accordingly?
Function component should be pure, you can think of them as the "render" method of a class component.
You can either use a class component and do side effects in componentDidMount / componentDidUpdate, or use hooks with useEffect.
hooks useEffect / class cycle methods
I am guessing the issue is with ProfilePage. Added error Cannot update during an existing state transition (such as withinrender). thrown mostly when a setState which is a async method, another setState gets called. For your case it's a functional component which you are using it in render method. So basically each time your component rerenders ProfilePage function gets called which calls the action. So you might wanna change ProfilePage this to a react component and use life cycle methods such as componentDidMount to fix your issue.
Or if you are using react 16 or above use hooks with useEffect.

converting react classes to functions 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);

Prevent react component from rendering twice when using redux with componentWillMount

I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.
Here is the component:
class Editor extends React.Component {
componentWillMount() {
const { dispatch, params } = this.props
dispatch(editItem(params.id))
}
render() {
const item = this.props.item
console.log("Editing", item)
}
}
export default connect(state => ({item: state.item}))(Editor)
Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).
According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."
In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.
Is there a way around this besides adding a line like if (!item) return; ?
One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.
export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
return props => {
if (hasLoaded(props)) {
return <Component {...props} />
}
else {
if (onLoad) onLoad(props)
return { LoaderComponent ? <LoaderComponent /> : null }
}
}
}
Then you can wrap your component before connecting it to get the desired behaviour.
export default connect(state => ({item: state.item}))(LoaderWrapper(
((props) => !!props.item),
Editor,
null,
(props) => props.dispatch(editItem(props.params.id))
))
You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.
It looks like there's already an issue in the react-redux library.
https://github.com/rackt/react-redux/issues/210
What does editItem do? Does it add item to the redux state or is it there already?
If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank.
Then it gets rendered again when the props have changed, via setting the item.
One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.
But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?

Resources