Child component won't re-render - reactjs

I am using react with redux and I have a todo-list.
I have a 'Todos' component which acts like a container and a 'Todoitem' component which holds every todo.
Everything works fine - reducers change the state and it is updating with new data, but the child component (aka 'Todoitem' component) won't re-render.
Todos.js:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import TodoItem from "./TodoItem";
class Todos extends Component {
render() {
return (
<div className="Todos">
<div className="todos_title"> {this.props.title} </div>
{this.props.todos.map(todo => {
console.log(todo); // this line prints updated data from state just fine!
return <TodoItem todo={todo} key={todo.id}></TodoItem>;
})}
</div>
);
}
}
// PropTypes
Todos.propTypes = {
todos: PropTypes.array.isRequired
};
const mapStateToProps = state => ({
todos: state.todosReducer.todos
});
export default connect(mapStateToProps)(Todos);
TodoItem.js:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { checkTodo } from "../actions/todosAction";
class TodoItem extends Component {
onChange = (e, id) => {
console.log(this.props.todo.completed.toString()); // this also prints fine the updated data
this.props.checkTodo(id); // dispatches an action to the reducer to toggle the todo.completed with the right todo.id
};
render() {
console.log('rendering'); // this is the problem - this line calls only in the first rendering, but not when state changes
let { id, title, completed } = this.props.todo;
return (
<div className={completed ? "TodoItemDone" : "TodoItem"}>
<p>
<input
className="todo_cb"
type="checkbox"
onChange={e => this.onChange(e, id)}
checked={completed ? "checked" : ""}
/>
{id}) {title}
</p>
</div>
);
}
}
// PropTypes
TodoItem.propTypes = {
todo: PropTypes.object.isRequired
};
const mapDispatchToProps = dispatch => ({
checkTodo: todo => dispatch(checkTodo(todo))
});
const mapStateToProps = state => ({});
export default connect(
null,
mapDispatchToProps
)(TodoItem);
I noticed that if I do pass a mapStateToProps in child comp it is re-rendering, like this:
const mapStateToProps = state => ({
some_prop: state
});
I understand the if I use mapStateToProps in the child it re-renders but I don't need anything directly from the state in child, the parent does this.
It makes some sense but my todos are stored in an Array in the state and I am mapping over them as you see in the parent component, so I can't map a specific todo from this array to the component props (how could I distinguish each element in the array to map to the prop?).
I am very confused.
I read that component re-renders when state or his props change. Inside the child component the props do change because the parent re-renders and it iterates the todos again and return the component with new props.
Maybe it's not the way to pass the todos to the components but I still don't understand how come the props changes and render() is not called.
Thank you very much!
Edit 1:
I connected the checkTodo action to the parent component and passed the function with props and it works just fine.
Still I don't understand why before the child component haven't re-rendered with the previous code...
Edit 2:
Actually I just lied, it does not work. I forgot to remove mapStateToProps which I said worked, so I am back to square one.
Edit 3:
Solved with by calling forceUpdate(). Still can't understand why it happened.

Related

Pass the data from child functional component to parent class component

