Overview
We have a page with a header (Blue color) and content section (Green color) that can be seen in image below. The requirement is when a user selects a year in header, then the content page will show data as per the selected year.
What is happening right now Technically
When user selects a year in header, we dispatch the selected value and the active container's mapStateToProps function is triggered and the selected year is passed to the component.
class Page1Content extends Component {
}
function mapStateToProps(state) {
return { selectedYear : state.userSelectedValue };
}
export default connect(mapStateToProps, null)(Page1Content);
Question 1
How will data on Page1Content will refresh? Few approaches:
In ComponentDidUpdate react life cycle method of the Page1Content API to fetch data can be called. However have seen opinions where we should avoid React hooks and life cycle methods with Redux.
In mapStateToProps function API can be called.
Can any one suggest what is the better place to call the API?
Question 2
Data on Page1Content will be used only by this page. This data will not be used by any other component and hence need not be shared by any other Component. Now question 2 is
In case we decide to use ComponentDidUpdate should we again dispatch the API call using Thunk or any other library and then catch the response in mapStatesToProps again?
Or we should make the API call and resolve it in the component itself as a promise. Then the response will be set in State and respective Template will be refreshed.
ComponentDidUpdate is a lifecycle method not a hook. Hooks is functionality that allows functional components to have class based functionality such as state.
You are using a class based component in your example so you are not using hooks.
https://reactjs.org/docs/hooks-intro.html
Yes Redux shouldnt be used with hooks since context is a better option.
You can lift state up so to speak and update the local state in the parent component getting rid of redux completely.
Just pass down the setState function and the state itself to the appropriate children.
class App extends Component {
constructor(props) {
super(props);
this.state = {
some_prop: false
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({some_prop: true})
console.log('Click happened');
}
render() {
return (
<Header onClick={this.handleClick } />
<Page1Component props={this.state.some_prop} />
}
}
Edit:
Question 1
How will data on Page1Content will refresh?
best option is with a ternary expression in your render method, there is no need to check if the state updated. In react if the state is changed your component will automatically re render.
render() {
return (
<div>
{this.props.selectedYear
? <p> {this.props.selectedYear}</p>
: null
}
</div>
}
}
Question 2
Data on Page1Content will be used only by this page. This data will not be used by any other component and hence need not be shared by any other Component. Now question 2 is
If I understand this correctly you will need to use an action creators, redux thunk is overkill here.
class Header extends Component {
constructor(props) {
super(props);
}
handleClick() {
this.props.dispatchActionCreator({some_value})
console.log('Click happened');
}
render() {
return (
<button onClick={(some_value) => this.handleClick(some_value)}>Click </button>
}
}
function mapDispatchToProps(state) {
return {
dispatchActioNCreator: (some_value) => dispatch(ACTIONS.action_creator(some_value) };
}
This will save your value from your header to the global redux state and then you can just access with mapStateToProps in your Page1Component.
Related
I want to use the useSelector() hook but I'm getting the error mentioned above. Where can I use this hook to get access to my state data?
function RetrieveDataSources() {
var dataSources = useSelector(state => state.dataSourcesReducer);
console.log(dataSources);
}
class Data extends Component {
constructor(props) {
super(props);
this.state = {
errorMessage: false,
isLoading: true,
resultData: propsState && propsState.resultData,
};
RetrieveDataSources();
}
render() {
return( some return code );
}
}
export default Data;
Hooks can only be called in either a function component, or custom hooks.
You are calling it from a normal function instead hence the error.
Furthermore, it seems you want to call a hook from a class component - that's unfortunately not directly supported. If you have to use class components, consider using mapStateToProps & connect apis instead to get it from your redux store.
If you still prefer to use hooks from within class component, here is an example to use function component and React render props to share it back with the parent component. This is usually done by libraries though where people cannot dictate whether the caller is using function component or class component.
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 have CompetitionSection which repeats all the competitions from database. When user clicks on one, it redirects him to a Competition Page, loads for a second and renders the page with all the details in it. So far, so good.
But when users goes back to the Competition Section and then click on the second competition, it instantly loads up the previous competition, 0 loading time.
From my point of view, what is failing is that the props of the component are not updating when I render the component (from the second time). Is not a router problem, which was my first instinct because I'm seeing the route.params changing acordingly, but the actions I dispatch to change the props are not dispatching. Here's a bit of code of said component.
class CompetitionPage extends React.Component {
componentWillMount() {
let id = getIdByName(this.props.params.shortname)
this.props.dispatch(getCompAction(id));
this.props.dispatch(getCompMatches(id));
this.props.dispatch(getCompParticipants(id));
this.props.dispatch(getCompBracket(id));
}
render() {
let { comp, compMatches, compBracket, compParticipants } = this.props
...
I tried every lifecycle method I know. component Will/Did Mount, component Will/Did update and I even set shouldUpdate to true and didn't do the trick. As I understand, the problem will be solved with a lifecycle method to dispatch the actions everytime an user enters Competition Page and not just for the first time. I'm running out of options here, so any help will be appreciated.
NOTE: I'm a newbie at React/Redux so I KNOW there are a couple of things there are anti-pattern/poorly done.
UPDATE: Added CompetitionsSection
class CompetitionsSection extends React.Component {
render() {
const {competitions} = this.props;
return (
...
{ Object.keys(competitions).map(function(comp, i) {
return (
<div key={i} className={competitions[comp].status ===
undefined? 'hide-it':'col-xs-12 col-md-6'}>
...
<Link to={"/competitions/"+competitions[comp].shortName}>
<RaisedButton label="Ver Torneo" primary={true} />
</Link>
...
It helps to better understand the lifecycle hooks. Mounting a component is when it is placed on the DOM. That can only happen once until it is removed from the DOM. An UPDATE occurs when new props are passed or setState is called. There are a few methods to troubleshoot when updates are not happening when you think they should:
Ensure that you are changing state in componentDidMount or componentDidUpdate. You cannot trigger an update in componentWillMount.
Make sure that the new props or state are completely new objects. If you are passing an object down in props and you are just mutating the object, it will not trigger an update. For instance, this would not trigger a update:
class CompetitionPage extends React.Component {
constructor(props) {
super(props)
this.state = {
competitions: [ compA, compB ]
}
}
triggerUpdate() {
this.setState({
competitions: competitions.push(compC)
})
}
componentDidMount() {
triggerUpdate()
}
render() {
return(
<div>
Hello
</div>
)
}
This is due to the fact that a new competition is being appended to the array in state. The correct way is to completly create a new state object and change what needs to be changed:
const newCompetitions = this.state.competitions.concat(compC)
this.setState(Object.assign({}, this.state, { competitions: newCompetitions }))
Use ComponentWillRecieveProps on an update to compare previous and current prop values. You can setState here if clean up needs to be done:
Read more about this method in the React documentation:
https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
I have a React component: stepA, which obtains a value on submit which is then stored in my redux store as state.chef_positions.[0].chef_title_id.
After a user submits StepA, they are instantly directed to StepB. StepB is mounting before stepA's reducer has finished, meaning StepB is loading a incorrect value from the store.
What is the right way to get stepB to re-render when stepA user action has been saved to the server/store, to re-render it's dispatch has finished?
class stepA extends React.Component {
handleSubmit(data) {
this.props.actions.createChefPosition(data);
this.props.nextStep();
}
...
class stepB extends React.Component {
componentDidMount() {
const title_id = this.props.chef_positions[0].chef_title_id;
this.props.dispatch(loadChefTitleSkills(chef_title_id));
}
stepB.propTypes = {
chef_positions: PropTypes.array.isRequired,
};
function mapStateToProps(state, ownProps) {
return {
chef_positions: state.chef_positions,
};
}
export default connect(mapStateToProps)(stepB);
actually, nothing.
Because of the way Redux is connected to React app is through "first-class object" level (you connect() mapStateToProps to your component), the props in the components are modified to current store state when changes happen at the store.
the only thing you need to do is to re-assign "title_id" with { componentDidUpdate } lifecycle method/use directly "this.props ..." wherever you use title_id/use "useSelector" redux hook.
one other thing, you might cinsider add { mapDispatchToProps } to the connect
On my React + Redux client app, I need to get the active user info (fetch from myapp.com/users/me) and keep it somewhere so that I can access it from multiple components.
I guess window.activeUser = data would not be the best practice. But I could not find any resource about the best practice of doing that. What would be the best way to do what I want?
you can keep it in a separate reducer, and then import multiple parts of your state with connect() in your components.
Say if you have 2 reducers called users.js and tags.js which are combined with combineReducers when setting up your store. You would simply pull different parts by passing a function to your connect() call. So using es6 + decorators:
const mapStateToProps = state => {
return {users: state.users, tags: state.tags}
}
#connect(mapStateToProps)
export default class MyComponent extends React.Component {
and then down in your render function:
return (
<div>
<p>{this.props.users.activeUsernameOrWhatever}</p>
<p>{this.props.tags.activeTags.join('|')}</p>
</div>
);
So your different reducer states become objects on this.props.
You can use React's context, HOC or global variables to make your data available to multiple components, via context
Something like...
class ParentDataComponent extends React.Component {
// make data accessible for children
getChildContext() {
return {
data: "your-fetchet-data"
};
}
render() {
return < Child />
}
}
class Child extends React.Component {
render() {
// access data from Parent's context
return (<div> {this.context.data} </div>);
}
}
create an action creator and call it on componentWillMount of the appropriate component so it runs right before your component mounts, and in the action creator fetch the data you need and pass it to a reducer. in that reducer you can keep the data you want throughout your application. so whenever you needed the data you can retrieve it from redux state. this tutorial from official redux website covers everything you need to know. mention me if you had any questions.