Where is the correct place to load data in a redux component?
Currently I have it this way.
Say I have this container component:
import { loadResultsPage } from '../actions/winratio-actions';
function mapStateToProps(state) {
const {
isFetching,
results
} = state;
return {
isFetching,
results
};
};
export default connect(mapStateToProps, {
loadResultsPage
})(WinRatio);
I then make a call in the wrapped component's componentWillMount lifecycle event:
export default class WinRatio extends Component {
componentWillMount() {
this.props.loadResultsPage();
}
render() {
return (
<div>
<h1>Win Ratio</h1>
</div>
);
}
};
Where should this call happen?
You can do it in your container component. If you use redux you probably use smart\dumb components strategy. Create a container where you use compose function from redux package and you can compose it like this:
export default compose(
connect(null, { loadData }), //this is your async action
doOnComponentMount(({props}) => props.loadData()),
)(MyDumbComponent)
and doOnComponentMount is:
function doOnComponentMount(cb) {
return (Component) => {
return class extends React.Component {
componentDidMount() {
cb(this);
}
render() {
return <Component {...this.props} />;
}
}
}
}
Related
In React can methods be passed to {this.children} in a container consumer model. What I mean to ask is I have a provider component and I need to pass or refer the provider components methods in the child component.
export default class ContainerCompo extends React.Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
methodOne() {
//some code
}
methodTwo() {
//some code
}
render() {
return (
{this.props.children}
}
}
export default class InputComponent extends React.Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
validate() {
ContainerCompo.methodOne(param)
}
render() {
return <InputComponent />
}
// Rendering the components
<ContainerCompo>
<InputComponent containerMethods={methods of ContainerCompo}/>
</ContainerCompo>
I hope my question is clear here, please suggest
First create a react context.
import React, { Component, createContext } from 'react';
// Create's authentication context to be use anywhere in the app
const ContainerContext = createContext();
export default ContainerContext;
Then create a provider for it.
export default class ContainerProvider extends Component {
constructor(props) {
super(props);
this.myHocComponent = null;
}
methodOne() {
//some code
}
methodTwo() {
//some code
}
render() {
const { children } = this.props;
return (
<ContainerContext.Provider
value={{
container: {
methodOne: (...params) => this.methodOne(...params),
methodTwo: (...params) => this.methodTwo(...params)
}
}}
>
{children}
</ContainerContext.Provider>
)}}
Wrap your App with the provider.
import ContainerProvider from './ContainerProvider'
<ContainerProvider>
<App />
</ContainerProvider>
Then create a consumer for the context
export default function withContainer(InComponent) {
return function ContainerComponent(props) {
return (
<ContainerContext.Consumer>
{({ container }) => <InComponent {...props} container={container} />}
</ContainerContext.Consumer>
);
};
}
Then import the consumer and user in your components and you will get the methods as props
import withContainer from './ContainerConsumer'
render() {
const { container } = this.props;
return(<div />)
}
export default withContainer(YourComponent);
import React from "react";
import OtherComponent from "./OtherComponent";
class Main extends React.Component {
constructor(props) {
super(props);
this.runMyFunction = this.runMyFunction.bind(this);
this.myFunction = this.myFunction.bind(this);
}
runMyFunction(event) {
event.preventDefault();
this.myFunction();
}
myFunction() {
return console.log("I was executed in Main.js");
}
render() {
return (
<div>
<OtherComponent runMyFunction={this.runMyFunction} />
</div>
);
}
}
export default Main;
import React from "react";
class OtherComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.runMyFunction();
}
render() {
return (
<div>
<button onClick={this.handleClick} />Click me to execute function from Main </button>
</div>
);
}
}
export default OtherComponent;
I'm new in redux and don't know how to pass and run that function in other component. It was easy not using redux, just pass as props like in example above.
I have folder with actions, components, containers and reducers.
Now I have Main.js where I have
import React from "react";
const Main = ({data, getData}) => {
const myFunction = () => {
return "ok";
};
return (
<div>
<p>This is main component</p>
</div>
);
};
export default Main;
In MainContainer.js I got:
import Main from "../../components/Main/Main";
import { connect } from "react-redux";
import {
getData
} from "../../actions";
function mapStateToProps(state) {
return {
data: state.main.data
};
}
const mapDispatchToProps = (dispatch) => {
return {
getData: () => dispatch(getData())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Main);
So how I can run function myFunction() in OtherComponent.js:
import React from "react";
const OtherComponent = ({executeFunctionInMainComponent}) => {
return (
<div>
<button onClick={executeFunctionInMainComponent}>run action</button>
</div>
);
};
export default OtherComponent;
I need to just run, not pass whole function, just execute myFunction in Main.js but action to run this function will came from OtherComponent.
So first i have to mention that i believe that you have a misconception of redux. This isn't to allow for functions created in components to be reused in different locations. This is to move that logic to a reducer outside of your function which would allow it to be used wherever you wired it with {connect} from react-redux. So you will need a couple of files (for clarity). First you're going to need an action file which we'll name myReturnOkAction.
export const myReturnOkAction = (/*optional payload*/) => {
return {
type: 'PRINT_OK',
}
}
Redux Actions
This is what you're going to call in your mapDispatchToProps function where you're going to trigger this event. You're going to have to import it into your OtherComponent so import {myReturnOkAction} from "/*wherever the files exists*/" and to include it in your mapDispatchToProps as okFunction: () => dispatch(myReturnOkAction())
Once you have your action your connect Higher Order Component (HOC) wrapping your main component is going to need a Reducer to modify your current store state as well as do any actions.
export const myReturnOkReducer = (state, action) => {
if(action.type === 'PRINT_OK'){
/*This is where you update your global state*/
/*i.e. return {...store, valueWanted: ok}*/
}else{
return state
}
}
Redux Reducers
So the way that this is going to move is that you're function, somewhere is going to call the action. Once the action is called its going to trigger the reducer and make any changes to the store which you need. Once the reducer has updated the store with new values its then going to update any components which are connected to it through the connect HOC which will cause them to re-render with new information.
Also my favorite image to describe how redux works.
I hope this helps.
I found an answer:
I still can pass as props in redux but I can't do this in this way: OtherComponent = ({executeFunctionInMainComponent}) => {}. I need to do in this way: OtherComponent = (props) => {} and then inside that component I have an access via props.executeFunctionInMainComponent
Originally I had this call at index.js to trigger the load of my main data:
const store = configureStore();
store.dispatch(doStuff());
Now I want to do the next step and load this data at page level (seems better).
I'm basing myself on this post by Gaearon at the Redux github:
I have this code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { PropTypes } from 'prop-types';
import { bindActionCreators } from 'redux';
import * as myActions from '../../actions/myActions';
import { MuiThemeProvider } from 'material-ui/styles';
let createHandlers = function(dispatch) {
let doStuff = function() {
dispatch(myActions.doStuff())
};
return {
doStuff,
// other handlers
};
}
class MyPage extends Component {
constructor(props, context) {
super(props, context);
this.handlers = createHandlers(this.props.dispatch);
//this.handlers.doStuff();
this.state = {
myStuff: []
}
}
render() {
return (
<MuiThemeProvider>
<div>...</div>
</MuiThemeProvider>
);
}
}
function mapStateToProps(state, ownProps) {
return {
// Set state
};
}
function mapDispatchToProps(dispatch) {
return {
// Set state
};
}
MyPage.propTypes = {
// My props
}
export default connect(mapStateToProps, mapDispatchToProps)(MyPage);
The issue
When I uncomment the line, I get this error:
TypeError: dispatch is not a function
let doStuff = function() {
dispatch(myActions.doStuff())
};
The (most important) difference that I see is that I do mapping:
export default connect(mapStateToProps, mapDispatchToProps)(MyPage);
What do I need to do to get this to work?
Likely something easy, but I don't see it.
Connect's react-redux docs explain that:
If you do not supply your own mapDispatchToProps function or object
full of action creators, the default mapDispatchToProps implementation
just injects dispatch into your component’s props.
When you don't pass mapDispatchToProps params to connect, react-redux passes dispatch as a prop to the wrapped component.
If you pass mapDispatchToProps to connect, the wrapped actions are passed instead of dispatch, and this.props.dispatch is undefined.
So if you need dispatch in your compon, refrain from using mapDispatchToProps, or wrap all your actions with dispatch inside mapDispatchToProps.
Oh boy ... I didn't need Gaearon's script at all. All I had to do is call the actions list from the constructor:
class MyPage extends Component {
constructor(props, context) {
super(props, context);
props.actions.doStuff();
this.state = {
myStuff: []
}
}
render() {
return (
<MuiThemeProvider>
<div>...</div>
</MuiThemeProvider>
);
}
}
Where this is the important line:
props.actions.doStuff();
Which is available because it's mapped in mapDispatchToProps:
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(loadOrderActions, dispatch)
};
}
My first time using react/redux and I'm trying to bind a simple action addClick to the 'click' event, but when I click I receive the error:
Uncaught TypeError: Cannot read property 'props' of undefined
My (stripped down) code is:
import {addClick} from './actions'
const mapDispatchToProps = {addClick}
class App extends Component {
componentDidMount() {
document.addEventListener('click', this.props.addClick)
}
componentWillUnmount() {
document.removeEventListener('click', this.props.addClick)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Before I had it implemented without mapDispatchToProps using an action creator defined within the class and bound to this in the constructor. But I thought part of the point of mapDispatchToProps was to bind the action creator to this (as well as wrapping it in a dispatch)
What am I missing?
Thanks!
From what I can tell from the documentation, the object short-hand (const mapDispatchToProps =
{addClick}) you use for mapDispatchToProps doesn't bind this to anything. It just sees to it that your addClick action creator gets called with dispatch. So that if you, in your component execute addClick(3), then that will result in a call looking like this dispatch(addClick(3)).
I'm not sure why your action creator would need access to this though. Can't you just pass it what ever data it needs as a parameter? So that the call in your component might look like
componentDidMount() {
const {addClick, someOtherProp} = this.props;
document.addEventListener('click', () => addClick(someOtherProp));
}
Do you use props inside addClick action?
Check this example:
import React from "react";
import { render } from "react-dom";
import { connect, Provider } from "react-redux";
import { createStore } from "redux";
function addClick(event) {
return {
type: "CLICK",
payload: `pageX: ${event.pageX} | pageY: ${event.pageY}`
};
}
const mapStateToProps = state => {
return {
clickXY: state
};
};
const mapDispatchToProps = { addClick };
class App extends React.Component {
componentDidMount() {
document.addEventListener("click", this.props.addClick);
}
componentWillUnmount() {
document.removeEventListener("click", this.props.addClick);
}
render() {
return (
<h1>
Click message: {this.props.clickXY}
</h1>
);
}
}
function clickReducer(state = "None", action) {
switch (action.type) {
case "CLICK": {
return action.payload;
}
default:
return state;
}
}
let store = createStore(clickReducer);
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);
class Root extends React.Component {
render() {
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
}
}
render(<Root />, document.getElementById("root"));
Link to editor
So bind works well in this code.
I am new to react-redux and I am having some difficulty in understanding the syntax. I am pasting my sample code below... please help me understand if there are any syntactical errors.
SampleParent.js:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchNames, fetchDownloadLink } from '../../actions/actions'
import SampleChild from '../ui/SampleChild'
class SampleParent extends Component {
constructor(props) {
super(props) ;
}
componentDidMount() {
const { dispatch } = this.props
dispatch(fetchNames());
}
render() {
return(<div><ul id="myUL">{this.props.reports.map((report) => (
<li>
<SampleChild
key={report.id}
label={report.label}
uri={() => fetchDownloadLink("http://localhost:8080/sample"+this.props.uri+".pdf")}
/>
</li>))}</ul></div>)}
}
function mapStateToProps(state) {
const { reports } = state
return {
reports
}
}
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink(url) {
dispatch(
fetchDownloadLink(url)
)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReportsApp)
SampleChild.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchDownloadLink } from '../../actions/actions'
class OpenReport extends Component {
constructor(props) {
super(props) ;
}
render(){
return(<div className="in_sample" id={this.props.label}>
{this.props.label}
<a href={this.props.uri}>
<img src="../images/pdf-file_128.png" height="25px" width="25px"></img></a><br></br></div>
)
}
}
module.exports = OpenReport;
Currently I am getting this error:
Uncaught TypeError: dispatch is not a function
at ReportsApp.componentDidMount (bundle.js:39883)
Basically what I need to do is get a url as a string from the 'fetchDownloadLink ' function and pass this string to my child component. Is there any other way to do that?
Please suggest...
Thanks in advance!
According to the Documentaion:
mapDispatchToProps returns an object that somehow uses dispatch to bind
action creators in your own way.
However in your case you are returning an object without keys. Change your function to
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink: (url) => dispatch(fetchDownloadLink(url))
}
}
}
MoreOver, using connect function you need to connect mapStateToProps and mapDispatchToProps function to the component in which you will be using the action creators which in your case is SampleParent
Also if you pass mapDispatchToProps as the second parameter to connect, then dispatch is not available as a prop to your component.
So change your code to the following
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchNames, fetchDownloadLink } from '../../actions/actions'
import SampleChild from '../ui/SampleChild'
class SampleParent extends Component {
constructor(props) {
super(props) ;
}
componentDidMount() {
const { dispatch } = this.props
this.props.fetchNames();
}
render() {
return(<div><ul id="myUL">{this.props.reports.map((report) => (
<li>
<SampleChild
key={report.id}
label={report.label}
uri={() => this.props.fetchDownloadLink("http://localhost:8080/sample"+this.props.uri+".pdf")}
/>
</li>))}</ul></div>)}
}
}
function mapStateToProps(state) {
const { reports } = state
return {
reports
}
}
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink: (url) => dispatch(fetchDownloadLink(url)),
fetchNames: () => dispatch(fetchNames)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SampleParent)
The problem is that you are passing your action creator as a prop uri to your child component, and then using that prop as an HREF tag.
You should instead pass it as onChildClick (example name) prop to your report item component, and call it on the onClick prop of the <a>.
<a onClick={ this.props.onChildClick }>xxxx</a>
The previous answer about the action creator mapping is fine, but you don't even need a function: if you use an object with functions as keys, they will be wrapped with dispatch for you.
const mapDispatchToProps = {
fetchDownloadLink
}
EDIT after your updated question
I see the problem now. You don't have dispatch as prop because you're using mapDispatchToProps to provide some action creators as props. It doesn't make sense to map dispatch to fetchDownloadLink and not doing it as well for fetchNames. Map both or neither, but you shouldn't mix and match.