I've been through many tutorials and questions on Stack but I can't find a solution. I'm just learning React/redux, trying to build OnClick action. I've got the following error "Maximum call stack size exceeded error". I got this because I'm rendering a function that's changing my state infinitely. I'm trying to deal with my <button onClick={DisplayTable(click)}>cool</button> differently but nothing seems to work.
I also know that my action and I guess my reducers works properly since when I'm dispatching my action trough the console : $r.store.dispatch({type: 'SET_TABLE_DATA'});, my state is updated properly.
Any advices ?
here is my action :
export const setTableFilter = (click) => {
return {
type: 'SET_TABLE_DATA',
click : click,
};
};
here is my reducer :
const tableFilter = (state = 0, action) => {
if(action.type === 'SET_TABLE_DATA') {
return state + 1;
}
return state;
}
and here is my component :
const DisplayTable = (click) => {
return (
<div>
<button onClick={DisplayTable(click)}>cool</button>
</div> )
}
function mapStateToProps(state) {
return {
click: state.tableFilter.click
};
};
const mapDispachToProps = (dispatch) => {
return {
DisplayTable: (click) => {dispatch (setTableFilter(click));
},
};
};
const AppTable = connect(mapStateToProps, mapDispachToProps)(DisplayTable);
export default AppTable;
I also know that I should build my reducer in a way that my state should be updated without any mutation, however I'll keep this for later ! :)
thanks.
The answer given doesn't really explain why your code was not working, so I thought I'd expand on that.
Your problem is that you are exceeding the function call stack, more commonly known as infinite recursion. The reason this is happening is because you aren't passing a function to the onClick attribute of your button, but rather invoking a function and passing its return value instead. So the following scenario is happening:
React component is mounted to the DOM
render() is called
The DisplayTable function is invoked, which dispatches an update to the store
The store updates, and passes new props to the React component
render() is called again
DisplayTable is invoked again
...and so on.
What you'll want to do instead is pass the function to the button's onClick attribute. So your component should look like this:
const Component = props => {
return (
<div>
<button onClick={props.DisplayTable}>cool</button>
</div>
);
};
In that above code snippet, I removed your click prop because it doesn't look like you're using it at all (given the code you posted in the OP).
A few tips, not a complete solution since that would not help you learn:
Your action and reducer are looking fine. You are passing the click property which is not used in the reducer. Maybe you will use it in the future but for now it is useless.
A React component function takes props as an argument:
const Comp = props => {
const click = props.click;
// ...
};
mapDispatchToProps is usually not needed. Use plain objects instead:
connect(state => state.tableFilter, { setTableFilter })(DisplayTable);
You can then access the function from props:
<button onClick={() => props.setTableFilter(click)}>cool</button>
Keep in mind: onClick takes a function!
Also the state you defined in the reducer has no property called click, instead it is a number (see correct mapStateToProps function above)
Related
I have what is basically a form wizard with multiple steps. The wizard is divided into two parts: labels and contents. When a content component changes its internal state, from say incomplete to error or something, I want the wizard to update the label. The issue I am running into is grabbing the state from the contents component, trying to save that in the wizard component as a state so I can update the labels is causing an infinite loop.
While I understand the problem I don't really know how to solve it. This is a very minimal example and my real components use some advanced features like cloneElement to pass props to user components without them having to worry about setting 10 different props. So far this has worked flawlessly until now.
So I understand that each time I update my main components state, it's going to re-render the children, which will call the same set state function forever. What can I do instead?
import React from "react";
import "./styles.css";
// This component containsLabel children
interface LabelState {
error: boolean;
}
interface LabelGroupProps {
states: LabelState[],
}
const LabelGroup = (props: LabelGroupProps) => {
return (<div>I am Comp A</div>)
}
// This component contains Component children
interface ContentState {
error: boolean;
}
interface ContentGroupProps {
getStates: (state: ContentState, index: number) => void
}
const ContentGroup = (props: ContentGroupProps) => {
// Indicate that step 2 has an error
props.getStates({
error: true
}, 2)
return (<div>I am Comp B</div>)
}
// This is the main wizard that contains both above components
// When the state of the a content component changes, the labels must be updated.
const App = () => {
const [states, setStates] = React.useState<LabelState[]>([]);
const getStates = (state: ContentState, index: number) => {
// This causes an infinite loop
// The intention is to save this state and then update the labels
const temp = [];
temp[index] = state;
setStates(prev => [...prev, ...temp])
}
return (
<div className="App">
<LabelGroup states={states}/>
<ContentGroup getStates={getStates}/>
</div>
);
};
export default App;
https://codesandbox.io/s/react-fiddle-forked-1ypi6
Your issue is caused by updating state every time ContentGroup renders. Any time there is a change in props or state, a component will automatically re-render. Then the component updates state again, then we render again, and so on. We can fix that.
What is your intention with calling getStates? You say:
// Indicate that step 2 has an error
An error could be in response to some event. For example, the user has clicked 'submit' on a form and you have a custom validation error. That might look like this:
const ContentGroup = (props: ContentGroupProps) => {
// Indicate that step 2 has an error
return (
<form
onSubmit={(event) => {
event.preventDefault();
const someErrorCondition = /* snip */;
props.getStates({
error: someErrorCondition
}, 2);
}}
>
{/* snip */}
</form>
)
}
This would not cause an infinite loop in the render because a change in state does not cause ContentGroup to call the onSubmit handler again.
Sometimes you want to perform initialization and cleanup as a component mounts and unmounts:
const ContentGroup = (props: ContentGroupProps) => {
// Indicate that step 2 has an error
React.useEffect(() => {
props.getStates({
error: true
}, 2);
return function cleanup() {
props.getStates({
error: false
}, 2);
};
}, []);
return (
<form
onSubmit={(event) => {
event.preventDefault();
/* snip */
}}
>
{/* snip */}
</form>
)
}
This does not cause an infinite loop because getStates will only be called twice: when the component mounts and right before the component unmounts.
Please treat this as pseudo-code because there are more details to leveraging hooks. Here's the documentation for useEffect, https://reactjs.org/docs/hooks-reference.html#useeffect The example code I posted won't pass many linters because it does not declare getStates as part of the hook's dependency array. In this one case, it's actually fine because we're only going to run the effect once. Further changes to getStates will be ignored. Here are more details about the dependency array of a hook: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
I have setInterval setup to be working properly inside componentDidMount but the parameters are not updated. For example, the text parameter is the same value as when the component initially mounted, despite being changed in the UI. I've confirmed text's value is correctly updated in Redux store but not being passed to this.retrieveData(text). I suspect the const { text } = this.props set the value in componentDidMount, which forbids it from updating despite it being different. How would I go about this issue?
Code below is an example, but my real use-case is retrieving data based on search criteria. Once the user changes those criteria, it will update with the new result. However, I'm unable to pass those new criteria into componentDidMount so the page would refresh automatically every few seconds.
class App extends React.Component {
componentDidMount() {
const { text } = this.props //Redux store prop
setInterval(() => this.retrieveData(text), 3000)
}
retrieveData = (text) => {
let res = axios.post('/search', { text })
updateResults(res.data) //Redux action
}
render() {
const { text, results } = this.props
return (
<input text onChange={(e) => updateText(e.target.value)} />
<div>
{results.map((item) => <p>{item}</p>}
</div>
)
}
}
Because you are using componentDidMount and setTimeout methods your retrieveData is called only once with initial value of the text. If you would like to do it in your current way please use componentDidUpdate method which will be called each time the props or state has changed. You can find more information about lifecycle here https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/.
If you would like to use setInterval just like in the question, you just need to access props inside of retrieveData method instead of using an argument.
retrieveData = () => {
let res = post("/search", { text: this.props.text });
updateResults(res); //Redux action
};
You can find working example for both cases here https://codesandbox.io/s/charming-blackburn-khiim?file=/src/index.js
The best solution for async calls would be to use some kind of middleware like https://github.com/reduxjs/redux-thunk or https://redux-saga.js.org/.
You have also small issue with input, it should be:
<input type="text" value={text} onChange={(e) => updateText(e.target.value)} />
I'm trying to learn and test React.PureComponent and it keeps rendering even though no state changes for that pure component.
My PureComponent is very simple and it accepts only one Redux Action function via connect hoc
import React from 'react';
import {
Container,
Button
} from 'reactstrap'
import { connect } from 'react-redux'
import { resetWorkouts } from '../actions/workoutApiActions'
class About extends React.PureComponent {
render () {
const { resetWorkouts } = this.props;
console.log('in about render...')
return (
<React.Fragment>
<Container>
<h2>Api Data Reset</h2>
<Button color="danger" onClick={resetWorkouts}>Reset Data</Button>
</Container>
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
resetWorkouts: () => dispatch(resetWorkouts())
}
}
export default connect(null, mapDispatchToProps)(About);
In the above code, you can see that, there is no state in the component. It only accepts the action function as props from connect. However, whenever I clicks on the Reset Data button, it keeps calling the render method as shown in the screenshot.
In the screenshot, I can see that, global state store has been changed whenever, I click a button. But, that state is not used in my PureComponent and it should be out of the scope and my component should ignore to re-render.
Or Redux Action functions are created every time the global state store has been changed. And passed as a new object to my PureComponent ?
Theoretically, I don't need to write my own shouldComponentUpdate function, right? I'm confused and could you please help me to understand about this behaviour?
My goal is I don't want my PureComponent to render again when a user clicks a button.
Updates:
I have tried like the following according to this article and it's still re-rendering
const mapDispatchToProps = {
resetWorkouts
};
this because react do a shallow comparison between the prevProps and the nextProps,
and you can control that only in the shouldComponentUpdate, react doesn't know that the dispatcher is the same one from the previous render, because you are using return inside the mapDispatchToProps function.
In your component and in your case, while the function will remain the same, you can go with two paths:
path 1:
override the shouldComponentUpdate life cycle hook, to be as the following:
shouldComponentUpdate(){
return false;
}
path 2:
get rid of the return inside mapDispatchToProps and simplify the connect so it be as the following:
`conncect(state => ({}), {
resetWorkouts: resetWorkouts})(YourComponent);`
using one of the above paths should make you good to go
The reason why your component is rendering is because everytime the following function executes:
const mapDispatchToProps = dispatch => {
return {
resetWorkouts: () => dispatch(resetWorkouts())
}
}
your components receives a new instance of a property named resetWorkouts(because you're creating an inline array function). You may look at the ownProps to check if your component already have the resetWorkouts:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
resetWorkouts: ownProps.resetWorkouts || () => dispatch(resetWorkouts())
}
}
In component I want to get the data immediately in props after calling
webapi service call and do some operation,but issue is that it is not
updating the props immediately because as we know that call will be
async, So what will be the solution? My codes in component are like
this:-
openPreviewClick=(event) => {
this.props.GetReport();
console.log(this.props.reportData);
}
function mapStateToProps (allReducers) {
return {reportData: allReducers.reportData
}}
const matchDispatchToProps = (dispatch) => ({
GetReport: () => dispatch(LoadReportData())
})
export default connect(mapStateToProps, matchDispatchToProps)(MyContainer)
Now I have to open a pdf for this I have tried with two solution:-
Handling life cycle of the page
componentWillReceiveProps(nextProps) {
if(nextProps.reportPath!=undefined){
window.open(nextProps.reportPath,"thePop","menubar=1,resizable=1,scrollbars=1,status=1,top=280,width=850,height=600");
}
Writing the code in render
render () {
if(this.props.reportPath!=undefined && this.props.reportPath!=""){}
window.open(this.props.reportPath,"thePop","menubar=1,resizable=1,scrollbars=1,status=1,top=280,width=850,height=600");
}
openPreviewClick is my button click on which I want to access the
props named as reportData.But console.log(this.props.reportData); is
giving me the null value for the first time,second time if I will
click then we are getting the data.How we can manage this? I already tried above two solution but it is not working.
Simple answer, you don't ^1
If this is truely an async request, there is no guarantee when the data will come back, so your component needs to "understand" that is can exist in a "without data" state.
Simplest form of this is:
render() {
if( ! this.props.reportData) return null;
// normal render code, at this point we have data
return <div>{this.props.reportData.map(foo, ...)}</div>
}
A better form, would be something like:
render() {
if( ! this.props.reportData) {
return <div><img src="loading.gif" /></div>;
}
// normal render code, at this point we have data
return <div>{this.props.reportData.map(foo, ...)}</div>
}
^1 Note: You could technically use async functions, but I feel that would complicate the problem, especially without a fundamental understanding of what is already going on.
In your main file where you create the store you can dispatch action and set initial value just like
import configureStore from './store/configureStore;
import {LoadReportData} from './actions/LoadReportData';
const store = configureStore();
store.dispatch(LoadReportData());
I was reading the documentation for the Redux library and it has this example:
In addition to reading the state, container components can dispatch actions. In a similar fashion, you can define a function called mapDispatchToProps() that receives the dispatch() method and returns callback props that you want to inject into the presentational component.
This actually makes no sense. Why do you need mapDispatchToProps when you already have mapStateToProps?
They also provide this handy code sample:
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
What is this function and why it is useful?
I feel like none of the answers have crystallized why mapDispatchToProps is useful.
This can really only be answered in the context of the container-component pattern, which I found best understood by first reading:Container Components then Usage with React.
In a nutshell, your components are supposed to be concerned only with displaying stuff. The only place they are supposed to get information from is their props.
Separated from "displaying stuff" (components) is:
how you get the stuff to display,
and how you handle events.
That is what containers are for.
Therefore, a "well designed" component in the pattern look like this:
class FancyAlerter extends Component {
sendAlert = () => {
this.props.sendTheAlert()
}
render() {
<div>
<h1>Today's Fancy Alert is {this.props.fancyInfo}</h1>
<Button onClick={sendAlert}/>
</div>
}
}
See how this component gets the info it displays from props (which came from the redux store via mapStateToProps) and it also gets its action function from its props: sendTheAlert().
That's where mapDispatchToProps comes in: in the corresponding container
// FancyButtonContainer.js
function mapDispatchToProps(dispatch) {
return({
sendTheAlert: () => {dispatch(ALERT_ACTION)}
})
}
function mapStateToProps(state) {
return({fancyInfo: "Fancy this:" + state.currentFunnyString})
}
export const FancyButtonContainer = connect(
mapStateToProps, mapDispatchToProps)(
FancyAlerter
)
I wonder if you can see, now that it's the container 1 that knows about redux and dispatch and store and state and ... stuff.
The component in the pattern, FancyAlerter, which does the rendering doesn't need to know about any of that stuff: it gets its method to call at onClick of the button, via its props.
And ... mapDispatchToProps was the useful means that redux provides to let the container easily pass that function into the wrapped component on its props.
All this looks very like the todo example in docs, and another answer here, but I have tried to cast it in the light of the pattern to emphasize why.
(Note: you can't use mapStateToProps for the same purpose as mapDispatchToProps for the basic reason that you don't have access to dispatch inside mapStateToProp. So you couldn't use mapStateToProps to give the wrapped component a method that uses dispatch.
I don't know why they chose to break it into two mapping functions - it might have been tidier to have mapToProps(state, dispatch, props) IE one function to do both!
1 Note that I deliberately explicitly named the container FancyButtonContainer, to highlight that it is a "thing" - the identity (and hence existence!) of the container as "a thing" is sometimes lost in the shorthand
export default connect(...)
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
syntax that is shown in most examples
It's basically a shorthand. So instead of having to write:
this.props.dispatch(toggleTodo(id));
You would use mapDispatchToProps as shown in your example code, and then elsewhere write:
this.props.onTodoClick(id);
or more likely in this case, you'd have that as the event handler:
<MyComponent onClick={this.props.onTodoClick} />
There's a helpful video by Dan Abramov on this here:
Redux: Generating Containers with connect() from React Redux (VisibleTodoList)
mapStateToProps() is a utility which helps your component get updated state(which is updated by some other components),
mapDispatchToProps() is a utility which will help your component to fire an action event (dispatching action which may cause change of application state)
mapStateToProps, mapDispatchToProps and connect from react-redux library provides a convenient way to access your state and dispatch function of your store. So basically connect is a higher order component, you can also think as a wrapper if this make sense for you. So every time your state is changed mapStateToProps will be called with your new state and subsequently as you props update component will run render function to render your component in browser. mapDispatchToProps also stores key-values on the props of your component, usually they take a form of a function. In such way you can trigger state change from your component onClick, onChange events.
From docs:
const TodoListComponent = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
const TodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
Also make sure that you are familiar with React stateless functions and Higher-Order Components
Now suppose there is an action for redux as:
export function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
When you do import it,
import {addTodo} from './actions';
class Greeting extends React.Component {
handleOnClick = () => {
this.props.onTodoClick(); // This prop acts as key to callback prop for mapDispatchToProps
}
render() {
return <button onClick={this.handleOnClick}>Hello Redux</button>;
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: () => { // handles onTodoClick prop's call here
dispatch(addTodo())
}
}
}
export default connect(
null,
mapDispatchToProps
)(Greeting);
As function name says mapDispatchToProps(), map dispatch action to props(our component's props)
So prop onTodoClick is a key to mapDispatchToProps function which delegates furthere to dispatch action addTodo.
Also if you want to trim the code and bypass manual implementation, then you can do this,
import {addTodo} from './actions';
class Greeting extends React.Component {
handleOnClick = () => {
this.props.addTodo();
}
render() {
return <button onClick={this.handleOnClick}>Hello Redux</button>;
}
}
export default connect(
null,
{addTodo}
)(Greeting);
Which exactly means
const mapDispatchToProps = dispatch => {
return {
addTodo: () => {
dispatch(addTodo())
}
}
}
mapStateToProps receives the state and props and allows you to extract props from the state to pass to the component.
mapDispatchToProps receives dispatch and props and is meant for you to bind action creators to dispatch so when you execute the resulting function the action gets dispatched.
I find this only saves you from having to do dispatch(actionCreator()) within your component thus making it a bit easier to read.
React redux: connect: Arguments