I coming from ReactJS and React Native. I want to try out Flutter. So far I want to have a login screen. Therefore I want to check if a user is already logged in. If so forward to the Home Screen. If not, show the login screen.
In React with TypeScript and Firebase I would to it this way:
interface RootScreenState {
isLoading: boolean;
user: firebase.User | null;
}
class RootScreen extends React.Component<{}, RootScreenState> {
constructor(props) {
super(props);
this.state = {
isLoading: true,
user: null
}
}
componentDidMount() {
// make an async call to some firebase libraries to look for stored user credentials
// if some credentials are found try to login
// of no credentials are found or login fails return null
// otherwise the user is returned
tryToLogin().then((currentUser: firebase.User | null) => {
this.setState({
isLoading: false,
user: currentUser
}).catch(err => { /* do some error handling */});
}
render() {
const { isLoading, user } = this.state;
if(isLoading) return ( /* loading screen */ );
else if(user === null) return ( /* login screen */ );
else return ( /* home screen */ );
}
}
How do I do with Flutter? I could not find anything about an equivalent to compnentDidMount(), should I do it in the constructor? In React this would fail.
use initState in Stateful widget.InitState is called when the stateful widget is first time painted. For ex
class _MyAppState extends State<MyApp> {
Future<Album> futureAlbum;
#override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
You can do this in Flutter's initState.
It gets called first when your widget tree is rendered.
Check the code below:
#override
void initState() {
super.initState();
}
For more details on various things in React Native you wished to know in Flutter.
See the link below: It is the official documentation of people coming to Flutter from a React Native background.
Documentation
I hope this helps.
When it comes to request data from the server, you may want to use FutureBuilder directly.
Sometimes widget build will trigger twice if you put states in initial of state improperly.
I prefer to put FutureBuilder into widget build scope and more clean and readable for me.
In your case, as you also work with navigation, I would work with a FutureBuilder. Initialize the framework, look for the user and navigate the user to the proper screen based on the state with initialroute.
If you want it nicer, you can play around with a SplashScreen. A good package you can use is flutter_native_splash. You will also find a full example there.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
// The event you are waiting for (can also be your user object, once loaded)
future: _initializeFrameworks,
builder: (context, snapshot) {
// Show splash screen
if (snapshot.connectionState == ConnectionState.waiting) {
return MaterialApp(home: SplashScreen());
} else {
// Return app and navigate
return MaterialApp(
initialRoute: snapshot.data ? MainScreen.id : WelcomeScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
MainScreen.id: (context) => MainScreen(),
},
);
}
},
);
}
}
class Mainscreen extends StatelessWidget {
static const id = "main_screen";
// your widget goes here
}
class WelcomeScreen extends StatelessWidget {
static const id = "welcome_screen";
// your widget goes here
}
Related
Today I started using MobX and the first problem I ran into is how to execute a function in a React class component whenever an Observable updates.
I am under the impression this can be achieved using a reaction, but I'm not sure how to make it work.
class MissionLog {
private _missions: Array<IMissionItem> = [];
public get missions() {
return this._missions;
}
constructor() {
makeAutoObservable(this);
}
// Example of a method that modifies the _missions array
public receiveMission(mission: IMissionItem) {
this._missions.push(mission);
}
}
export const missionLog = new MissionLog();
// Example of modifying the missions array
missionLog.receiveMission(someMission);
export const ObserverTest = observer(class _ObserverTest extends React.Component {
constructor(props: any) {
super(props);
// Executes the console.log at the start,
// but not when missionLog.missions changes.
autorun(() => {
console.log("Autorun", missionLog.missions);
})
// Never executes the console.log
reaction(
() => missionLog.missions,
(mission) => {
console.log("Reaction");
}
)
}
render() {
return (
// Accessing missionLog.missions here
// gives me the correct, updated data,
// so my setup should be fine.
)
}
});
I also tried to use intercept and observe instead of reaction, but also no result.
I'm using Next.js with redux and firebase auth.
What I'm trying to achieve is to check whether a user is logged in before being able to access some private pages.
I have created a component called AdminLayout which is wrapped around every private page. I get the currentUser as props using mapStateToProps but I'm not getting the value in the first run.
// admin-layout.component.jsx
// Imports
class AdminLayout extends React.Component {
componentDidMount() {
console.log(this.props.currentUser);
// if (this.props.currentUser) {
// if (this.props.currentUser.currentUser.role != "admin") {
// Router.push("/admin/login");
// }
// }
// if (!this.props.currentUser) {
// Router.push("/admin/login");
// }
}
render() {
return (
<React.Fragment>
// Irrelevant code here
{this.props.children}
// Irrelevant code here
</React.Fragment>
);
}
}
const mapStateToProps = (state) => ({
currentUser: state.user.currentUser,
});
export default connect(mapStateToProps)(AdminLayout);
Any way to overcome this situation and allow only authenticated users to access the private pages?
I have an app with redux and router where on the first load, all users are loaded. To this end, I've implemented a main component that loads the user when the component is mounted:
class Content extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.load();
}
render() {
return this.props.children;
}
}
The afterwards, if the user chooses to load the details of one user, the details are also obtained through the same lifehook:
class Details extends Component {
componentDidMount() {
this.props.getByUrl(this.props.match.params.url);
}
render() {
const { user: userObject } = this.props;
const { user } = userObject;
if (user) {
return (
<>
<Link to="/">Go back</Link>
<h1>{user.name}</h1>
</>
);
}
return (
<>
<Link to="/">Go back</Link>
<div>Fetching...</div>
</>
);
}
Now this works well if the user lands on the main page. However, if you get directly to the link (i.e. https://43r1592l0.codesandbox.io/gal-gadot) it doesn't because the users aren't loaded yet.
I made a simple example to demonstrate my issues. https://codesandbox.io/s/43r1592l0 if you click a link, it works. If you get directly to the link (https://43r1592l0.codesandbox.io/gal-gadot) it doesn't.
How would I solve this issue?
Summary of our chat on reactiflux:
To answer your question: how would you solve this? -> High Order Components
your question comes down to "re-using the fetching all users before loading a component" part.
Let's say you want to show a Component after your users are loaded, otherwise you show the loading div: (Simple version)
import {connect} from 'react-redux'
const withUser = connect(
state => ({
users: state.users // <-- change this to get the users from the state
}),
dispatch => ({
loadUsers: () => dispatch({type: 'LOAD_USERS'}) // <-- change this to the proper dispatch
})
)
now you can re-use withUsers for both your components, which will look like:
class Content extends Component {
componentDidMount() {
if (! this.props.users || ! this.props.users.length) {
this.props.loadUsers()
}
}
// ... etc
}
const ContentWithUsers = withUsers(Content) // <-- you will use that class
class Details extends Component {
componentDidMount() {
if (! this.props.users || ! this.props.users.length) {
this.props.loadUsers()
}
}
}
const DetailsWithUsers = withUsers(Details) // <-- same thing applies
we now created a re-usable HOC from connect. you can wrap your components with withUsers and you can then re-use it but as you can see, you are also re-writing the componentDidMount() part twice
let's take the actual load if we haven't loaded it part out of your Component and put it in a wrapper
const withUsers = WrappedComponent => { // notice the WrappedComponent
class WithUsersHOC extends Component {
componentDidMount () {
if (!this.props.users || !this.props.users.length) {
this.props.loadUsers()
}
}
render () {
if (! this.props.users) { // let's show a simple loading div while we haven't loaded yet
return (<div>Loading...</div>)
}
return (<WrappedComponent {...this.props} />) // We render the actual component here
}
}
// the connect from the "simple version" re-used
return connect(
state => ({
users: state.users
}),
dispatch => ({
loadUsers: () => dispatch({ type: 'LOAD_USERS' })
})
)(WithUsersHOC)
}
Now you can just do:
class Content extends Component {
render() {
// ......
}
}
const ContentWithUsers = withUsers(Content)
No need to implement loading the users anymore, since WithUsersHOC takes care of that
You can now wrap both Content and Details with the same HOC (High Order Component)
Until the Users are loaded, it won't show the actual component yet.
Once the users are loaded, your components render correctly.
Need another page where you need to load the users before displaying? Wrap it in your HOC as well
now, one more thing to inspire a bit more re-usability
What if you don't want your withLoading component to just be able to handle the users?
const withLoading = compareFunction = Component =>
class extends React.Component {
render() {
if (! compareFunction(this.props)) {
return <Component {...this.props} />;
}
else return <div>Loading...</div>;
}
};
now you can re-use it:
const withUsersLoading = withLoading(props => !props.users || ! props.users.length)
const ContentWithUsersAndLoading = withUsers(withUsersLoading(Content)) // sorry for the long name
or, written as a bit more clean compose:
export default compose(
withUsers,
withLoading(props => !props.users || !props.users.length)
)(Content)
now you have both withUsers and withLoading reusable throughout your app
I have one question about the react-way.
I have two components. For example: App & Browser.
App is a general component. It loads inner modules and renders application tree-structure
Browser is an inner-component of App. It shows some fetched data by its ID.
App-component doesn't matter about ID and data what currently rendered in Browser-component. App doesn't want to control browser-component's navigation. Browser is mostly independent.
But sometimes App wanna do one of it:
Ask Browser-component for refresh (fetch data again + render it) active page
Ask Browser-component to load particular ID
I don't understand how I can do it using react-way with props.
Some code for example:
class App extends Component {
render(){
return ... <Browser server={this.server}/> ...;
}
}
class Browser extends Component {
constructor(props){
super(props);
this.state = { id: 'default' };
}
componentDidMound() { this.checkData(); }
componentWillUpdate() { this.checkData(); }
async checkData(){
if(!this.state.data}{
const data = await this.props.server.fetch(this.state.id);
this.setState({ data });
}
}
onChange(newId){
this.setState({ data: null, id: newId });
}
render(){
return <div>
<Page data={this.state.data}/>
<Navigation activeId={this.state.id} onChange={::this.onChange}/>
</div>;
}
}
I have some bad idea's. Example:
I can set Browser to App by ref-attribute. And directly run needed methods
I can use global variables
I can provide empty {}-object into Browser-component. In initialization in browser-component set all needed methods into this object. Finally run it from App
I think all this variants isn't react-way. How I can do it right?
I don't use redux in this project. Just react.
I found one interesting decision. I can pass observer-method into child components. Example:
class App extends Component {
constructor(props){
super(props);
this.subscribers = [];
}
subscribe = id => { this.subscribers.push(id); }
anyMethod(){
// ...
for(let fn of this.subscribers)
fn(newId);
// ...
}
render(){
return <div>...<Browser subscribe={this.subscribe} />...</div>
}
}
class Browser extends Component {
constructor(props){
super(props);
props.subscribe(id => { this.forceLoad(id); })
}
}
It's like API for inner component gained from Parent component :)
TL;DR:
After reading through various resources about React and Relay and by building a prototype app with react-relay, I came across a problem that I cannot solve without help. It's about how to tell a mutation which data to save.
I have a component that shows details of a User entity. It's loaded via a relay query. This component has an <input /> field with the name of the user and a save button that triggers a relay.commitUpdate(...) call. Whenever I change the name of the user in the input field and click on save, I can see that not the changed but the original name is send to the backend.
I am building an app with react-relay and I am working on a component that should display details about a user entity with the possibility to change these data and save it. It`s the detail part of the Master-Detail-Pattern.
Loading and changing of data works fine. But changes are not commited to the backend. For data updates I am trying to use relay mutations.
After clicking on save, I can see in the developer console in my chrome browser, that unaltered data is send to my backend. What do I miss? Here is what I have so far (Typescript code).
(Update: when placing break points into the onSaveClick() method of the component and the getVariables() method in the mutation, I see that in onSaveClick the property this.props.user contains made changes while this.props.user in getVariables() returns the initial data.)
export interface IUserDetailsProps { user: User }
export interface IUserDetailsStates { user: User }
class UserDetails extends React.Component<IUserDetailsProps, IUserDetailsStates> {
constructor(props: IUserDetailsProps) {
super(props);
this.state = { user: this.props.user };
}
public onFirstNameChange(event: any) {
let user = this.state.user;
this.state.user.firstName = event.target.value;
this.props.user.firstName = event.target.value; // is this needed?
this.setState({ user: user });
}
public onSaveClick() {
this.props.relay.commitUpdate(new UserMutation({ user: this.props.user }));
}
public render(): JSX.Element {
let user: User = this.props.user;
return <div>
<input type="text" value={user.firstName} onChange={this.onFirstNameChange.bind(this)} />
<button onClick={this.onSaveClick}>Save</button>
</div>;
}
}
class UserMutation extends Relay.Mutation {
public getMutation() {
return Relay.QL`mutation {saveUser}`;
}
public getVariables() {
return this.props.user;
}
public getFatQuery() {
return Relay.QL`
fragment on UserPayload {
user {
id,
firstName
}
}
`;
}
public getConfigs() {
return [{
type: "FIELDS_CHANGE",
fieldIDs: {
user: this.props.user.id
}
}];
}
static fragments = {
user: () => Relay.QL`
fragment on User {
id,
firstName
}
`
}
}
export default Relay.createContainer(UserDetails, {
fragments: {
user: () => Relay.QL`
fragment on User {
id,
firstName,
${UserMutation.getFragment("user")}
}
`
}
});
Solution attempt:
I changed the line for the input field of the user's first name to this:
<input type="text"
ref={(ref) => {
this.myTextBox = ref;
ref.value = this.props.user.firstName;
}} />
and removed the change event handler and removed all state code.
My click handler now looks like this:
public onSaveClick() {
let user = Object.assign({}, this.props.user);
user.firstName = this.myTextBox.value;
this.props.relay.commitUpdate(new UserMutation({user: user}));
}
and I can see, that the user object passed to the mutation now has the new value for firstName that I have changed in the text input. But again, relay sends the unchanged user to my backend. Here is a screenshot that shows my problem: