Trying to orient through the dark depths of Redux-React-API jungle - managed to fetch data from API and console.log it - but neither me nor my Google skills have managed to find out why it doesn't render.
React Components
Parent Component:
class Instagram extends Component {
componentWillMount(){
this.props.fetchInfo();
}
render() {
return (
<div className="container">
<div className="wrapper">
<InstagramPost />
</div>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ fetchInfo }, dispatch);
}
export default connect(null, mapDispatchToProps)(Instagram);
Child Component:
class InstagramPost extends Component {
render() {
console.log(this.props.info);
this.props.info.map((p,i) => {
console.log("PROPS ID: " + p.id);
})
return (
<div>
<h1>POSTS</h1>
<ul className="uls">
{
this.props.info.map((inf, i) =>
<li key={i}>{inf.id}</li>
)
}
</ul>
</div>
)
}
}
const mapStateToProps = ({ info }) => {
return { info }
}
export default connect(mapStateToProps)(InstagramPost);
Redux Action method:
const ROOT_URL = 'https://jsonplaceholder.typicode.com/posts';
export const fetchInfo = () => {
const request = axios.get(ROOT_URL);
return {
type: types.FETCH_INFO,
payload: request
};
}
Redux Reducer method:
export default function(state = [], action) {
switch (action.type) {
case FETCH_INFO:
return action.payload.data;
default:
return state;
}
}
The JSON file looks like this:
In the console - it works and I get my Objects:
The state is also updated:
But when I map over this.props.info, trying to render this.props.info.id, nothing is rendered on the page.. Incredibly thankful for any input!
Looks like your props aren't set on the initial render. I'm guessing your API call hasn't finished.
Try checking the the variable is set or is an array first:
Something like this:
class InstagramPost extends Component {
render() {
if(!this.props.info) return null
return (
<div>
<h1>POSTS</h1>
<ul className="uls">
{
this.props.info.map((inf, i) => {
return <li key={i}>{inf.id}</li>
})
}
</ul>
</div>
)
}
}
const mapStateToProps = ({ info }) => {
return { info }
}
export default connect(mapStateToProps)(InstagramPost);
Or you may want to check the length this.props.info.length > 0.
There were two problems. As Mayank Shukla pointed out, nothing was returned from the map callback because of the block braces ({}) without a return statement.
The other problem was in the reducer. As the redux state for info is an array of users, you need to replace the old state on FETCH_INFO rather than add the fetched array to the beginning of it. Otherwise, you're maintaining an array of arrays of users, which will grow by one on each fetch.
Note that you don't need any checks on this.props.info, as it will be initialized to [] by your reducer and [].map(f) == [].
For redux debugging I can very much recommend installing the Redux DevTools extension, as it will allow you to inspect all updates to the store. It needs a little setup per project, but that's well worth it.
Oh, and in the future, you might want to refrain from updating your question with suggestions from the comments/answers, as the question will no longer make sense to other visitors :-)
Related
Let's say I have a link that, when clicked, adds a random number to an array in my state object called queue. The entire state.queue array is displayed on my page and gets updated as we click the link. I currently have a link that is supposed to be doing something similar but after my reducer alters the state (adds a new item to the array), my page isn't (re-?)rendering my component and so the page does not update.
What should I be doing to show the entirety of state.queue on my page, including dynamically updating as I click the link? Here's what I have:
Channel.js
class Channel extends Component {
constructor(props) {
super(props);
this.state = {
queue: [],
// more initializations
};
...
}
...
render() {
return (
<div>
{/*This part is never called because this.state.queue never has anything in it! */}
{this.state.queue &&
this.state.queue.length > 0 &&
<div>
<ul>
{this.state.queue.map((queuedItem, i) =>
<li key={i}>{queuedItem}</li>
)}
</ul>
</div>}
<div>
<QResults
allQueryResults={this.state.queryState}
requestHandler={queueNewRequest}
/>
</div>
</div>
);
}
}
function mapStateToProps(state) {
const{queue} = state
return {queue}
}
function mapDispatchToProps(dispatch) {
return ({
queueNewRequest: (newRequestData) => { dispatch({type: newRequestData}) }
})
}
export default withRouter(connect(mapStateToProps , mapDispatchToProps )(Channel))
QResults.js
export default class QResults extends Component {
render() {
const {requestHandler} = this.props
return (
<ul>
{this.props.allQueryResults.items.map((trackAlbum, i) =>
<li key={i}>
<a href='#'
onClick={
() => requestHandler(trackAlbum.name)}>
Some link
</a>
</li>
)}
</ul>
)
}
}
actions.js
export const QUEUE_NEW_REQUEST = 'QUEUE_NEW_REQUEST'
export function queueNewRequest(newRequestInfo) {
return dispatch => {
dispatch({
type: QUEUE_NEW_REQUEST,
payload: newRequestInfo
})
}
}
reducers.js
import { combineReducers } from 'redux'
function reducer1(state = {}, action) {
...
}
function reducer2(state = {}, action) {
switch (action.type) {
case QUEUE_NEW_REQUEST:
return {
...state,
/*queue: [...state.queue,action.payload],*/ //compiler complains that 'TypeError: state.queue is not iterable'
queue:[action.payload]
}
default:
return state
}
}
const rootReducer = combineReducers({
reducer1,
reducer2
})
export default rootReducer
Once you connected the state object to the props with connect you will get the returned objects in the props. See this: https://react-redux.js.org/using-react-redux/connect-mapstate#return.
So, you just need to change this.state.queue to this.props.queue in your Channel.js file.
And your component is not rendering because it's not dependent on the changed props. So, doing the above-suggested change should solve the issue.
this.state.queue refers to the state which is local to your Channel component. It is not the redux state. You initialize this.state.queue to an empty array, and then you never change it (ie, you never call this.setState).
If you want to interact with the state in redux, that's where mapStateToProps comes in. Since you've got a mapStateToProps that looks like this:
function mapStateToProps(state) {
const{queue} = state
return {queue}
}
The Channel component will get a prop named queue, and you can interact with it via this.props.queue.
So, the fix is to update Channel to interact with the prop. Most likely you'll want to delete this.state.queue, as it doesn't seem to serve a purpose and is causing confusion due to its name.
{this.props.queue &&
this.props.queue.length > 0 &&
<div>
<ul>
{this.state.queue.map((queuedItem, i) =>
<li key={i}>{queuedItem}</li>
)}
</ul>
</div>
}
Additionally, the way you've set up the root reducer has your redux state looking something like this:
{
reducer1: {}, // not sure what this contains, since reducer 1 was omitted
reducer2: {
queue: [],
}
}
So if that's really what you want your redux state to look like, your map state to props will need to be updated to this:
function mapStateToProps(state) {
const {queue} = state.reducer2;
return {queue};
}
In addition to all the responses about changing this.state.queue to this.props.queue, I needed to do a few things. Initialize queue in my reducer2:
function reducer2(state = {queue:[]}, action) {
switch (action.type) {
case QUEUE_NEW_REQUEST:
return {
...state,
queue: [...state.queue,action.payload],
}
default:
return state
}
}
Change mapStateToProps in Channel.js:
function mapStateToProps(state) {
const{queue} = state.reducer2
return {queue}
}
I want run it using http get, but it not show nothing, Where is the error?. Angular http.get easier to get JSON and doing ngFor and show, but on React is little special. So, in conclusion I don't like do a simple "import data from './data.json'", I need load json from the cloud.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class App extends Component {
// 1.JSON
constructor(props) {
super(props);
this.state = {
items: [],
};
}
// 2. JSON
componentJSON() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => this.setState({ items: data.items }))
}
render() {
// this.componentJSON = this.componentJSON.bind(this);
this.setState({ items: data})
// 3. JSON
// const { items } = this.state;
return (
<Router>
<div className="App">
<ul>
{items.map(item =>
<li key={item.title}>
{item.title}
</li>
)}
</ul>
</div>
</Router>
);
}
}
export default App;
Working now!,
Thanks anyway friends!
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
items : []
};
// You should bind this object to componentWillMount method, other setState was not working
this.componentWillMount = this.componentWillMount.bind(this);
}
// This method is call before component will mounted
componentWillMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then( data => this.setState({ items : data }) );
}
render() {
const { items } = this.state;
return (
<ul>
{items.map(item =>
<li key={item.title}>
{item.title}
</li>
)}
</ul>
);
}
}
export default App;
In your function you are assuming that the context is the class, but its not, its not how js works, so when you are trying to use this.setState it would not work because the context of the function doesnt have any function called setState.
A simple way of solving this is binding the function to the class, by simply adding the following line in the ctor:
this.componentJSON = this.componentJSON.bind(this);
You need to call your componentJSON function.
It is best to do this within componentDidMount()
componentDidMount(){
this.componentJSON()
}
This will get called when the component is rendered in the browser.
It is a common mistake to call your API within componentWillMount() but this will make your API call happen twice.
As mentioned in my other comment, be careful about calling your API in your render function as it will mean that you API is called every time there is a re-render. A re-render happens after setting state and since you are setting state in your API call, it will cause an infinite loop
Use componentDidMount
componentDidMount ()
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => this.setState({ hits: data.hits }))
}
Let's say I have 2 <Logs/> components. They're only displayed when their parent <Block/> has been clicked. Each <Block/> only has one <Logs/> but there will be many <Blocks/> on the page.
class Block extends React.Component {
toggleExpanded() {
this.setState({
isExpanded: !this.state.isExpanded
});
}
render() {
let blockId = // ...
return (
<div>
<button type="button" onClick={() => this.toggleExpanded()}>Toggle</button>
{this.state.isExpanded && <Logs blockId={blockId} />}
</div>
);
}
}
export default Block;
As the <Logs/> is created, I want to get data from the server using Redux. There could be a lot of <Logs/> some day so I don't want to load in all data to begin with, only data as needed.
class Logs extends React.Component {
componentDidMount() {
if (!this.props.logs) {
this.props.fetchBlockLogs(this.props.blockId);
}
}
render() {
return (this.props.logs && this.props.logs.length)
? (
<ul>
{this.props.logs.map((log, i) => <li key={i}>{log}</li>)}
</ul>
)
: (
<p>Loading ...</p>
);
}
}
SupportLogs.defaultProps = {
logs: null
}
const mapStateToProps = (state) => ({
logs: state.support.logs
});
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchBlockLogs: (blockId) => {
dispatch(ActionTypes.fetchBlockLogs(blockId));
}
});
export default connect(mapStateToProps, mapDispatchToProps)(SupportLogs);
Currently, as I toggle one parent <Block/> closed and then re-opened, the data is remembered. That's a nice feature - the data won't change often and a local cache is a nice way to speed up the page. My problem is that as I toggle open a second <Block/>, the data from the first <Logs/> is shown.
My question is: how can I load in new data for a new instance of <Logs/>?
Do I have to re-load the data each time and, if so, can I clear the logs property so that I get the loading animation back?
Maybe think about changing the logs in your store to be an object with logs loaded into it keyed by block id:
logs: {
'someBlockId': [
'some logline',
'some other logline'
]
]
Have your reducer add any requested logs to this object by block id, so as you request them they get cached in your store (by the way I really like that your component dispatches an action to trigger data fetching elsewhere as a side effect, rather than having the fetch data performed inside the component :) ).
This reducer assumes that the resulting fetched data is dispatched in an action to say it has received the logs for a particular block, {type: 'UPDATE_LOGS', blockId: 'nic cage', receivedLogsForBlock: ['oscar', 'winning', 'actor']}
logsReducer: (prevState, action) => {
switch(action.type)
case 'UPDATE_LOGS':
return Object.assign({}, prevState, {
[actions.blockId]: action.receivedLogsForBlock
})
default: return prevState
}
}
And in your component you can now look them up by id. If the required block is not defined in the logs object, then you know you need to send the action for loading, and can also show loader in meantime
class Logs extends React.Component {
componentDidMount() {
const {logs, blockId} = this.props
if (!logs[blockId]) {
this.props.fetchBlockLogs(blockId);
}
}
render() {
const {logs, blockId} = this.props
return (logs && logs[blockId]) ? (
<ul>
{logs[blockId].map((log, i) => <li key={i}>{log}</li>)}
</ul>
) : (
<p>Loading ...</p>
)
}
}
This has the added bonus of logs[blockId] being undefined meaning not loaded, so it is possible to distinguish between what needs to be loaded and what has already loaded but has empty logs, which your original would have mistaken for requiring a load
I have a a react component which is supposed to render a list of users from an API. I am calling my action creator in my lifecycle hook method like so:
componentWillMount() {
this.props.fetchUsers();
}
I have checked and data is being returned and dispatched to the reducers in the correct format. But when I try to render these users in my component, nothing is being shown on the page:
renderUser(user) {
return (
<div className="card card-block">
<h4 className="card-title">{user.name}</h4>
<p className="card-text">{user.company.name}</p>
<a className="btn btn-primary" href={user.website}>
Website
</a>
</div>
);
}
render() {
return (
<div className="user-list">{this.props.users.map(user => this.renderUser)}</div>
);
}
I get no errors or warnings, and my component is being rendered on the page after inspecting it, but is empty of content. When I try to do a few console logs, it seems that the componentWillMount() is being called before the reducers, and I don't know why. This means that my array of users after calling the fetchUsers() action creator is empty.
EDIT: Mapping the state and action creators to props
function mapStateToProps(state) {
return { users: state.users };
}
export default connect(mapStateToProps, actions)(UserList);
I import all my actions at the top of the component. My reducer adds the payload (array) from the action creator to the app state.
export default function(state = [], action) {
switch (action.type) {
case FETCH_USERS:
return [...state, action.payload.data];
}
return state;
}
My action creator simply makes a get request and sends it off to the reducers:
export function fetchUsers() {
const request = axios.get("https://jsonplaceholder.typicode.com/users");
return {
type: FETCH_USERS,
payload: request
};
}
EDIT 2:
Example of JSON data received:
You are not passing user data to the function
<div className="user-list">{this.props.users.map(user => this.renderUser(user))}</div>
You can try to insert a boolean like
loaded: false,
to your initialState in your reducer and put them in your action
case USERS_LOAD :
loaded: true,
users: action.users,
... users,
Then You can insert something liket that in your component
const Users = ({ loaded, users }) => {
if (!loaded) {
return <div>Is loading ...</div>
}
return (
<p>Voici les utilisateurs :</p>
{users.map(user => {
<li>{user}</li>}
);
};
Users.propTypes = {
users: PropTypes.object.isRequired,
loaded: PropTypes.bool.isRequired,
};
to give a start to call your datas. I hope it can help you ;-)
If u look at the lifecycle the first one is componentwillmount().
It is called before render.
refer
I'm working on application using react & redux and I need setProps but it's deprecated.
Take a look the error below:
Warning: setProps(...) and replaceProps(...) are deprecated. Instead,
call render again at the top level.
Uncaught Invariant Violation: setProps(...): You called setProps on
a component with a parent. This is an anti-pattern since props will
get reactively updated when rendered. Instead, change the owner's
render method to pass the correct value as props to the component
where it is created.
So how can I set the props? basically is to manage some tabs, take a look below my code:
export default React.createClass({
render: function() {
return (
<div className="demo-tabs">
<Tabs activeTab={this.props.activeTab}
onChange={(tabId) => this.setProps({ activeTab: tabId })} ripple>
<Tab>Stack</Tab>
<Tab>GitHub</Tab>
<Tab>Twitter</Tab>
</Tabs>
<section>
<div className="content">Content for the tab: {this.props.activeTab}</div>
</section>
</div>
);
}
});
Container
import React from 'react';
import TimeLine from './timeline';
import { connect } from 'react-redux';
import { getStackoverflow } from 'api/timeline';
const TimeLineContainer = React.createClass({
componentWillMount: function() {
getStackoverflow();
},
render: function() {
return (
<TimeLine {...this.props} />
)
}
});
const stateToProps = function(state) {
return {
timeline: state.timelineReducer.timeline
}
}
const dispatchToProps = function(dispatch) {
return {
onClick: () => {console.log('timeline was clicked')}
}
}
export default connect(stateToProps, dispatchToProps)(TimeLineContainer)
Reducer
var timelineInitialState = {
github: [],
gist: [],
stackoverflow: [],
twitter: [],
activeTab: 2
}
export default function(state = timelineInitialState, action) {
switch (action.type) {
case 'GET_GITHUB':
//TODO: implement.
break;
case 'GET_GIST':
//TODO: implement.
break;
case 'GET_STACKOVERFLOW':
var stackoverflowState = Object.assign({}, state)
stackoverflowState.stackoverflow = action.stackoverflow;
return stackoverflowState;
break;
case 'GET_TWITTER':
//TODO: implement.
break;
default:
return state;
}
}
It looks like you dived into using Redux without first getting a firm grip of React. I wouldn’t recommend doing this because you appear to be somewhat confused now.
Thinking in React is a great guide and I recommend you to go through it first and get comfortable with the idea of state ownership in React, before using Redux.
In React, things that change over time (“state”) are always “owned” by some component. Sometimes it’s the same component that uses this state for rendering. Sometimes many components need to be synchronized so the state gets “hoisted” to some parent component that can manage them all, and pass that information via props. Whenever state changes, they all get updated.
The important part here is that props are meant to be received by parent. If a component wants to change its own props, it is a symptom of a problem:
Either you should have used state for this component, so you can call setState
Or your component needs to access a callback prop like onChange so it can “ask” the value to be changed
In the first case, your code would look like this, and it would be valid React:
export default React.createClass({
getInitialState: function() {
return { activeTab: 0 }
},
render: function() {
return (
<div className="demo-tabs">
<Tabs activeTab={this.state.activeTab}
onChange={(tabId) => this.setState({ activeTab: tabId })} ripple>
<Tab>Stack</Tab>
<Tab>GitHub</Tab>
<Tab>Twitter</Tab>
</Tabs>
<section>
<div className="content">Content for the tab: {this.state.activeTab}</div>
</section>
</div>
);
}
});
However, you can continue the pattern you are already using with <Tabs> component inside, and hoist the state even higher. Then your component would need to accept an onChange prop which it would pass down to <Tabs>. Now it doesn’t know how the state is updated, and doesn’t hold the state:
export default React.createClass({
render: function() {
return (
<div className="demo-tabs">
<Tabs activeTab={this.props.activeTab}
onChange={this.props.onChange} ripple>
<Tab>Stack</Tab>
<Tab>GitHub</Tab>
<Tab>Twitter</Tab>
</Tabs>
<section>
<div className="content">Content for the tab: {this.props.activeTab}</div>
</section>
</div>
);
}
});
Neither approach is better or worse, they are used for different purposes. The first one is more convenient when the state is irrelevant to the rest of the app, the second one is convenient when other distant components happen to need it too. Make sure you understand the tradeoffs of both approaches.
Even with the second approach, something’s got to hold the state. It could be another component, or (and this is where Redux comes in) it could be a separate data storage like a Redux store. In this case, rather than supply onChange from a parent component, you would use connect() to bind an onChange prop it injects to dispatching a Redux action:
const mapStateToProps = (state) => {
return {
activeTabId: state.activeTabId
}
}
const mapDispatchToProps = (dispatch) => {
return {
onChange: (tabId) => dispatch({ type: 'SET_ACTIVE_TAB', tabId })
}
}
And in your reducers, you can handle this action:
const activeTabId = (state = 0, action) => {
switch (action.type) {
case 'SET_ACTIVE_TAB':
return action.tabId
default:
return state
}
}
const reducer = combineReducers({
activeTabId,
// other reducers
})
const store = createStore(reducer)
In neither case we need setProps. You can either:
let the component own the state and use setState,
let it accept the activeTabId and onChange props and manage them from a component higher in the tree that would use setState, or
you can move the state handling completely out of the components into something like Redux, but your components would still accept activeTabId and onChange as props.
Provide onTabChange callback to Timeline component from Container.
Container:
const dispatchToProps = function(dispatch) {
return {
onClick: () => {console.log('timeline was clicked')},
onTabChange: (tabId) => { setActiveTabAction(tabId) }
}
}
const stateToProps = function(state) {
return {
timeline: state.timelineReducer.timeline,
activeTab: state.timelineReducer.activeTab
}
}
Timeline component:
export default React.createClass({
render: function() {
return (
<div className="demo-tabs">
<Tabs activeTab={this.props.activeTab}
onChange={this.props.onTabChange} ripple>
<Tab>Stack</Tab>
<Tab>GitHub</Tab>
<Tab>Twitter</Tab>
</Tabs>
<section>
<div className="content">Content for the tab: {this.props.activeTab}</div>
</section>
</div>
);
}
});
Presentational components (like your Timeline component) usually shouldn't manage application state. All data and callbacks they must receive by props.
I suggest you to read article about Presentational and Container components:
https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.sijqpzk93