I want to make simple CRUD functionality which should be the same in my web app, Android app and iOS app.
Therefore, I have in node.js made an API from which I can retrieve a paginated list of all documents, a detail page of each document, and post/put requests to delete and update.
Now I am able to check for administrator rights and other things at these routes. Is this the correct approach?
Now I want to traverse this paginated list and present it with reactjs.
I guess it could be done with
import React from 'react';
import ReactDOM from 'react-dom';
export default class ItemLister extends React.Component {
constructor() {
super();
this.state = { items: [] };
console.log('test');
}
componentDidMount() {
fetch('/api/events/').then(result => {
this.setState({items:result.json()});
});
}
render() {
return (
<div>
<div>Items:</div>
{ this.state.items.map(item => { return <div>{http://item.name}</div>}) }
</div>
);
}
}
ReactDOM.render(<ItemLister/>, document.getElementById('hello-world'));
but nothing happens (it doesn't even print anything in the console window as i tried in the constructor).
I have this script in a file /components/ItemLister.jsx and I have imported react-15.2.1 and react-dom-15.2.1. I have installed babel-cli, babel-preset-es2015, babel-preset-react and set
"presets": [
"es2015",
"react"
]
in my package.json.
Issue with {http://item.name}
Put :
{ this.state.items.map(item => <div>http://{item.name}</div>) }
NOT :
{ this.state.items.map(item => { return <div>{http://item.name}</div>}) }
Here with Sample code using SWAPI and showing results.
Full code: https://react-vzbqum.stackblitz.io
import React from 'react';
import ReactDOM from 'react-dom';
export default class ItemLister extends React.Component {
constructor() {
super();
this.state = { items: [] };
console.log('test');
}
componentDidMount() {
fetch('https://swapi.co/api/planets').then(res => res.json()).then(res => {
console.log(res.results);
if (res.results && res.results.length > 0) {
this.setState({items: res.results});
}
});
}
render() {
return (
<div>
<div>Items:</div>
{ this.state.items.map(item => { return <div>{`http://${item.name}`}</div>}) }
</div>
);
}
}
ReactDOM.render(<ItemLister/>, document.getElementById('hello-world'));
Your issue is at below line.
{ this.state.items.map(item => { return <div>{http://item.name}</div>}) }
As you using ES6 you no need to use return when returning on the same line. Replace the above code with below one...
{ this.state.items.map((item,index) => <div key={item.index}>http://{item.name}</div>)}
React recommends using key attribute with unique key value for better performance reconciliation when we insert dynamic nodes using map.
Related
When changing my id (/movie/:id), i'm re rendering my whole component. Sometimes i have to click 3 or 4 times on my like to have a change and sometimes i have only to click once(but im one component behind).
Here is my code :
import React from "react";
import "../styles/DetailFilm.css"
import {Link} from 'react-router-dom';
const API_IMAGES = 'https://image.tmdb.org/t/p/w500';
class DetailFilm extends React.Component {
constructor(props) {
super(props);
this.state = {
id: props.movie_id,
info: {},
recommandation: []
}
}
componentDidMount() {
const fetchData = async () => {
//fetch api
this.setState({info: data,recommandation:data_recommandation_spliced })
}
fetchData();
}
componentWillReceiveProps(nextProps) {
console.log("RENDERING" + nextProps.movie_id)
const fetchData = async () => {
// fetch api
this.setState({id: nextProps.movie_id,info: data,recommandation:data_recommandation_spliced })
console.log("Rendered" + nextProps.movie_id)
}
fetchData();
}
render() {
return (
//css
{this.state.recommandation.map((movie) =>
<Link to={`/movie/${movie.id}`}>
<img src = {API_IMAGES + movie.poster_path} className="image-movie-genre"/>
</Link>
)}
)
}
}
export default DetailFilm;
Thanks for helping !
When adding JSX elements from an array, each one needs a unique key property so that React can keep track of necessary changes in the DOM. You need to add keys to your Link elements so that React will know to update them.
I found a solution which wasn't the one i was looking for at first.
I changed from using a Class to a function using useEffect avec id as param.
I want to do code-splitting manually using preact. Preact already splits code for routes, but I want to do it myself.
My use case is that I am building a tool where a user can add widgets to a dashboard. On the home page I only want to include the code for the widgets that the user has configured, not the ones the user has not used.
So I do not want to have the code for all widgets bundled in the bundle.js, but request it lazily when needed, when rendering the list of widgets.
I have attempted to use the async! syntax, which I saw in some old commits for the boiler plate, but that did not work.
A simplified example of my code
The configuration data
[{ "type": "notes", "title": "Widget 1}, { "type": "todo", "title": "Widget 2"}]
The render function of the list
const Grid = ({ widgets }) => (
<ul>
{widgets.map((widget) => <li key={widget.title}><Widget widget={widget} /></li>)}
</ul>
);
Widget component
Here I have a mapping from type to component:
import notes from widgets/notes;
import todo from widgets/todo;
class Widget extends Component {
widgetMap(widget) {
if (widget.type === 'notes') {
return notes;
}
if (widget.type === 'todo') {
return todo;
}
}
render ({ widget }) {
const widgetComponent = this.widgetMap(map);
return (
<div>
<h1>{widget.title}</h1>
<widgetComponent />
</div>
);
}
}
If you are using Preact X, it features <Suspense> and lazy which is same API React also uses. More about it in depth you can read here: https://reactjs.org/docs/concurrent-mode-suspense.html
Your example, modified would look like this (code adjusted from here):
import { Suspense, lazy } from `preact/compat`;
const notes = lazy(() => import('./widgets/notes'));
const todo = lazy(() => import('./widgets/todo'));
class Widget extends Component {
widgetMap(widget) {
if (widget.type === 'notes') {
return notes;
}
if (widget.type === 'todo') {
return todo;
}
}
render ({ widget }) {
const widgetComponent = this.widgetMap(map);
return (
<Suspense fallback={<div>loading...</div>}>
<div>
<h1>{widget.title}</h1>
<widgetComponent />
</div>
</Suspense>
);
}
}
For older version of Preact, you can put together async loading HOC yourself as long as you have Babel or some other transpiler set up to handle dynamic module loading
export default asyncComponent = (importComponent) => {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = { component: null };
}
async componentDidMount() {
const { default: component } = await importComponent();
this.setState({ component });
}
render() {
const Component = this.state.component;
return Component ? <Component {...this.props} /> : <div>loading...</div>;
}
}
return AsyncComponent;
}
I'm using Meteor, React, react-meteor-data, and React Router. I am linking to a page and adding an /:id, which I am then trying to use to query the database and build out the component with the returned data.
The problem is the initial query returns an empty array. I can then see in my render method and componentWillReceiveProps that it returns the data I expect a moment later. The problem is that my component does not re-render. I am using withTracker because I want the component to update and re-render every time the database changes in the targeted Collection.
Here is the React code on the client-side:
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
And here is the publication in 'imports/api/activeGames.js':
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const ActiveGames = new Mongo.Collection('activeGames');
if (Meteor.isServer) {
Meteor.publish('game', function (id) {
check(id, String);
return ActiveGames.find({ _id: id });
});
Meteor.publish('activeGames', function activeGamesPublication() {
return ActiveGames.find();
});
}
Here is a screenshot of the output I'm getting, with console logs to track the pertinent life cycle methods.
I believe it's a simple matter of doing something with the listLoading prop.
For instance, your Game component could be:
class Game extends Component {
constructor(props){
super(props);
}
renderGames = () => {
return this.props.games.map(g => {
return (
<li>{g.title}</li>
)
})
}
render() {
return (
<ul>
{this.props.listLoading ? <span>Loading...</span> : this.renderGames()}
</ul>
);
}
}
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
This is how I handle the time between subscribing and receiving data.
Hope it helps, though I'm sure you found a solution by now ;)
I have done this before and I am providing a key, but for some reason I am still getting the error. I am a bit new to react so it could be a silly mistake somewhere.
Edit: Error is the each child in an array should have a unique key
Here is the code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import _ from 'lodash';
class BooksList extends Component {
renderList() {
return _.map(this.props.books, book => {
return (
<div key={book.id}>
<h2>{book.title}</h2>
<h3>{book.description}</h3>
<h4>{book.price}</h4>
</div>
);
});
}
render() {
return (
<div>
{this.renderList()}
</div>
);
}
}
function mapStateToProps(state) {
return {
books: state.books
}
}
export default connect(mapStateToProps)(BooksList);
Edit:
I tried binding this.renderList, in the constructor, but still have the same issue:
constructor(props) {
super(props);
this.renderList = this.renderList.bind(this);
}
Your issue is you are not loading the books before the map happens initially.. so you have invalid data initially.
Initially this.props.books is this
then you load the books and it turns to this.
The fix for this is to only render when you have a valid object. Use the lodash isEmpty check for an object... use a forEach so you dont return empty in an array (less items to hold in array). and then just return that array at the end :)
renderList() {
const elems = [];
_.forEach(this.props.books, book => {
if (_.isEmpty(book)) return;
elems.push(
<div key={book.id}>
<h2>{book.title}</h2>
<h3>{book.description}</h3>
<h4>{book.price}</h4>
</div>
);
});
return elems;
}
I've been having an issue all day that has been driving me nuts. My project is basically building a chatbot with the React Rails gem, with a top level Alt Container, main component and a child component.
I'm having issues getting the onClick handlers in the child component to fire under specific circumstances.
The problem
I can not get the onClick function of the ExampleChildComponent component to fire when running in React Rails and if the component is being rendered inside a conditional statement which uses an Alt js store.
The app works perfectly fine when hosted with Webpack, but using the React-Rails gem, Babelify, and Browserify I get the above issue.
Project description
I'm using the following React related technologies:
Babelify
React
React Rails
Webpack
Alt
I'm developing side by side with Webpack and Rails. I work in Webpack and then switch over to Rails to make sure everything works. My project works 100% perfectly using Webpack, React hot loader and HTML Webpack Plugin but breaks upon being run by Rails.
I have a top level component which wraps everything into an Alt Container.
import React from 'react';
import AltContainer from 'alt/AltContainer';
import ExampleStore from './stores/ExampleStore';
import ExampleActions from './actions/ExampleActions'
import ExampleComponent from './components/ExampleComponent';
export default class ChatContainer extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
ExampleStore.fetchSurvey();
}
render() {
return(
<AltContainer
stores={{ExampleStore: ExampleStore}}
actions={{ExampleActions: ExampleActions}}>
<ExampleComponent/>
</AltContainer>
);
}
}
In my main ExampleComponent I have the following code, which swaps in and out components depending on my Alt store's state:
import React from 'react';
import ExampleChildComponent from './ExampleChildComponent';
export default class ExampleComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
const isReady = this.props.ExampleStore.isReady;
return <div>{isReady ? <ExampleChildComponent/>: 'Loading...'}</div>;
}
}
An example child component type would be the following FaceMood component:
import React from 'react';
export default class ExampleChildComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
console.log('Example child component clicked');
}
render() {
return(
<div onClick={this.handleClick}>
Example Child Component
</div>
);
}
}
This all works perfectly fine in Webpack but in React-Rails the handleClick function is not being executed when I click on the <div> in the ExampleChildComponent.
Interestingly, I managed to narrow the issue down to the following line of code in my ExampleComponent component:
return <div>{isReady ? <ExampleChildComponent/>: 'Loading...'}</div>;
Now if I don't refer to the this.props.ExampleStore.isReady at ALL, the onClick event handler works fine:
return <div><ExampleChildComponent/></div>
This is of course not ideal, as I want my Pane to show different component types depending on the current progress in the chatbot conversation.
I'm pretty much at my wits end so I hope some of you can help me!
Extra information
The prop this.props.ExampleStore.isReady is set using Alt.js' async datasource methods. Its a javascript promise that sets the isReady flag to true after the data fetching has been completed.
Here is the code for the Flux actions:
import alt from '../utils/Alt';
class ExampleActions {
fetchSurvey() {
this.dispatch();
}
updateSurvey(survey) {
this.dispatch(survey);
}
fetchSurveyFailed(errorMessage) {
this.dispatch(errorMessage);
}
}
export default alt.createActions(ExampleActions);
Now the code for the Flux store:
import alt from '../utils/Alt';
import ExampleActions from '../actions/ExampleActions';
import ExampleSource from '../sources/ExampleSource';
class ExampleStore {
constructor() {
this.isReady = false;
this.bindListeners({
handleFetchSurvey: ExampleActions.FETCH_SURVEY,
handleUpdateSurvey: ExampleActions.UPDATE_SURVEY,
handleFetchingSurveyFailed: ExampleActions.FETCH_SURVEY_FAILED
});
this.registerAsync(ExampleSource);
}
handleFetchSurvey() {
}
handleUpdateSurvey() {
this.isReady = true;
}
handleFetchingSurveyFailed(errorMessage) {
}
}
export default alt.createStore(ExampleStore, 'ExampleStore');
Now the code for the flux datasource:
import ExampleActions from '../actions/ExampleActions';
const MockQuestions = [
{
}
];
const ExampleSource = () => {
return {
fetchSurvey() {
return {
remote() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (true) {
resolve(MockQuestions);
} else {
reject('Fetch failed');
}
}, 500);
});
},
local() {
return null;
},
loading: ExampleActions.fetchSurvey,
success: ExampleActions.updateSurvey,
error: ExampleActions.fetchSurveyFailed
};
}
};
}
export default ExampleSource;
The onClick() function can be seen in the react dev tools app also, its just not executing it!
Screenshot