I am quite new to react and I have been trying to figure out how to pass the data from child function to parent class component. In my child, I only have a variable that stores username from the URL. But I can't seem to figure out how to pass it to the parent component.
Here is my child.
const GetUsername = () => {
const params = useParams();
const [param, getParam] = useState(params.name);
return <p>{params.name}</p>;
};
export blabla
I would like to know how I would have to access that params.name in my parent component.
Here is my parent.
export class test extends Component {
constructor(props) {
super(props);
this.data = React.createRef();
}
render() {
console.log(this.data);
return ({some code});
}
When I ran this, I got null. Thank you so much in advance for helping me!
In React, Data flows in one direction only in form of props from Parent to Child.
Either move the logic to fetch params name to parent and pass it to child as props or have a central data storage such as redux and dispatch an action from child to save the information there and fetch it from there in Parent.
If you want to pass a child to parent component data then use usecontext or use redux to pass the data I recommend you can use redux.
parent component is handling the state
import GetUsername from "./GetUserName"
import { useState } from 'react'
const Test = () => {
const [param, getParam] = useState('');
console.log(param)
return (
<div>
<GetUsername getParam={getParam}/>
</div>
)
}
export default Test
and the children GetUserName utilizes the getParam useState setter to set the state handled in its parent component Test
import { useEffect } from "react";
import { useParams } from "react-router-dom";
const GetUsername = ({getParam}) => {
const params = useParams();
useEffect(()=>{
getParam(params)
},[])
return <p>children component</p>;
};
export default GetUsername
you will see that the console.log in the Test component will output your param received in its child component GetUserName.
in case you don't want to pass the props inline, you can use useContext, or redux, or any other state management library like zustand, recoil, etc...
You can pass the setState hook as a prop from the parent to the child and when you have access to the data. Use the setState hook.
Parent Component:
export default function App() {
const [name, setName] = useState();
return (
<div className="App">
<h1>Hello {name}</h1>
<Child setName={setName} />
</div>
);
}
Child Component:
export default function Child({ setName }) {
const [param, getParam] = useState("blabla");
useEffect(() => {
setName(param);
}, []);
return (
<>
<input onChange={(e) => setName(e.target.value)} />
</>
);
}

Redux cant access state after added

i have a parent component and one child component (custom function component). in my child component i have event handler that will change the state (redux) from the parent. everything work fine when i call the addProduct function the state is added i can see in my redux tools product state is changed. But Why after i added i cant access that state (console.log (product._id)) i get a error message a product is null and one thing make me confused is i can access that state in my JSX from parent Component ({JSON.stringify(product)}) but not after i added.
here is my code
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {addProduct} from '../action/product';
const ChildComponent = ({onClick}) => {
return (
<button onClick={onClick}>Add</button>
)
}
const ParentComponent = ({product}) => {
const onNotFoundThenAddNew = () => {
addProduct ({name : 'new product'});
console.log(product._id);
}
return (
<Fragment>
{JSON.stringify(product)}
<ChildComponent onClick={onNotFoundThenAddNew}/>
</Fragment>
)
}
ParentComponent.propTypes = {
addProduct : PropTypes.func,
product : PropTypes.object
};
const mapStateToProps = (state) => ({
product : state.product.product
})
export default connect (mapStateToProps, {addProduct}) (ParentComponent)

React Redux Component passing props not re-render

I'm using React with react-redux package and I've created a Redux component.
import React from 'react'
import { connect } from 'react-redux'
class Prova extends React.Component {
constructor(props) {
super(props)
this.state = { ...props }
}
componentDidUpdate(prevProps) {
if (prevProps.errors !== this.props.errors) {
this.setState({
errors: this.props.errors
});
}
}
render() {
const props = this.props.errors ? <div>props: {Object.keys(this.props.errors).map(prop => <div className='props'>{prop}</div>)}</div> : <>NO ERRORS</>
return (
<div>
{props}
</div>
)
}
}
function mapState(state, ownProps) {
return {
}
}
const actionCreators = {
}
const connectedProva = connect(mapState, actionCreators)(Prova)
export { connectedProva as Prova }
I'm calling this component like this:
<Prova errors={this.state.errors} />
Where errors is stored and updated in the state of the Parent component.
The problem is that errors aren't updated in the store state of redux but in the state of the component and this value is passed to the Prova component as "simple" props. But updating the Parent state doesn't re-render the Prova component.
Is it possible that using a Redux component hide the props passed? they are readed only when the Prova component is created the first time.
Thanks
EDIT:
Finally I understood the problem.
In the parent component where I was updating the errors state I wasn't destroying the prev version of errors but filling with the new data.
Now it's working changing it to:
var errors = { ...prevErrors }
Thanks

How to pass props (that getting from Redux) from one Component to another (React/Redux)?

How to pass props (that getting from Redux) from WrapperComponent to InnerComponent (React/Redux)?
import React, { Component } from "react";
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import InnerComponent from "./InnerComponent ";
class WrapperComponent extends Component {
state = {
data: []
};
render() {
return (
<div>
<InnerComponent props={this.props} />
</div>
);
}
}
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps)(withRouter(WrapperComponent));
After rendered the WrapperComponent - the props are still not in.
Any LifeCicle Methods can't help to resolve it.
Is It Possible?
It is possible and it is recommended to do so, so you don't to need call all the HOC store each time via 'connect'. Call all concerned actions and reducers to your containers and pass them as props.
In this case, your reducer name is called data, you need to call it like this (I changed the props name to data, so you can call props.data to your child):
<InnerComponent data={this.props.data} />
Or you can pass all the props from the parents like this:
<InnerComponent {...this.props} />

React / Redux wait for store to update

I have a problem that a react component is rendering before the redux store has any data.
The problem is caused by the React component being rendered to the page before the existing angular app has dispatched the data to the store.
I cannot alter the order of the rendering or anything like that.
My simple React component is
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
// *** at this point I have the store in state prop
//but editorFlow array is not yet instanced, it's undefined
const tasks = this.props.state.editorFlow[0].flow.tasks
return (
<div>
Flow editor react component in main container
</div>
);
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
So how can I hold off the rendering until editorFlow array has elements ?
You can use Conditional Rendering.
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
// *** at this point I have the store in state prop
//but editorFlow array is not yet instanced, it's undefined
const { editorFlow } = this.props.state;
let tasks;
if (typeof editorFlow === 'object' && editorFlow.length > 0) {
tasks = editorFlow[0].flow.tasks;
}
return (
{tasks &&
<div>
Flow editor react component in main container
</div>
}
);
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
As far as I know, you can't.
the way redux works is that it first renders everything, then actions take place with some async stuff(such as loading data), then the store gets populated, and then redux updates the components with the new state(using mapStateToProps).
the lifecycle as I understand it is this :
render the component with the initial state tree that's provided when you create the store.
Do async actions, load data, extend/modify the redux state
Redux updates your components with the new state.
I don't think mapping the entire redux state to a single prop is a good idea, the component should really take what it needs from the global state.
Adding some sane defaults to your component can ensure that a "loading" spinner is displayed until the data is fetched.
In response to Cssko (I've upped your answer) (and thedude) thanks guys a working solution is
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
const { editorFlow } = this.props.state;
let tasks;
if (typeof editorFlow === 'object' && editorFlow.length > 0) {
tasks = editorFlow[0].flow.tasks;
}
if(tasks){
return (
<div>
Flow editor react component in main container
</div>
)
}
else{
return null;
}
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)

Resources