i am making a simple todo app on react the app was working fine when i store my data in the predefined object.
but now am i getting my data from a link (rest) using ajax , getting problem with this ,
path- pages/todo.js
import React from "react";
import Todo from "../components/Todo";
import * as TodoActions from "../actions/TodoActions.js";
import TodoStore from "../stores/TodoStore";
export default class Settings extends React.Component {
constructor(){
super();
this.getTodos=this.getTodos.bind(this);
this.state={
todos: TodoStore.getAll(),
};
console.log("STATE",this.state.todos);
}
componentDidMount(){
TodoStore.on("load",this.getTodos);
}
getTodos()
{
this.setState({
todos:TodoStore.getAll(),
});
}
reloadTodos(){
TodoActions.reloadTodos();
}
render() {
const {todos}=this.state;
const TodoComponents=todos.map((todo)=>{
return <Todo key={todo.id} {...todo}/>;
});
return (
<div>
<button onClick={this.reloadTodos.bind(this)}>Reload!!</button>
<h1>TODO.net</h1>
<ul>{TodoComponents}</ul>
</div>
)
}
}
path -stores/Todo
import {EventEmitter} from "events";
import * as $ from "jquery";
import dispatcher from "../Dispatcher";
class TodoStore extends EventEmitter
{
constructor(){
super()
this.todos=[];
}
createTodo(text)
{ const id=Date.now();
this.todos.push({
id,
text,
complete:false
});
this.emit("change");
}
getAll(){
$.ajax({
type: 'GET',
url: 'http://rest.learncode.academy/api/chitranks/todo',
success: function(data) {
console.log("here",data);
this.todos=data;
window.todos=this.todos;
}.bind(this),
error: function() {
alert('error GET connecting to REST');
}.bind(this)
});
return this.todos;
}
handleActions(action) {
switch(action.type){
case "CREATE_TODO":{
this.createTodo(action.text);
}
case "RECEIVED_TODOS":{
this.todos=action.todos;
this.emit("change");
}
}
}
}
const todoStore=new TodoStore;
dispatcher.register(todoStore.handleActions.bind(todoStore));
window.dispatcher=dispatcher;
export default todoStore;
when i type in console todos i can see the data, but it is not rendering (shows undefined)
and also in pages/todo.js file the object is undefined
Since AJAX is asynchronous you can't return the actual data from response. Instead use callbacks to obtain data from response.
Here is the updated code
import React from "react";
import Todo from "../components/Todo";
import * as TodoActions from "../actions/TodoActions.js";
import TodoStore from "../stores/TodoStore";
export default class Settings extends React.Component {
constructor(){
super();
this.getTodos=this.getTodos.bind(this);
this.state={
todos: []
};
console.log("STATE",this.state.todos);
}
componentWillMount(){
this.getTodos();//directly hydrating store with todos.
}
getTodos()
{
var _this = this; //context saved to another variable to use this in anonymous function as callback
TodoStore.getAll(function(todos){
_this.setState({
todos: todos,
}, function(){ console.log("updated TODOS::", _this.state.todos)});
});
}
reloadTodos(){
TodoActions.reloadTodos();
}
render() {
const {todos}=this.state;
const TodoComponents=todos.map((todo)=>{
return <Todo key={todo.id} {...todo}/>;
});
return (
<div>
<button onClick={this.reloadTodos.bind(this)}>Reload!!</button>
<h1>TODO.net</h1>
<ul>{TodoComponents}</ul>
</div>
)
}
}
And your store
import {EventEmitter} from "events";
import * as $ from "jquery";
import dispatcher from "../Dispatcher";
class TodoStore extends EventEmitter{
constructor(){
super()
this.todos=[];
}
createTodo(text){
const id=Date.now();
this.todos.push({
id,
text,
complete:false
});
this.emit("change");
}
getAll(callback){ //call this on componentWillMount() (required)
$.ajax({
type: 'GET',
url: 'http://rest.learncode.academy/api/chitranks/todo',
success: function(data) {
console.log("here",data);
this.todos=data; //store gets hydrated with todos
if(typeof callback == "function"){
callback(data) //giving data to callback
}
window.todos=this.todos; //not necessary other than for checking in console
}.bind(this),
error: function() {
alert('error GET connecting to REST');
}.bind(this)
});
return this.todos; //not the proper palce to retutn
}
handleActions(action) {
switch(action.type){
case "CREATE_TODO":{
this.createTodo(action.text);
}
case "RECEIVED_TODOS":{
this.todos=action.todos;
this.emit("change");
}
}
}
}
const todoStore=new TodoStore;
dispatcher.register(todoStore.handleActions.bind(todoStore));
window.dispatcher=dispatcher;
export default todoStore;
Related
Below is the code of the Service
import { fetchEventSource } from '#microsoft/fetch-event-source';
export const AlertFetchEventSource = () => {
fetchEventSource('https://puppygifs.tumblr.com/api/read/json'),
{
onmessage(ev) {
const data = JSON.parse(ev.data);
return data;
},
};
};
export default { AlertFetchEventSource };
index.tsx where I am making the call to this service but its not returning any data
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
import backendService from './services/backendService';
interface AppProps {}
interface AppState {
name: string;
}
class App extends Component<AppProps, AppState> {
constructor(props) {
super(props);
this.state = {
name: 'React',
};
console.log(backendService.AlertFetchEventSource());
}
render() {
return (
<div>
<Hello name={this.state.name} />
<p>Start editing to see some magic happen :)</p>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Given the library you're using is an event handler, it doesn't ever actually return data. You'd need to provide it a callback to execute when it receives events.
// you might want to specify a better type than `any`
type MessageHandler = (data: any) => void;
export const AlertFetchEventSource = (onEvent: MessageHandler) => {
fetchEventSource('https://puppygifs.tumblr.com/api/read/json'), {
onmessage(ev) {
const data = JSON.parse(ev.data);
onEvent(data);
},
}); // you had a typo here, missing ")"
};
and use it like
import { AlertFetchEventSource } from "./services/backendService";
// snip
AlertFetchEventSource(data => {
// handle event data, eg
console.log(data);
// or perhaps something like
this.setState(data);
});
I'm working my way learning some react+redux-thunk and I've put together a simple form that hits an API and retrieves some jokes. My core component and code:
containers/AsyncApp.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import {bindActionCreators} from 'redux';
import SearchJokes from '../components/SearchJokes';
import Jokes from '../components/Jokes';
import {fetchJokes} from '../actions';
class AsyncApp extends Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleInput = this.handleInput.bind(this)
this.state = {searchText: ''};
}
handleSubmit(e){
e.preventDefault();
//const {searchText} = this.props;
console.log('button clicked ' + this.state.searchText);
this.props.fetchJokes(this.state.searchText);
}
handleInput = (e) => {
this.setState({
searchText: e.target.value,
})
}
render(){
const { jokes, isFetching } = this.props
return(
<div>
<SearchJokes
handleSubmit={this.handleSubmit}
onChange={this.handleInput}
searchText={this.state.searchText}
/>
{jokes ? (<Jokes jokes={jokes}/>) : (<div></div>)}
</div>
)
}
}
function mapStateToProps(state) {
return {
isFetching: state.isFetching,
jokes: state.items
}
}
function mapDispatchToProps(dispatch) {
return{
fetchJokes: bindActionCreators(fetchJokes, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AsyncApp)
actions/index.js
export const REQUEST_JOKES = 'REQUEST_JOKES'
export const RECEIVE_JOKES = 'RECEIVE_JOKES'
function requestJokes(term) {
return {
type: REQUEST_JOKES,
term
}
}
function receiveJokes(term, json) {
return {
type: RECEIVE_JOKES,
term,
jokes: json.results.map(joke => joke)
}
}
export function fetchJokes(term) {
return dispatch => {
dispatch(requestJokes(term))
return fetch(`https://icanhazdadjoke.com/search?term=${term}`, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
}).then(response => response.json())
.then(json => dispatch(receiveJokes(term, json)))
}
}
reducers/index.js
import { combineReducers } from 'redux'
import {
REQUEST_JOKES,
RECEIVE_JOKES
} from '../actions'
function jokesBySearch(state = {}, action) {
switch(action.type){
case REQUEST_JOKES:
return Object.assign({}, state, {isFetching: true, items: []})
case RECEIVE_JOKES:
return Object.assign({}, state, {
isFetching: false,
items: action.jokes,
})
default:
return state
}
}
const rootReducer = combineReducers({
jokesBySearch,
})
export default rootReducer
The form component works, and I can see the json array returned as part of the action. But the value of this.props.jokes is empty or undefined in the console.log and I'm wondering how to populate it once the results from the API call are returned.
I see two problems in this code.
First you don't need to use combineReducers when you only have one reducer. Just keep it simple and add complexity only when absolutely necessary. Instead of combineReducers
export default jokesBySearch
If you do use combineReducers be mindful how it affects your state.
Second there is no need to use bindactioncreators here, just dispatch the action (and don't forget to pass the term).
function mapDispatchToProps(dispatch) {
return{
fetchJokes: (term) => dispatch(fetchJokes(term))
}
}
See here when you need to use bindactioncreators (pretty rare use case)
In general good way to lear a library like redux is just use the core functionality until you encounter problem and only then add complexity.
I am new to react and I'm trying to print out some data from a GET request and print them out but I'm getting Uncaught TypeError: this.props.todos.map is not a function - error.
If I change this.setState({data: data}) on the success function I can get the data in my console, but is there any quick and simple way to fix this to print the data?
App.js
import React, { Component } from 'react';
import $ from 'jquery';
import Todos from "./Components/Todos"
class App extends Component {
constructor(){
super();
this.state = {
todos:[]
}
}
getTodos(){
$.ajax({
type: "GET",
url: 'url',
dataType:'json',
headers: {
Authorization: "Basic " + btoa("username" + ":" + "password")
},
cache: false,
success: function(data){
this.setState({todos: data}, function(){
console.log(this.state);
});
}.bind(this),
error: function(xhr, status, err){
console.log(err);
}
});
}
componentWillMount(){
this.getTodos();
}
componentDidMount(){
this.getTodos();
}
render() {
return (
<div className="App">
<Todos todos={this.state.todos}/>
</div>
);
}
}
export default App;
Todos.js
import React, { Component } from 'react';
import TodoItem from './TodoItem';
class Todos extends Component {
render() {
let todoItems;
if(this.props.todos){
todoItems = this.props.todos.map(todo => {
return (
<TodoItem key={todo.code} todo={todo} />
);
});
}
return (
<div className="Todos">
<h3>Results:</h3>
{todoItems}
</div>
);
}
}
export default Todos;
TodoItem.js
import React, { Component } from 'react';
class TodoItem extends Component {
render() {
return (
<li className="Todo">
<strong>{this.props.todo.code}</strong>
</li>
);
}
}
export default TodoItem;
Snippet from the console log:
todos has to be an array always. So change setState like this in your success function.
this.setState({todos: (data.resources ? data.resources : [])}, function(){
console.log(this.state);
});
You also need to bind the getTodos function to reference the scope of the class inside it. So bind it inside constructor :
this.getTodos = this.getTodos.bind(this)
First thing it useless to call this.getTodos() twice just call it in the componentDidMount() methode, one more thing that you need to be aware of , the component TodoItem.js will render no mather the value of this.props.todo.code to avoid the issue you need added this into your TodoItem.js
class TodoItem extends Component {
render() {
if(!this.props.todo){
<div>Loading</div>
}
return (
<li className="Todo">
<strong>{this.props.todo.code}</strong>
</li>
);
}
}
the Array.prototype.map() expect an array so if you try to pass an undefined value to it , it will automatically trow an error wish i believe is the case in your app
one more thing make sur that this.state.todo before trying my solution
I have the following code where inside my React component I'm using a third party component -- FineUploader in my case.
Upon uploading files, it calls its onComplete function. From here, I'm trying to call my action creators to handle post-upload processes but I'm unable to access my props or actions from there because this is all outside of my component.
Up to that point, everything is working. I'm able to call uploader instance from my component and upload the file to my Azure Blob Storage and get the fileName and blobName once the upload is completed.
It's funny that I'm stuck at the easier part!
Here's my component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import FineUploaderAzure from 'fine-uploader-wrappers/azure'
// Components
import Gallery from './gallery/index';
// Actions
import * as myActions from '../myActions';
// Instantiate FineUploader
const uploader = new FineUploaderAzure({
options: {
cors: {
expected: true,
sendCredentials: false
},
signature: {
endpoint: 'http://localhost:123/getsas'
},
request: {
endpoint: 'https://myaccount.blob.core.windows.net/my-container'
},
callbacks: {
onComplete: function (id, name, responseJSON, xhr) {
const fileName = uploader.methods.getName(id);
const blobName = uploader.methods.getBlobName(id);
// I now need to call my action creator to handle backend stuff
// Or I can call the handleFileUpload function inside my component.
// How do I access either my action creator or handleFileUpload function from here?
}
}
}
})
class FileUploader extends Component {
constructor(props) {
super(props);
this.handleFileUpload = this.handleFileUpload.bind(this);
}
handleFileUpload(fileName, blobName) {
debugger;
}
render() {
return (
<Gallery uploader={uploader} />
)
}
}
function mapStateToProps(state, ownProps) {
return {
}
}
function mapDispatchToProps(dispatch, ownProps) {
return {
actions: bindActionCreators(myActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(FileUploader)
I came up with the following approach that works. I'm not sure if this is the best way or there's a more elegant approach. I won't accept my answer as the correct one and let everyone post their comments and votes.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import FineUploaderAzure from 'fine-uploader-wrappers/azure'
// Components
import Gallery from './gallery/index';
// Actions
import * as myActions from '../myActions';
// Instantiate FineUploader
const uploader = new FineUploaderAzure({
options: {
cors: {
expected: true,
sendCredentials: false
},
signature: {
endpoint: 'http://localhost:123/getsas'
},
request: {
endpoint: 'https://myaccount.blob.core.windows.net/my-container'
}
}
})
class FileUploader extends Component {
constructor(props) {
super(props);
this.handleFileUpload = this.handleFileUpload.bind(this);
}
componentDidMount() {
uploader.on('complete', (id, name, responseJSON, xhr) => {
const originalName = uploader.methods.getName(id);
const blobName = uploader.methods.getBlobName(id);
this.handleFileUpload(originalName, blobName);
}
}
handleFileUpload(fileName, blobName) {
// Received fileName and blobName. We can call our actions creators here.
this.props.actions.someAction(fileName, blobName);
}
render() {
return (
<Gallery uploader={uploader} />
)
}
}
function mapStateToProps(state, ownProps) {
return {
}
}
function mapDispatchToProps(dispatch, ownProps) {
return {
actions: bindActionCreators(myActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(FileUploader)
After upgrading to Meteor 1.5 from 1.4, createContainer function from react-meteor-data gives the following error:
Uncaught TypeError: Super expression must either be null or a function, not undefined
at exports.default (modules.js?hash=fb99b6a…:1144)
at ReactMeteorData.jsx:6
at ReactMeteorData.jsx:6
at createContainer (createContainer.jsx:16)
at AppContainer.jsx (AppContainer.jsx:8)
AppContainer.jsx:
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { createContainer } from 'meteor/react-meteor-data';
import App from '../layouts/App.jsx';
export default AppContainer = createContainer(props => {
return {
currentUser: Meteor.user(),
};
}, App);
App file below, in constructor i am performing super(props) however error is still thrown
App.jsx:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
menuOpen: false,
showConnectionIssue: false,
headerTitle: null,
};
this.setHeaderTitle = this.setHeaderTitle.bind(this);
this.logout = this.logout.bind(this);
}
logout() {
Meteor.logout();
this.context.router.replace(`/home`);
}
render() {
... omitted render function
}
}
App.propTypes = {
user: React.PropTypes.object, // current meteor user
connected: React.PropTypes.bool, // server connection status
loading: React.PropTypes.bool, // subscription status
menuOpen: React.PropTypes.bool, // is side menu open?
children: React.PropTypes.element, // matched child route component
location: React.PropTypes.object, // current router location
params: React.PropTypes.object, // parameters of the current route
};
App.contextTypes = {
router: React.PropTypes.object,
};
export default App;
Personnally, for a container data i do like this (from masterchef Base, updated and working as well) :
/* AppContainer.js */
// import react and proptypes ...
import { Meteor } from 'meteor/meteor';
import container from '../../../modules/container';
// Some code for app layout and proptypes...
export default container((props, onData) => {
const user= Meteor.user(); // adapted for your case
onData(null, {
currentUser:user,
// and others data ...
});
}, App);
/* container.js */
import { compose } from 'react-komposer';
import getTrackerLoader from './get-tracker-loader';
export default function container(composer, Component, options = {}) {
return compose(getTrackerLoader(composer), options)(Component);
}
/* get-tracker-loader.js */
import { Tracker } from 'meteor/tracker';
export default function getTrackerLoader(reactiveMapper) {
return (props, onData, env) => {
let trackerCleanup = null;
const handler = Tracker.nonreactive(() => Tracker.autorun(() => {
trackerCleanup = reactiveMapper(props, onData, env);
}));
return () => {
if (typeof trackerCleanup === 'function') trackerCleanup();
return handler.stop();
};
};
}
Hope it ll be useful.
Try following snippet:
export default AppContainer = createContainer((props) => {
// do subscriptions if you have any
return {
currentUser: Meteor.user(),
};
}, App);
You might be missing super() in the App Component.