show another component as soon as the user logs in - javascript

I have a use case where the user gets to log in. As soon as the user gets log in, another component should be shown. This is not working to me. I have to hit the login button again to show another component or has to refresh the page.
Here is what I have done
This is the parent component
const mapStateToProps = state => {
return {
user: state.loginReducer
}
}
class App extends React.Component {
state = {
hasToken: false
}
componentDidMount() {
const { user } = this.props;
window.chrome.storage.sync.get(['user_token'], result => {
if ((user && user.access_token) || result.user_token) {
console.log('user_token in cdm', result, user);
this.setState({ hasToken: true })
}
})
}
componentWillReceiveProps(nextProps) {
if (this.props.user !== nextProps.user) {
window.chrome.storage.sync.get(['user_token'], result => {
if (nextProps.user.length || result.user_token) {
this.setState({ hasToken: true })
}
})
}
}
anotherComponent() { // just to show the demo
return (
<div class="content">
component to show when the user logs in
</div>
)
}
render() {
const { hasToken } = this.state;
return (
<div>
<Header />
{ !hasToken ? <Login /> : this.anotherComponent()}
</div>
)
}
}
export default connect(mapStateToProps, null)(App);
login.js
const mapDispatchToProps = dispatch => ({
userLogin: user => dispatch(login(user))
})
class Login extends React.Component {
state = {
user: {
email:"",
password: "",
grant_type: "password"
}
}
handleChange = e => {
this.setState({user: {...this.state.user, [e.target.name]: e.target.value}})
}
handleSubmit = e => {
e.preventDefault();
this.props.userLogin(this.state.user);
}
render() {
const { user } = this.state;
return (
<Grid>
<Row className="pad-10">
<Col sm={12} md={6} mdOffset={3}>
<Form onSubmit={this.handleSubmit}>
<FormGroup controlId="email">
<ControlLabel>Email</ControlLabel>
<FormControl
type="email"
placeholder="Email"
name="email"
onChange={this.handleChange}
value={user.email}
/>
</FormGroup>
<FormGroup controlId="password">
<ControlLabel>Password</ControlLabel>
<FormControl
type="password"
placeholder="Password"
name="password"
onChange={this.handleChange}
value={user.password}
/>
</FormGroup>
<FormGroup>
<Button type="submit">Sign in</Button>
</FormGroup>
</Form>
</Col>
</Row>
</Grid>
);
}
}
export default connect(null, mapDispatchToProps)(Login);
I am not using any router concept so What I wanted to do is when user hits the login button, if the login is successful, the token is respond from the server and that is checked so that if it is successful, the user will be shown another component.
UPDATE
export const login = action(LOGIN, 'user');
export const loginSuccess = action(LOGIN_SUCCESS, 'data');
export const loginFailure = action(LOGIN_FAILURE, 'error');
reducer code
const initialState = {
fetching: false,
error: null,
user: []
}
function loginReducer(state=initialState, action) {
switch (action.type) {
case LOGIN:
return {...state, fetching: true}
case LOGIN_SUCCESS:
return {...state, fetching: false, user: action.data.access_token}
case LOGIN_FAILURE:
return {...state, fetching: false, error: action.error}
default:
return state;
}
}
export default loginReducer;

I don't know exactly how does window.chrome.storage.sync works, but obvious solution (at the first glance) is:
// parent component
render() {
const { user } = this.props;
return (
<div>
<Header />
{ !user ? <Login /> : this.anotherComponent()}
</div>
)
}
You have to get user from your state
I'll provide more details when you bring your reducers/actions

The problem your code is not working is because the App component is only rendered once irrespective of weather the user is logged in or not. So later when the user is logged in, your app component is not re-rendered.
There could be many ways to solve this. What I suggest you to do is something like this:
pass an onUserLogin callback to Component something like
<LoginModal isModalOpen={isModalOpen} onLoginClick={this.onLoginClick} />
Then do a setState inside the onLoginClick function to make sure component is rendered with changed props.
onLoginClick = () => {
if (!this.state.isUserLoggedIn) {
this.setState({
isModalOpen:!this.state.isModalOpen,
});
}
}

