gothinkster / react-redux-realworld-example-app Goto Github PK
View Code? Open in Web Editor NEWExemplary real world application built with React + Redux
Home Page: https://react-redux.realworld.io
License: MIT License
Exemplary real world application built with React + Redux
Home Page: https://react-redux.realworld.io
License: MIT License
Do you think this project could benefit from having a unified style and linting implemented?
Tried to delete a comment, but this end ponit has a 404 error
https://conduit.productionready.io/api/articles/abc-wryjjz/comments/7743
More of a question rather than an issue, but following along with this file structure, where should the css styles be placed? Thanks!
There is quite a bit of mixup of action payloads in this project. Sometimes FSA (Flux Standard Actions) are used which use a payload
field, sometimes the payload is directly attached to the actions alongside type
. Would be nice if the style is consistent.
Step to reproduce:
Screenshot of the error:
First of all, thanks for this making this. It clarified a lot of react/redux concepts for me.
I opened this PR to point out that
/editor
page can be accessed without logging inMain reasons, in reducers/common.js
case 'ARTICLE_SUBMITTED':
const redirectUrl = `article/${action.payload.article.slug}`;
return { ...state, redirectTo: redirectUrl };
When ARTICLE_SUBMITTED
doesn't succeed, there is no article returned and an error is thrown. This might work better
case 'ARTICLE_SUBMITTED':
if(!action.error){
const redirectUrl = `article/${action.payload.article.slug}`;
return { ...state, redirectTo: redirectUrl };
}
break;
In reducers/editor.js
, when you're not logged in the server returns a 401 and an error occurs below (Since the payload is empty):
case 'ARTICLE_SUBMITTED':
return {
...state,
inProgress: null,
errors: action.error ? action.payload.errors : null
};
Changing the above to this works:
case 'ARTICLE_SUBMITTED':
return {
...state,
inProgress: null,
errors: action.error?
(action.payload? action.payload.errors: ["Unauthorized"]) : null
};
I've hardcoded "Unauthorized" here but I guess there might be much better approaches for this.
Just my two cents. Thanks for the amazing tutorial.
awesome project!
now the complaint :)
I have no way of telling which clicks create browser history and which don't.
Clicking tags doesn't.
Clicking Home / Settings / New Post does.
Clicking Your Feed / Global Feed doesn't.
As a result, when I hit the back button, I have no idea where i'm going back to
Hi!
the real-world example will not work with node 4. Because of related issue in react-scripts
Had to update to 6.0.0 on Mac machine.
Maybe it is obvious but I had to spend some time to get it. Is it possible to update readme for example?
I can volunteer for pull request if that's ok.
See
https://drive.google.com/file/d/0B3J7t9DdAoVXS0c5QjB6ckNYNVE/view?usp=sharing
I just cloned the repo, and started the server on local.
Btw, this looks awesome, thanks!
I have installed some third-party modules which contain custom CSS files. But the project seems
can't import these CSS files. I have tried
@import url("react-s-alert/dist/s-alert-default.css");
@import "~react-s-alert/dist/s-alert-default.css";
both failed.
That version of node js makes react-create-app fail silently. Basically you will npm start, and you will get just a couple of warnings.
If your node version is that, you need to update your react-scripts module to latest.
Would be nice if the maintainers had time to update the package.json, I can send a PR too.
store.subscribe( ()=> this.setState(store.getState()));
is being done in componentWillMount().
According to the React docs, "avoid introducing any side-effects or subscriptions in [componentWillMount]". Is Redux an exception? The next two sentences lead me to believe that I should perhaps subscribe within the constructor, but I'm not sure.
Why are we subscribing within componentWillMount() when the docs say to avoid doing so?
I just cloned the repo, ran npm install
, then npm run watch
and webpack failed to found history
package, which was not included inside the dependencies.
Hey guys, first of all huge thanks for putting down this great demo. I am using this example to bootstrap my real world project.
Just want to know the reason behind design decision to not use popular redux middlewares like redux-saga or redux-thunk and writing own middleware. I know its working well, but just curious. And is there possibility that using such pre-build, well-tested middlewares may reduce our code and improve its quality..?
And sorry I am missing something trivial, I am still new to React/Redux world so learning along.
I'm used to seeing a structure similar to:
/components
/containers
/redux
Where containers contain the logic and components are generally props-driven.
What were the reasons for not using containers, and for having multiple components in one file?
If you switch from "edit article" (/#/editor/test-55123) to "new post" (/#/editor) the input values are not removed.
Regarding the pagination at the bottom of the page:
Most pagination UIs allow you to Cmd+Click (on Mac) on a specific page number and open that page in a new tab. Likewise, the "Open in New Tab" option in Chrome does not work as expected. It opens a new tab, but the page defaults to 1 in the new tab:
The Popular Tags section of the UI exhibits similar behavior.
I think there is an error when somebody put their username with an "@" sign. For example, I cannot view my articles or favorited articles when clicking my username. I can see "undefined" at address bar so that may be the problem
./src/index.js
28:15-26 'react-router' does not contain an export named 'hashHistory'.
i googled a bit, apparently hashHistory is old, so what do we use now?
I saw in the readme there was an offer to release backend code for this demo .. Is that possible?
Thanks, again, this is great.
the following two do not exist in constants/actionType
they are from articleList.js in reducers
PROFILE_FAVORITES_PAGE_LOADED,
PROFILE_FAVORITES_PAGE_UNLOADED
} from '../constants/actionTypes';
How do i get clean url for my application, any suggestion!
What am getting
http://localhost:3001/#/site?_k=lye3pt
What i want
http://localhost:3001/site
When you write a comment and press post comment, the text area should clear and unfocus. I can just press the button over and over again :)
I was wondering if there could be a race condition.
Suppose I try to edit an article, Editor component si being mounted and EDITOR_PAGE_LOADED
event is dispatched.
The request is sent to server, but the server didn't respond yet. (1)
Now I go back to homepage, editor component is unmounted, EDITOR_PAGE_UNLOADED
is dispatched, the state related to editor component is cleared.
Now I try to edit another article, Editor component is mounted, EDITOR_PAGE_LOADED
is dispatched for another article.
The request is sent to server, and server responds instantly. (2)
Reducer for EDITOR_PAGE_LOADED
action triggered by second request is called. Everything cool still.
Now, the first request (1) is full-filled, and reducer for EDITOR_PAGE_LOADED
action is called, with information from first request, and in the editor I will see the contents for first article I tried to edit, instead of the second one.
Hm?
We should probably consider providing loading indicators to show off pending async requests.
Should be nice to see how the server-side rendering is handled
I deployed the django backend on Azure and then updated the URL in ajent.js (React frontend) to point to django backend. It does not proceed. Do I need to some more changes for this to work?
I followed the instructions on the readme, but got the below error.
Clone this repo
npm install to install all req'd dependencies
npm run watch to have webpack bundle the JS files into /bin/main.js, then run npm start
Uncaught ReferenceError: ArticleActions is not defined
http://localhost:4000/favicon.ico Failed to load resource: the server responded with a status of 404 (Not Found)
FIX: Add this line to ArticlePreview.js , at the top
import ArticleActions from './Article/ArticleActions';
There are many developer working with react redux paradigm, who streamline workflow with webpack. So, it would be nice if we can use webpack here.
I seem to be getting a problem if I try to create or update an article when I force an error, eg the form I submit has a blank body and description. The 422 response from the back-end is acknowledged, but the UI appears to continue to its next steps, eventually crashing with a "Cannot read property of slug" error.
Here's the 422 response received:
{"errors":{"description":["can't be empty"],"body":["can't be empty"]}}
and here's the UI console log:
POST http://192.168.1.75:3000/api/articles 422 (Unprocessable Entity)
middleware.js:33 ERROR Error: Unprocessable Entity
at PromiseRequest. (client.js:501)
at PromiseRequest.Emitter.emit (index.js:133)
at XMLHttpRequest.xhr.onreadystatechange (client.js:772) core.js:104
action ASYNC_END @ 17:04:08.438 core.js:116
prev state Object {article: Object, articleList: Object, auth: Object, common: Object, editor: Object…} core.js:120
action Object {type: "ASYNC_END", promise: Object} core.js:128
next state Object {article: Object, articleList: Object, auth: Object, common: Object, editor: Object…} core.js:104
action ARTICLE_SUBMITTED @ 17:04:08.440 core.js:116
prev state Object {article: Object, articleList: Object, auth: Object, common: Object, editor: Object…} core.js:120
action Object {type: "ARTICLE_SUBMITTED", payload: Object, error: true} core.js:124
error TypeError: Cannot read property 'slug' of undefined
at exports.default (common.js:40)
at combination (combineReducers.js:132)
at dispatch (createStore.js:179)
at index.js:104
at middleware.js:60
at middleware.js:46
at Object.dispatch (applyMiddleware.js:45)
at middleware.js:39 core.js:128
next state Object {article: Object, articleList: Object, auth: Object, common: Object, editor: Object…}
common.js:40 Uncaught (in promise) TypeError: Cannot read property 'slug' of undefined
at exports.default (common.js:40)
at combination (combineReducers.js:132)
at dispatch (createStore.js:179)
at index.js:104
at middleware.js:60
at middleware.js:46
at Object.dispatch (applyMiddleware.js:45)
at middleware.js:39
The odd thing is I don't appear to get this behaviour when running the demo at react-redux.realwold.io, even though the responses coming from the back-end appear essentially the same.
Default port for React app is conflicting with Node's default port 3000. It will be better to change it, because for Rails app 3000 is also default port.
Just look at angular projects with 4000+ port range.
Any ideas when the repo will be online?
Thanks,
I
Just looking for some place to put these.
I think understanding patterns/idioms is more important than reading a codebase, so I tried to extract all that I could find in this project. As well some of my own ideas snuck a little in (e.g. thinking of how to integrate Flow), Would like feedback on anything missed, ideally this describes how everything works in the project.
Enjoy:
File: index.js
Imports: agent.js
, store.js
, ./components/*
Renders routes pointing to their associated components:
<Provider store={store}>
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="login" component={Login} />
<Route path="register" component={Register} />
<Route path="editor" component={Editor} />
<Route path="editor/:slug" component={Editor} />
<Route path="article/:id" component={Article} />
<Route path="settings" component={Settings} />
<Route path="@:username" component={Profile} />
<Route path="@:username/favorites" component={ProfileFavorites} />
</Route>
</Router>
</Provider>
File: agent.js
Exports an object where each key is a "service" and a service has
methods that internally run a request:
get
put
post
delete
For example, Auth
:
const Auth = {
current: () =>
requests.get('/user'),
login: (email, password) =>
requests.post('/users/login', { user: { email, password } }),
register: (username, email, password) =>
requests.post('/users', { user: { username, email, password } }),
save: user =>
requests.put('/user', { user })
};
Thus, these services essentially take some options, map to a request, and
return the promise of that request. The general type could be:
type Service = {
[key: string]: (opts: any) => Promise<T>
}
As well, agent.js
locally stores a token which can be set via the exported
setToken
. As some config there is API_ROOT
.
File: store.js
Imports: reducer.js
, middleware.js
Fairly simple store setup, applies promiseMiddleware
before
localStorageMiddleware
, logger only on development.
File: middleware.js
Imports: agent.js
Intercepts all actions where action.payload
is a Promise. In which case it:
store.dispatch({ type: 'ASYNC_START', subtype: action.type })
action.payload.then
store.dispatch({ type: 'ASYNC_END', promise: res })
action.error = true
, store.dispatch({ type: 'ASYNC_END', promise: action.payload })
action
object: store.dispatch(action)
Runs after promiseMiddleware
. Intercepts REGISTER | LOGIN
and either
agent.setToken(token)
''
and does agent.setToken(null)
File: reducer.js
Imports: ./reducers/*.js
Uses combineReducers
to export a reducer where each key is the reducer
of the file with the same key.
ASYNC_START
and action.subtype
case 'ASYNC_START':
if (action.subtype === 'LOGIN' || action.subtype === 'REGISTER') {
return { ...state, inProgress: true };
}
action.errors
if it is there (see middleware)case 'REGISTER':
return {
...state,
inProgress: false,
errors: action.error ? action.payload.errors : null
};
case 'REGISTER':
return {
...state,
inProgress: false,
errors: action.error ? action.payload.errors : null
};
componentWillReceiveProps
somewhere)case 'REDIRECT':
return { ...state, redirectTo: null };
case 'LOGOUT':
return { ...state, redirectTo: '/', token: null, currentUser: null };
case 'ARTICLE_SUBMITTED':
const redirectUrl = `article/${action.payload.article.slug}`;
return { ...state, redirectTo: redirectUrl };
Most mapStateToProps
won't be mentionned, as there are fairly simple. Take
some objects, use them in render.
mapDispatchToProps
will be referred to as "handlers". Some will emerge as
common ones. Dispatching some specific handlers on some specific lifecylce
methods will also emerge as a pattern.
Handlers:
onLoad
onUnload
onSubmit
onClick
onX
onLoad
seems to be the most common one, used for any components that need ajax in
data into store into props into their render method (which is basically everything on
an SPA lol).
Patterns
onLoad
handlers pass a Promise or multiple promises via Promise.all
sending multiple leads to magic payload[0]
and payload[1]
in reducer (see reducers/article.js
)
pass a handler, e.g. onClickTag
as a prop to a child component. child
component then calls it with agent: props.onClickTag(tag, agent.Articles.byTag(tag))
. (does this only ever happen with a connected index.jsx
inside a folder?)
to render or not to render:
if (!this.props.data) {
component = <Loading /> // or perhaps null like in Header.js, ListErrors, EditProfileSettings in Profile
} else {
component = <Thing data={this.props.data} />
}
componentWillMount() {
if (this.props.params.slug) {
return this.props.onLoad(agent.Articles.get(this.props.params.slug));
}
this.props.onLoad(null);
}
componentWillReceiveProps
to call handlers if necessary, e.g. in Editor.js
:componentWillReceiveProps(nextProps) {
if (this.props.params.slug !== nextProps.params.slug) {
if (nextProps.params.slug) {
this.props.onUnload();
return this.props.onLoad(agent.Articles.get(this.props.params.slug));
}
this.props.onLoad(null);
}
}
Imported components: Header
Handlers
onLoad: (payload, token) => dispatch({ type: 'APP_LOAD', payload, token, skipTracking: true })
onRedirect: () => dispatch({ type: 'REDIRECT' })
Lifecycle
componentWillMount() {
const token = window.localStorage.getItem('jwt');
if (token) {
agent.setToken(token);
}
this.props.onLoad(token ? agent.Auth.current() : null, token);
}
componentWillReceiveProps(nextProps) {
if (nextProps.redirectTo) {
this.context.router.replace(nextProps.redirectTo);
this.props.onRedirect();
}
}
(<IndexRoute>
on "/")
Handlers
onClickTag: (tag, payload) => dispatch({ type: 'APPLY_TAG_FILTER', tag, payload }),
onLoad: (tab, payload) => dispatch({ type: 'HOME_PAGE_LOADED', tab, payload }),
onUnload: () => dispatch({ type: 'HOME_PAGE_UNLOADED' })
Lifecycle
componentWillMount() {
const tab = this.props.token ? 'feed' : 'all';
const articlesPromise = this.props.token ?
agent.Articles.feed() :
agent.Articles.all();
this.props.onLoad(tab, Promise.all([agent.Tags.getAll(), articlesPromise]));
}
componentWillUnmount() {
this.props.onUnload();
}
Should be self explanatory, follow patterns described above, it was just the home
and index components are somewhat unique due to handling of routing.
Hey thanks for the beautiful work you do. I hope the tutorial will be out soon.
Is it normal with node 6.7.0 that when i run npm install, webpack is launched and I have ERROR in Cannot find module 'babel-core' . I can't launch the project.
Thanks
Also I would like to see here an example of displaying a sidebar with latest posts, or featured posts. Implemented with a multireducer or something like that.
Also maybe something like displaying recent comments, or on homepage show comments for each post.
I think this is great fit for using advanced patterns section.
In application, the forms are changing uncontrolled input of type text to controlled. Here is error screenshot.
It can be fixed if we add default values in render(). For example in Register.js
Change this
const email = this.props.email;
const password = this.props.password;
const username = this.props.username;
to
const email = this.props.email || '';
const password = this.props.password || '';
const username = this.props.username || '';
The pagination on user articles, favorites and feed are broken. Clicking on any of the paginated pages at the bottom will trigger a call to the all articles api endpoint and reset the data and pagination to the global feed. Might be because of this.
Tested on here https://react-redux.realworld.io
Hey all - we're 100% source code complete and would love your feedback!
The documentation around the codebase is a little slim right now but we're working to have it done within the next few days. The codebase is pretty straightforward but if you have any questions, feel free to post them here and @vkarpov15 can answer them.
Looking forward to hearing your thoughts!
It's difficult to grok where (if at all) this example shows off good patterns for optimistic updates, i.e. a user submits a comment and it appears in the UI as if the comment was successfully posted immediately, meanwhile the async request is sent. Successful responses would need not do anything to update the state (or UI) since we optimistically assumed the update would succeed, but failure responses would require prompting the user with an error or retry message.
Just testing this UI frontend against the #qewd backend.
Works nicely..
However
if I select a #tag and a tab opens (see #qewd tag tab open in this example)
.. then move to another tab, the #tag tab disappears..
Perhaps this is expected behaviour , but doesnt make sense from usability perspective.
The #tag tab should remain open until tab is closed
This app is using 0.6.1 version of react-scripts whereas latest version is at 0.8.5. The biggest advantage of new version is that it supports source-mapping out of box. And it can help hugely during development. As without source-maps the code in chrome dev tools is really hard to understand.
And it was not causing any other issue is app (although have not tested thoroughly). I can submit PR if its ok..
I don't really see any documentation in the code, which is really important if this is to be an example of using redux in a real-world application.
Hi, Thank you for the project, I really like the idea.
I use React + Redux for about a year however I don't feel very comfortable with testing.
Are tests going to be implemented?
Thanks
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.