In my componentDidMount(), I am calling an actionCreator in my redux file to do an API call to get a list of items. This list of items is then added into the redux store which I can access from my component via mapStateToProps.
const mapStateToProps = state => {
return {
list: state.list
};
};
So in my render(), I have:
render() {
const { list } = this.props;
}
Now, when the page loads, I need to run a function that needs to map over this list.
Let's say I have this method:
someFunction(list) {
// A function that makes use of list
}
But where do I call it? I must call it when the list is already available to me as my function will give me an error the list is undefined (if it's not yet available).
I also cannot invoke it in render (before the return statement) as it gives me an error that render() must be pure.
Is there another lifecycle method that I can use?
Just do this, and in redux store please make sure that initial state of list should be []
const mapStateToProps = state => {
return {
list: someFunction(state.list)
};
};
These are two ways you can play with received props from Redux
Do it in render
render() {
const { list } = this.props;
const items = list && list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
return(
<div>
{items}
</div>
);
}
Or Do it in componentWillReceiveProps method if you are not using react 16.3 or greater
this.state = {
items: []
}
componentWillReceiveProps(nextProps){
if(nextProps.list != this.props.list){
const items = nextProps.list && nextProps.list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
this.setState({items: items});
}
}
render() {
const {items} = this.state;
return(
<div>
{items}
</div>
);
}
You can also do it in componentDidMount if your Api call is placed in componentWillMount or receiving props from parent.
Related
I have a modal displaying data that I'm receiving in props. When I open the modal, I should see select data displayed from my props. However, the modal is empty the first time I open it, and populates the second time.
If I go on to change the data in props, the modal stays the same on the first new click, and refreshes on the second new click.
I've tried forcing it with setTimeout, messing with combos of componentDidMount, componentDidUpdate, and other lifecycle methods, but nothing seems to work. I'm sure it has something to do with my using the prevData param in componentDidMount. But even thought react devtools shows this.state.pricesData updates, when I try rendering from state I get blanks every time. When I invoke a console log as a callback of setState, I get an empty array bracket in the console log (which I can expand to show all the correct array data, but I guess that's populated async after the log).
Here's the code:
import React, { Component } from "react";
import "../../App.css";
let explanations = [];
export default class ExplanationModal extends Component {
constructor(props) {
super(props);
this.state = {
pricesData: [],
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.pricesData !== prevState.pricesData) {
return { pricesData: nextProps.pricesData };
} else {
return null;
}
}
// to allow for async rendering
getSnapshotBeforeUpdate(prevProps) {
if (prevProps.pricesData !== this.state.pricesData) {
return this.state.pricesData;
}
}
componentDidMount = () => {
this.setState({ pricesData: this.props.pricesData }, () =>
console.log(this.state.pricesData)
);
};
componentDidUpdate = (prevData) => {
this.renderExp(prevData.pricesData);
};
renderExp = (data) => {
explanations = [];
data.forEach((set) =>
explanations.push({ title: set.titel, explanation: set.explenation })
);
};
onClose = () => {
this.props.hideModal();
};
render() {
return (
<div className="modal">
<div>
{explanations.map((item) => (
<span>
<h4>{item.title}</h4>
<p>{item.explanation}</p>
</span>
))}
</div>
<button onClick={this.onClose} className="update">
Close
</button>
</div>
);
}
}
you have to keep your explanation array in your state. then update the state when new data arrives. because react doesn't trigger a re render if you don't update the state .
your constructor should be
super(props);
this.state = {
pricesData: [],
explanations : []
};
}
and your renderExp function should be
renderExp = (data) => {
explanations = [];
data.forEach((set) =>
explanations.push({ title: set.titel, explanation: set.explenation })
);
this.setState({ explanations })
};
inside your render function
render() {
return (
<div className="modal">
<div>
{this.state.explanations.map((item) => (
<span>
<h4>{item.title}</h4>
<p>{item.explanation}</p>
</span>
))}
</div>
<button onClick={this.onClose} className="update">
Close
</button>
</div>
);
}
}
This way you will get the updated data when it arrives.
In component A I have a function that adds a object to my localforage:
localforage.setItem('trackedMovies', value)
In component B I map through the trackedMovies array that I grab from localforage:
getData = async () => {
const trackedMovies = await localforage.getItem<[]>('trackedMovies');
this.setState({
data: trackedMovies
})
}
componentDidMount = () => {
this.getData()
}
render() {
return (
<React.Fragment>
<h1>Dashboard</h1>
{ this.state.data.map(movie => {
return (
<span key={movie.id}>{movie.original_title}</span>
)
})}
</React.Fragment>
)
}
Initially this works fine. But if I add new objects in the trackedMovies array component B does not update so I have to manually refresh the page to see the newly added object.
How do I rerender component B to show the results in the updated trackedMovies array?
Try maintaining the elements of the localStorage in a state. So whenever you add an item in component A, add it to the state as well. Then you can pass it to B.
In the parent component, I receive data from the server and then map this data into a jsx format. Inside this mapping I have a child component and try to pass a value from state of parent to child as a property, however when I update state of this value, the render function for child is not executed.
Expected behavior: As a user I see a list of items. If I click on an item it should become as checked.
export class ReactSample extends React.Component {
constructor(props){
super(props);
this.state = {
items: [],
mappedItems: [],
selectedIds: [],
isSelected: false,
clickedTripId: null
};
this.toggleSelection = this.toggleSelection.bind(this);
}
componentWillMount(){
console.log("Component mounting")
}
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({selectedIds:
state.selectedIds.concat(id)}));
this.setState(() => ({clickedTripId: id}));
this.mapItems(this.state.items);
}
}
componentDidMount() {
const self = this;
MyService.getItems()
.then(res => {
self.setState(() => ({ items: res.allItems }));
self.setState(() => ({ mappedItems:
this.mapItems(res.allItems) }));
}
)
}
mapItems (items) {
return items.map(trip => {
return (
<li key={trip.id} onClick={(e) => (this.toggleSelection(trip.id,
e))}>
<span>{trip.title}</span>
<Tick ticked={this.state.clickedTripId}/>
<span className="close-item"></span>
</li>
);
});
}
getItems() {
}
render() {
return (
<div>
<a className="title">This is a react component!</a>
<Spinner showSpinner={this.state.items.length <= 0}/>
<div className="items-container">
<ul id="itemsList">
{this.state.mappedItems}
</ul>
</div>
</div>
);
}
}
export class Tick extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('RENDER');
return (<span className={this.props.ticked ? 'tick display' :
'tick hide' }></span>);
}
}
I see a couple issues.
In toggleSelection you aren't doing anything with the result of mapItems. This kind of bug would be much easier to avoid if you just remove mappedItems from state and instead just call mapItems within your render method.
The other issue is you are passing this.state.clickedTripId as the ticked property. I assume you meant to pass something more like this.state.clickedTripId === trip.id.
As Ryan already said, the problem was that mappedItems where not updated when toggleSelection was clicked. As it is obvious from the code mapItems returns data in jsx format. To update it I had to call this.setState({mappedItems: this.mapItems(this.state.items)}) which means that I call mapItems and then I assign the result to the state. In this case my list will be updated and Tick component will receive this.state.clickedItemId as a tick property. There is one more issue that needs to be done to make this code working:
this mapped list needs to be updated after this.state.clickedItemId is updated. The method setState is asynchronous which means that this.setState({mappedItems: this.mapItems(this.state.items)}) has to be called only after this.state.clickedItemId is updated. To achieve this, the setState method can receive a callback function as a second parameter. The code snippet is the following:
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({
clickedItemId: id,
selectedIds: state.selectedIds.concat(id)
}), () => this.setState({mappedItems: this.mapItems(this.state.items)}));
}
}
In this case, at the time the mapItems function is executed all data from the state that is needed here will be already updated:
mapItems (items) {
return items.map(item => {
return (
<li key={item.id} onClick={(e) => (this.toggleSelection(item.id, e))}>
<span>{item.title}</span>
<span>{this.state.clickedItemId}</span>
<Tick ticked={this.state.clickedItemId === item.id}/>
<span className="close-item"></span>
</li>
);
});
}
I'm unable to get the props values from Redux, I checked in reducers, store, action JS files everything working fine. Even I can see the state printed correctly in console.log("MAPSTOSTATEPROPS", state) inside mapsStateToProps function below.
But when I give this.props.posts to access the values I'm not getting them. Not sure, where I'm doing wrong here. Can someone help me?
posts.js:
class Posts extends Component {
componentWillMount() {
this.props.fetchPosts();
**console.log(this.props.posts); // gives empty array result**
}
render() {
return (
<div>
<h2>Posts</h2>
</div>
)
}
componentDidMount() {
console.log("DID mount", this.props);
}
}
Posts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.array
}
const mapsStateToprops = (state) => {
**console.log("MAPSTOSTATEPROPS", state)** // gives correct result from current state
return {
posts: state.items
}
};
export default connect(mapsStateToprops, {fetchPosts}) (Posts);
class Posts extends Component {
/* use did mount method because all will-methods
are will be deprecated in nearest future */
componentDidMount() {
/* on component mount you initiate async loading data to redux store */
this.props.fetchPosts();
/* right after async method you cannot expect to retrieve your data immediately so this.props.posts will return [] */
console.log(this.props.posts);
}
render() {
/* here you getting posts from props*/
const { posts } = this.props;
return (
<div>
<h2>Posts</h2>
/* here you render posts. After async method will be completed
and dispatch action with retrieved data that will cause rerender
of component with new props */
{posts.map(post => (<div>post.name</div>))}
</div>
)
}
}
Posts.propTypes = {
fetchPosts : PropTypes.func.isRequired,
posts : PropTypes.array
}
const mapsStateToprops = (state) => {
/* first call will log an empty array of items,
but on second render after loading items to store they will
appear in component props */
console.log("MAPSTOSTATEPROPS", state)
return {
posts : state.items || [];
}
This may be because of async nature of fetchPost() method.Try to log it inside render() and handle the async nature using promises.
So I have a firebase DB that looks like this:
I want to know how I can dynamically print every task in its own box with react even if more task are added.
Use a .map function. For example, check out this resource for the docs. I think this is what you're asking for:
https://facebook.github.io/react/docs/lists-and-keys.html
the example below is a simple todo list. I'm pulling the 'todos' object off this.props but that object can be substituted for however you're accessing the return object from firebase.
export class TodoList extends React.Component {
render () {
var {todos, showCompleted, searchText} = this.props;
var filteredTodos = TodoAPI.filterTodos(todos, showCompleted, searchText);
var renderTodos = () => {
if (filteredTodos.length === 0){
return (
<p className="container__message">Nothing To Do</p>
);
}
return filteredTodos.map((todo) => {
return (
<Todo key={todo.id} {...todo}/>
)
});
};
return (
<div>
{renderTodos()}
</div>
)
}
};
export default connect(
(state) => {
return state;
}
)(TodoList);
In this example, I call 'renderTodos()' which first checks if there's any todos and if there is, it uses javascripts .map method to iterate through all the todos returning another component (the individual todos - each individual todo is a component) -- that component displays each todo item. Hope that helps.