Related

How to edit react content dynamically

Ok..first things first:
Please refer the image of webApp attached:
My Application displays loginIdCard's consisting(website,username,password) from mongoDb which
i can edit from react when clicking on edit button.
What i did is initially i maintained a editMode key in component state a set it as false.
when a user clicks on edit button the LoginIdCard becomes editable and on clicking save button new values are set in component state and then editLoginId function is dispatch which updates this new value in database.
Now,
following are the things i want:
Initially when edit button is clicked, the value inside the input field should be the original values,
but now it is show empty.
2.The new values should be displayed immediately without rerendering of component.
Note: Now,after cliciking on save button , the component rerenders and the endpoint api return res data which is not a array, so the LoginDisplay component is not able to map and gives this.map is not a function error.
Please Help me
Web app rendering LoginIdCard in LoginDisplay Component
"LoginDispaly Component:Here LoginIdCard Component Will Rendender"
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import LoginIdCard from "./LoginIdCard";
import EditLoginIdComponent from "./EditLoginIdComponent";
import { fetchLoginIds } from "../actions/loginInIdsAction";
import "../css/displaySection.css";
class LoginsDisplay extends Component {
componentWillMount() {
this.props.fetchLoginIds();
}
render() {
const { logins } = this.props;
return (
<div className="display-section">
{logins.map((logins) => (
<LoginIdCard logins={logins} />
))}
</div>
);
}
}
function mapStateToProps(state) {
return {
logins: state.logins.logins,
};
}
LoginsDisplay.propTypes = {
logins: PropTypes.array.isRequired,
};
export default connect(mapStateToProps, { fetchLoginIds })(LoginsDisplay);
"LoginIdCard Component it will be render in LoginDispaly Component"
import React, { Component } from "react";
import { connect } from "react-redux";
import { editLoginId } from "../actions/loginInIdsAction";
import "../css/card.css";
class LoginIdCard extends Component {
constructor(props) {
super(props);
this.state = {
website: "",
username: "",
password: "",
editMode: false,
};
this.handleChange = this.handleChange.bind(this);
// this.handleSave = this.handleChange.bind(this);
}
handleChange = (fieldName, val) => {
console.log(val);
this.setState({
[fieldName]: val,
});
};
handleSave = () => {
const { website, username, password } = this.state;
const { logins } = this.props;
this.props.dispatch(editLoginId(website, username, password, logins._id));
console.log(this.state.website, username, password, logins._id);
};
render() {
const { editMode } = this.state;
const { logins } = this.props;
// const website = logins.website;
// const username = logins.username;
// const password = logins.password;
return (
<div className="card">
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("website", e.target.value)}
value={this.state.website}
/>
) : (
<p>{this.state.website}</p>
)}
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("username", e.target.value)}
value={this.state.username}
/>
) : (
<p>{logins.username}</p>
)}
{editMode ? (
<input
type="text"
onChange={(e) => this.handleChange("password", e.target.value)}
value={this.state.password}
/>
) : (
<p>{logins.password}</p>
)}
{editMode ? (
<button onClick={this.handleSave}>save</button>
) : (
<button onClick={() => this.handleChange("editMode", true)}>
edit
</button>
)}
</div>
);
}
}
// this.handleChange("editMode", false)
function mapStateToProps(state) {
return {
// user: state.user.users,
// cards: state.cards.cards,
logins: state.logins.logins,
};
}
// App.propTypes = {
// user: PropTypes.array.isRequired,
// };
export default connect()(LoginIdCard);
"redux action file for editing the LoginId in mongodb"
export function editLoginId(website, username, password, id) {
return function (dispatch) {
const req = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
website: website,
username: username,
password: password,
cardId: id,
}),
};
fetch("http://localhost:9000/editLoginId", req)
.then((res) => res.json())
.then((data) =>
dispatch({
type: EDIT_LOGIN_ID,
payload: data,
})
)
.catch((err) => {
console.log(err);
<!-- begin snippet: js hide: false console: true babel: false -->
});
};
}

How to store a complex form object into redux state on submit?

new to React/Redux, I am trying to make it so a user can have as many choices with different choice and customAttribute.
For now I have a button they can createUI with to dynamically create a new textfield to input another choice and customAttribute but am completely stumped on how to store such a thing in redux.
I've seen other questions and answers on how to store username and/or password, but have not seen any examples on how to store an object with my full state in it.
My Component
class CreateRIG extends Component<any, any> {
constructor(props) {
super(props);
this.state = {
rigName: '',
desc: '',
choices: [{
choice: '',
customAttribute: ''
}]
}
}
createUI() {
return this.state.choices.map((el, i) => (
<div key={i}>
<FormControl>
<Select
id="choice"
name="choice"
onChange={this.handleChange.bind(this, i)}
value={el.choice || ''}
>
<MenuItem value=''>
<em>None</em>
</MenuItem>
<MenuItem value='ID'>ID</MenuItem>
<MenuItem value='PSA'>PSA</MenuItem>
<MenuItem value='ExternalID'>ExternalID</MenuItem>
</Select>
</FormControl>
<div>
<TextField name="customAttribute" label="Attribute"
onChange={this.handleChange.bind(this, i)} value={el.customAttribute || ''} />
))
}
handleSubmit = (event) => {
event.preventDefault();
console.log(this.state);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div>
<TextField name="rigName"
value={this.state.rigName}
onChange={event => this.setState({ rigName: event.target.value })}/>
</div>
<div className={styles.spacing}>
<TextField name="description"
onChange={event => this.setState({ desc: event.target.value })}/>
</div>
{this.createUI()}
<Button type="submit"
onClick={this.props.onSubmitForm}>NEXT</Button>
</form>
);
}
}
const mapDispatchToProps = dispatch => {
return {
onSubmitForm: (rigName, desc, choice, customAttribute) => dispatch({ type: 'FORM_DATA'})
}
}
const connectedCreateRIGPage = connect(mapStateToProps, mapDispatchToProps)(CreateRIG);
export { connectedCreateRIGPage as CreateRIG };
export default CreateRIG;
Action.tsx
export const createRIGActions = {
searchInput
};
function searchInput(rigName, desc, choice, customAttribute) {
return {
type: createRIGConstants.STORE_FORM,
rigName,
desc,
choice,
customAttribute
}
}
Reducer.tsx
const initialState = {
results: []
};
export function createRIGReducer(state = initialState, action) {
switch (action.type) {
case createRIGConstants.STORE_FORM:
return {
...state,
results: state.results
}
// Need to somehow get the data from the form
default:
return state
}
}
How do you store a complex object from a form, into redux state on submit? Right now my onSubmit is console logging the correct object I want so thats nice
I believe it is just matter what you pass into dispatch. You can add payload there as well.
Try the following:
const mapDispatchToProps = dispatch => {
return {
onSubmitForm: (rigName, desc, choice, customAttribute) => dispatch({ type: 'FORM_DATA', payload: {rigName, desc, choice, customAttribute}})
}
}
Then in your reducer you can access that payload like the following:
export default (state=initialState, action) => {
switch(action.type) {
const { payload } = action;
case 'FORM_DATA':
return {
...state,
// here you can use data from payload to update the state
};
Read further here from Redux documentation: Managing Normalized Data
I hope this helps!

React Context API and child component refresh

I have a Context component which, when I set a certain value, I'd like them to effect other components using it, but my understanding seems wrong here.
I have a MainComponent component that basically handles the layout of the screen.
import { Consumer } from './context';
class MainContainer extends React.Component {
render() {
const noPadding = {
margin: 0,
padding: 0
}
const isAuthenticated = this.context.isAuthenticated()
return (
<HashRouter>
<Header />
<Container>
{isAuthenticated &&
<AuthorisedArea>
<Row style={noPadding}>
<Col md="3" style={noPadding}>
<RightInfoBar />
</Col>
<Col md="9">
<RouteManager />
</Col>
</Row>
</AuthorisedArea>
}
</Container>
</HashRouter>
)
}
}
MainContainer.contextType = Consumer;
export default MainContainer
This should all work when the user is logged in. When they're logged out, for now, I expect a blank screen, really.
In my Navbar, when a user clicks logout, I do this:
handleLogout() {
const { toastManager } = this.props;
const { pathname } = this.props.location;
this.context.changeAuthenticated(false);
this.context.logout();
if(pathname !== "/") {
this.props.history.push("/");
}
toastManager.add('You\'re now logged out', {
appearance: 'success',
autoDismiss: true,
pauseOnHover: false,
});
}
the this.context.logout updates a property in my context component, setting 'isAuthenticated' to false.
So my basic context component:
const Context = React.createContext();
export class Provider extends React.Component {
state = {
user: {
name: "",
email: "",
authStatus : Auth.isAuthenticated()
},
changeEmail: (newEmail) => {
let user = this.state.user;
user.email = newEmail;
this.setState({ user: user})
},
changeAuthenticated: (newState) => {
if(newState ===false) Auth.logout();
let user = this.state.user;
user.name = "Craig";
this.setState ({ user: user });
},
logout: () => {
console.log("Will logout");
Auth.logout();
this.setState({authStatus: false});
},
isAuthenticated: () => {
return Auth.isAuthenticated()
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
export const Consumer = Context.Consumer;
So, my main component, I was hoping, would notice that isAuthenticated goes to false... and the screen would go blank (for now... I will do better things once I have this working).
But when I click logout - the console indicates the method in the Context component fired... and the value becomes false... But the screen remains.
Only when I press F5, does the screen go as expected.
How should I be making my MainComponent react to React.Context value changes?

React: Child Component Does Not Rerender When Submitting Form

I am new in React and I will appreaciate much any help. I am using create-react-app, react-router-dom and express server. When I try to submit a comment to a blog post (child component called Details), it gets stored in the database, however the component does not seem to update and i do not see the new comment.As a result, I can see the new comment only after i refresh the page but not on form submit. I guess I am not setting componentDidUpdate properly but I do not have a clue how to do it, so i can see the comment immediately.
Here is my App.js:
class App extends Component {
constructor(props) {
super(props)
this.state = {
userId: null,
username: null,
isAdmin: false,
isAuthed: false,
jwtoken: null,
posts: [],
filtered: [],
}
this.handleSubmit = this.handleSubmit.bind(this)
}
static authService = new AuthService();
static postService = new PostService();
static commentService = new CommentService();
componentDidMount() {
const isAdmin = localStorage.getItem('isAdmin') === "true"
const isAuthed = !!localStorage.getItem('username');
if (isAuthed) {
this.setState({
userId: localStorage.getItem('userId'),
username: localStorage.getItem('username'),
isAdmin,
isAuthed,
})
}
this.getPosts()
}
componentDidUpdate(prevProps, prevState, posts) {
if (prevState === this.state) {
this.getPosts()
}
}
handleChange(e, data) {
this.setState({
[e.target.name]: e.target.value
})
}
handleCommentSubmit(e, data) {
e.preventDefault();
e.target.reset();
App.commentService.createComment(data)
.then(body => {
this.getposts()
if (!body.errors) {
toast.success(body.message);
}
else {
toast.error(body.message);
}
}
)
.catch(error => console.error(error));
}
getPosts() {
App.postService.getPost()
.then(data => {
this.setState({
posts: data.posts.length? data.posts : []
});
}
)
.catch(e => this.setState({ e }))
}
render() {
return (
<Fragment>
<Header username={this.state.username} isAdmin={this.state.isAdmin} isAuthed={this.state.isAuthed} logout={this.logout.bind(this)} />
<Switch>
<Route exact path="/" render={(props) => (
<Home
posts={this.state.posts}
handleSearchSubmit={this.handleSearchSubmit.bind(this)}
handleChange={this.handleSearchChange.bind(this)}
{...props} />
)} />
<Route path="/posts/:id" render={(props) =>
<Details handleSubmit={this.handleCommentSubmit.bind(this)}
isAdmin={this.state.isAdmin}
isAuthed={this.state.isAuthed}
posts={this.state.posts}
handleChange={this.handleChange}
{...props} />} />
</Switch>
<Footer posts={this.state.posts} formatDate={this.formatDate} />
</Fragment>
);
}
}
export default withRouter(App);
Here is my Details.js:
class Details extends Component {
constructor(props) {
super(props);
this.state = {
post: null,
comment: null
}
this.handleChange = props.handleChange.bind(this);
}
componentDidMount() {
const { posts, match } = this.props;
this.setState({
post: posts.length
? posts.find(p => p._id === match.params.id)
: null,
userId: localStorage.getItem('userId')
})
}
componentDidUpdate(prevProps) {
const { posts, match, isAuthed } = this.props;
if (JSON.stringify(prevProps) === JSON.stringify(this.props)) {
return;
}
this.setState({
post: posts.length
? posts.find(p => p._id === match.params.id)
: null
});
}
render() {
const { post } = this.state;
const { isAdmin, isAuthed } = this.props;
if (!post) {
return <span>Loading post ...</span>;
}
return (
<section className="site-section py-lg">
<form onSubmit={(e)=> this.props.handleSubmit(e, this.state)} className="p-5 bg-light">
<div className="form-group">
<label htmlFor="message">Message</label>
<textarea name="comment" id="message" onChange={this.handleChange} cols={30} rows={10} className="form-control" defaultValue={ ""} />
</div>
<div className="form-group">
<input type="submit" defaultValue="Post Comment" className="btn btn-primary" />
</div>
</form>}
</section>
);
}
}
export default Details;
Any help will be much appreciated!
You are doing a mistake that will be done by any new React developer. Just remember one thing that:-
UI is a function of state
So your UI will only be updated if your state is update.
After submitting a comment don't fetch all your comments again, just concat your new comment to current state and you will see your comment as soon as you submit it successfully

Axios get method response in React cannot be displayed getting data from firebase as an array in my blog application

I wonder if someone could help me. I have read many StackOverflow's answers around this and other great articles like this one and I couldn't implement an answer yet.
I have got a simple blog app in React. I have a form to submit the data and I have separate post and posts component as well. I can actually send data to my firebase database. I also get the response in GET method but I cannot show the response as I need it to be. I need an array of posts which each post has a title and content so that I can send its data to my Post component. But I always get an error like( map cannot be used on the response) and I actually cannot get an array out of my database. I even wonder if I am sending data in the right format. Please check my code below and help me out. Thanks.
// The individual post component
const Post = props => (
<article className="post">
<h2 className="post-title">{props.title}</h2>
<hr />
<p className="post-content">{props.content}</p>
</article>
);
// The form component to be written later
class Forms extends React.Component {}
// The posts loop component
class Posts extends React.Component {
state = {
posts: null,
post: {
title: "",
content: ""
}
// error:false
};
componentDidMount() {
// const posts = this.state.posts;
axios
.get("firebaseURL/posts.json")
.then(response => {
const updatedPosts = response.data;
// const updatedPosts = Array.from(response.data).map(post => {
// return{
// ...post
// }
// });
this.setState({ posts: updatedPosts });
console.log(response.data);
console.log(updatedPosts);
});
}
handleChange = event => {
const name = event.target.name;
const value = event.target.value;
const { post } = this.state;
const newPost = {
...post,
[name]: value
};
this.setState({ post: newPost });
console.log(event.target.value);
console.log(this.state.post.title);
console.log(name);
};
handleSubmit = event => {
event.preventDefault();
const post = {
post: this.state.post
};
const posts = this.state.posts;
axios
.post("firebaseURL/posts.json", post)
.then(response => {
console.log(response);
this.setState({ post: response.data });
});
};
render() {
let posts = <p>No posts yet</p>;
if (this.state.posts) {
posts = this.state.posts.map(post => {
return <Post key={post.id} {...post} />;
});
}
return (
<React.Fragment>
<form className="new-post-form" onSubmit={this.handleSubmit}>
<label>
Post title
<input
className="title-input"
type="text"
name="title"
onChange={this.handleChange}
/>
</label>
<label>
Post content
<input
className="content-input"
type="text"
name="content"
onChange={this.handleChange}
/>
</label>
<input className="submit-button" type="submit" value="submit" />
</form>
</React.Fragment>
);
}
}
class App extends React.Component {
render() {
return (
<React.Fragment>
<Posts />
</React.Fragment>
);
}
}
// Render method to run the app
ReactDOM.render(<App />, document.getElementById("id"));
And this is a screenshot of my firebase database:
My Firebase database structure
It is interesting that what I found is rarely mentioned anywhere around it.
This is the entire Posts component:
class Posts extends React.Component {
state = {
posts: [],
post: {
title: "",
content: ""
}
};
componentWillMount() {
const { posts } = this.state;
axios
.get("firebaseURL/posts.json")
.then(response => {
const data = Object.values(response.data);
this.setState({ posts : data });
});
}
handleChange = event => {
const name = event.target.name;
const value = event.target.value;
const { post } = this.state;
const newPost = {
...post,
[name]: value
};
this.setState({ post: newPost });
console.log(event.target.value);
console.log(this.state.post.title);
console.log(name);
};
handleSubmit = event => {
event.preventDefault();
const {post} = this.state;
const {posts} = this.state;
axios
.post("firebaseURL/posts.json", post)
.then(response => {
console.log(response);
const newPost = response.data;
this.setState({ post: response.data });
});
};
render() {
let posts = <p>No posts yet</p>;
if (this.state.posts) {
posts = this.state.posts.map(post => {
return <Post key={post.id} {...post} />;
});
}
return (
<React.Fragment>
{posts}
<form className="new-post-form" onSubmit={this.handleSubmit}>
<label>
Post title
<input
className="title-input"
type="text"
name="title"
onChange={this.handleChange}
/>
</label>
<label>
Post content
<input
className="content-input"
type="text"
name="content"
onChange={this.handleChange}
/>
</label>
<input className="submit-button" type="submit" value="submit" />
</form>
</React.Fragment>
);
}
}
Actually as I first time read in this question you should not rely on console.log to see if your posts (or your response data) has been updated. Because in componentDidMount() when you immediately update state you will not see the change in console. So what I did was to display the data that I got from the response using map over the posts and it showed my items as I actually had an array although couldn't see in the console. This is my code for componentDidMount:
axios.get("firebaseURL/posts.json").then(response => {
const data = Object.values(response.data);
this.setState({
posts: data
});
And show the posts:
let posts = <p>No posts yet</p>;
if (this.state.posts) {
posts = this.state.posts.map(post => {
return <Post key={post.id} {...post} />;
});
}
And it shows all the posts as expected. Take away is to be careful once woking on componentDidMound and other lifecycle methods as you might not see the updated data in the console inside them but you actually need to use it as it is in the response. The state is updated but you are not able to see it inside that method.
Not a database expert, but I believe your database is structured a bit odd and will only cause problems further down the line, especially when it comes to editing/updating a single post. Ideally, it should structured like a JSON array:
posts: [
{
id: "LNO_qS0Y9PjIzGds5PW",
title: "Example title",
content: "This is just a test"
},
{
id: "LNOc1vnvA57AB4HkW_i",
title: "Example title",
content: "This is just a test"
},
...etc
]
instead its structured like a JSON object:
"posts": {
"LNO_qS0Y9PjIzGds5PW": {
"post": {
"title": "Example title",
"content": "This is just a test"
}
},
"LNOc1vnvA57AB4HkW_i": {
"post": {
"title": "Example title",
"content": "This is just a test"
}
},
...etc
}
Anyway, your project should have a parent Posts container-component that controls all your state and fetching of data, then it passes down its state and class methods to component children. Then the children can update or display the parent's state accordingly.
OR
You should separate your Posts container-component, so that it either displays found posts or a "No posts found" component. And then, have your Posts Form component be it's own/unshared component whose only function is to show a form and submit it to a DB.
Up to you and what you think fits your needs.
Working example: https://codesandbox.io/s/4x4kxn9qxw (the example below has one container-component that shares with many children)
Note: If you change posts to an empty array [], instead of data in fetchData()s this.setState() function, you can have the PostForm be displayed under the /posts route!
ex: .then(({ data }) => this.setState({ isLoading: false, posts: [] }))
index.js
import React from "react";
import { render } from "react-dom";
import App from "./routes";
import "uikit/dist/css/uikit.min.css";
import "./styles.css";
render(<App />, document.getElementById("root"));
routes/index.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "../components/Home";
import Header from "../components/Header";
import Posts from "../containers/Posts";
export default () => (
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/posts" component={Posts} />
<Route path="/postsform" component={Posts} />
</Switch>
</div>
</BrowserRouter>
);
containers/Posts.js
import isEmpty from "lodash/isEmpty";
import React, { Component } from "react";
import axios from "axios";
import PostsForm from "../components/postsForm";
import ServerError from "../components/serverError";
import ShowPosts from "../components/showPosts";
import Spinner from "../components/spinner";
export default class Posts extends Component {
state = {
content: "",
error: "",
isLoading: true,
posts: [],
title: ""
};
componentDidUpdate = (prevProps, prevState) => {
// check if URL has changed from "/posts" to "/postsform" or vice-versa
if (this.props.location.pathname !== prevProps.location.pathname) {
// if so, check the location
this.setState({ isLoading: true }, () => this.checkLocation());
}
};
componentDidMount = () => this.checkLocation();
checkLocation = () => {
// if the location is "/posts" ...
this.props.location.pathname === "/posts"
? this.fetchData() // then fetch data
: this.setState({ // otherwise, clear state
content: "",
error: "",
isLoading: false,
posts: [],
title: ""
});
};
// fetches posts from DB and stores it in React state
fetchData = () => {
axios
.get("firebaseURL/posts.json")
.then(({ data }) => this.setState({ isLoading: false, posts: data }))
.catch(err => this.setState({ error: err.toString() }));
};
// handles postsForm input changes { content: value , title: value }
handleChange = e => this.setState({ [e.target.name]: e.target.value });
// handles postsForm form submission
handleSubmit = event => {
event.preventDefault();
const { content, title } = this.state;
alert(`Sumbitted values: ${title} - ${content}`);
/* axios.post("firebaseURL/posts.json", { post: { title, content }})
.then(({data}) => this.setState({ content: "", posts: data, title: "" }))
.catch(err => this.setState({ error: err.toString() }))
*/
};
// the below simply returns an if/else chain using the ternary operator
render = () => (
this.state.isLoading // if isLoading is true...
? <Spinner /> // show a spinner
: this.state.error // otherwise if there's a server error...
? <ServerError {...this.state} /> // show the error
: isEmpty(this.state.posts) // otherwise, if posts array is still empty..
? <PostsForm // show the postForm
{...this.state}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
/>
: <ShowPosts {...this.state} /> // otherwise, display found posts!
);
}
components/postsForm.js
import React from "react";
export default ({ content, handleSubmit, handleChange, title }) => (
<form
style={{ padding: "0 30px", width: 500 }}
className="new-post-form"
onSubmit={handleSubmit}
>
<label>
Post title
<input
style={{ marginBottom: 20 }}
className="uk-input"
type="text"
name="title"
onChange={handleChange}
placeholder="Enter post title..."
value={title}
/>
</label>
<label>
Post content
<input
style={{ marginBottom: 20 }}
className="uk-input"
type="text"
name="content"
onChange={handleChange}
placeholder="Enter post..."
value={content}
/>
</label>
<button
disabled={!title || !content}
className="uk-button uk-button-primary"
type="submit"
>
Submit
</button>
</form>
);
components/showPosts.js
import map from "lodash/map";
import React from "react";
export default ({ posts }) => (
<div className="posts">
{map(posts, ({ post: { content, title } }, key) => (
<div key={key} className="post">
<h2 className="post-title">{title}</h2>
<hr />
<p className="post-content">{content}</p>
</div>
))}
</div>
);
components/serverError.js
import React from "react";
export default ({ err }) => (
<div style={{ color: "red", padding: 20 }}>
<i style={{ marginRight: 5 }} className="fas fa-exclamation-circle" /> {err}
</div>
);

Categories