Codesandbox: https://codesandbox.io/s/github/adamschwarcz/react-firebase-app
I am really new to react and firebase and I followed this tutorial to come up with this app (full project – github link here) – it's an "Add your Wish app"
My problem is that I cannot store clap count on each post to my firebase – this component is called LikeButton.js.
I have been trying to add some similar firebase code (handleChange, handleSubmit, componentDidMount... etc.. etc..) as I learned in the tutorial to LikeButton.js to store the total amount of counts in firebase each time the button is clicked and the amount of claps incremented by +1.
Simply what I want – everytime the clap button is clicked and the initial ('0') state of count is incremented to +1 the current count is going to be updated into the database.
Just cannot come up with solution, can somebody please help?
My LikeButton.js code without any firebase:
import React, { Component } from 'react'
import firebase from '../../firebase.js';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import './Like.css';
class LikeButton extends Component {
state = {
count: 0,
}
incrementLike = () => {
let newCount = this.state.count + 1
this.setState({
count: newCount
})
console.log(this.state.count);
}
render() {
return(
<div class="counter">
<Button type="submit" color="primary" onChange={this.handleCount} onClick={this.incrementLike}>{this.state.count} 👏</Button>
</div>
)
}
}
export default LikeButton
My Add.js code with firebase:
import React, { Component } from 'react';
import firebase from '../../firebase.js';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import FadeIn from "react-fade-in";
import Placeholder from '../Placeholder/Placeholder.js';
import LikeButton from '../Like/Like.js'
import './Add.css';
class Add extends Component {
constructor() {
super();
this.state = {
loading: true,
currentItem: '',
username: '',
items: []
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
const itemsRef = firebase.database().ref('items');
const item = {
title: this.state.currentItem,
user: this.state.username
}
itemsRef.push(item);
this.setState({
currentItem: '',
username: ''
});
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(json => {
setTimeout(() => this.setState({ loading: false }), 1500);
});
const itemsRef = firebase.database().ref('items');
itemsRef.on('value', (snapshot) => {
let items = snapshot.val();
let newState = [];
for (let item in items) {
newState.push({
id: item,
title: items[item].title,
user: items[item].user
});
}
this.setState({
items: newState
});
});
}
removeItem(itemId) {
const itemRef = firebase.database().ref(`/items/${itemId}`);
itemRef.remove();
}
render() {
return (
<div className="container">
<div className="wrap">
<section className="add-item">
<h1>Napíš svoj wish</h1>
<h3>Možno prilepíš sebe, možno posunieš firmu.</h3>
<form onSubmit={this.handleSubmit}>
<TextField
id="filled-required"
label="Meno"
name="username"
variant="filled"
value={this.state.username}
onChange={this.handleChange}
/>
<TextField
required
id="standard-multiline-flexible"
label="Tvoje prianie"
name="currentItem"
variant="filled"
multiline
rows="6"
rowsMax="8"
value={this.state.currentItem}
onChange={this.handleChange}
/>
<Button
type="submit"
variant="contained"
color="primary">
Poslať wish
</Button>
</form>
</section>
<section className='items-list'>
<div className="item">
<div>
{this.state.items.map((item) => {
return (
<div>
{this.state.loading ? (
<>
<FadeIn>
<Placeholder />
</FadeIn>
</>
) : (
<div className="wish" key={item.id}>
<FadeIn>
<h2>{item.title}</h2>
<div className="name">
<p>poslal <span>{item.user}</span></p>
<LikeButton />
</div>
</FadeIn>
</div>
)}
</div>
)
})}
</div>
</div>
</section>
</div>
</div>
);
}
}
export default Add
First of all, you need to tell the LikeComponent which Wish it will be updating, and you will also need to be able to access the clapCount of the wish from the LikeComponent. This can be done easily using props. You should re-configure LikeComponent to accept a prop similar to wish, which would be the wish that you are displaying and modifying.
So, this line in Add.js
<LikeButton />
would instead look like <LikeButton wish={item} />. This way, your LikeComponent can access the item/wish.
Next, in the LikeComponent, you need to remove the local state and instead use the clap count stored in Firebase. Luckily, since you're passing the wish via a prop, you can simply refactor the LikeComponent to look like this:
class LikeButton extends Component {
incrementLike = () => {
// TODO: Implement clap incrementation via Firebase updates
}
render() {
return(
<div class="counter">
<Button type="submit" color="primary" onClick={this.incrementLike}>{this.props.wish.clapCount} 👏</Button>
</div>
)
}
}
Next, we need to actually implement incrementLike. Luckily, since we are getting the wish item passed to us via the wish prop, we can easily update it like so:
incrementLike = () => {
// get a reference to the item we will be overwriting
const wishRef = firebase.database().ref(`/items/${this.props.wish.id}`);
// get the current value of the item in the database
wishRef.once('value')
.then(snapshot => {
// get the value of the item. NOTE: this is unsafe if the item
// does not exist
let updatedWish = snapshot.val();
// update the item's desired property to the desired value
updatedWish.clapCount = updatedWish.clapCount + 1;
// replace the item with `wish.id` with the `updatedWish`
wishRef.set(updatedWish);
});
}
While this should work with only a few tweaks, I'm sure there's a better way to do it. You might even be able to avoid the call to once('value') since you're passing wish as a prop to LikeComponent. You should play around with it.
However, I strongly encourage you to explore migrating to Firebase Cloud Firestore. It's API is way more straightforward (in my opinion) than Realtime Database.
Related
Hello I am new to ReactJS so I am just practising on working with states and also so a good practice for a starting point I thought why not the classic TODO App.
So I do not know why the object is not being rendered or being added because when I even console logged the object It did not even show that it's empty or anything the was literally no output so I do not know where I could have went wrong with this methods
Code Below App.js: This is the file that has all the methods and state control of the TODO APP
import React, { Component } from 'react';
import style from './stylesheet/app.css'
import ListItems from './ListItems'
class App extends Component{
constructor(props){
super(props);
this.state = {
items:[],
currentItem:{
notes: '',
key: ''
}
}
this.handleInput = this.handleInput.bind(this);
this.addItem = this.addItem.bind(this);
}
// Handling user Input to save on before I add to the Items
// this.state.currentItems is a temporary store place for TODO'S
handleInput(e){
this.setState({
currentItem: {
notes: e.target.value,
key: Date.now()
}
})
}
// After handling input input once the add button is clicked I want to add
// the the object in the temporary storage into the permanent store place that is the
// this.state.items --> permanent store place
addItem(e){
e.preventDefault()
const newTodo = this.state.currentItem;
if (newTodo.text !== " "){
const newTodos = [...this.state.items, newTodo];
this.setState({
items:newTodos,
currentItem:{
notes:'',
key:''
}
})
}
}
render(){
return(
<div className="container" style={style}>
<div className='todo-form'>
<form id="form">
<input type="text"
placeholder="Enter in your todo's"
value={this.state.currentItem.notes}
onChange={this.handleInput}></input>
<button type="submit" onSubmit={this.addItem}>Add Todo</button>
</form>
<ListItems items={this.state.items}/>
</div>
</div>
)
}
}
export default App
Code Below ListItems.js: This file contains code where I tried to map through the ojects to display the TODO'S
import React from 'react';
const ListItems = (props) =>{
const items = props.items;
const listItems = items.map(item =>{
return <div className="todo-list" key={item.key}> <p>{item.key}</p> </div>
})
return(
<div>
{listItems}
</div>
)
}
export default ListItems
Can you please help me figure out where I could be going wrong?
Your code works perfectly fine. Just add addItem function to form element, so it preventsDefault correctly and doesnt reload whole page:
<form id="form" onSubmit={this.addItem}>
See here: https://codesandbox.io/s/musing-gareth-vlkmx
I'm creating a project-planning app using React, Redux, and Firebase. A single project record in my Firestore database contains a Title and some Content. When I go to update a project, I have the input fields' defaultValues set to the correct data for the project I want to edit. However, updating only works if I make changes to both the Content and Title input fields. Otherwise, upon submitting these values the data gets deleted because the local state has not seen any changes and therefore updates the untouched field to the empty string: ""
I have tried setting the local state of the EditProject component in the render method, but this is not possible:
render() {
const { project, auth } = this.props;
if (!auth.uid) return <Redirect to="/signin" />;
if (project) {
this.setState({
title: project.title,
content: project.content
});
...
I have also tried setting the state in during componentDidMount like so:
componentDidMount = () =>{
const { project } = this.props;
this.setState({
title: project.title,
content: project.content
})
}
But the issue with this is that the project prop does not get mapped by mapStateToProps before componentDidMount
Lastly, I've tried passing the project prop from the parent component, which is projectDetails, but I am unable to successfully do so. I might be doing this part wrong so please let me know if there is a good way to do this with the code I have. In ProjectDetails:
<Link to={"/edit/" + docId} key={docId}>
<button className="btn pink lighten-1 z-depth-0">Edit</button>
</Link>
This links to the 'broken' EditDetails component I am trying to fix.
Here is my code for the EditProject component
class EditProject extends Component {
state = {
title: "",
content: ""
};
handleChange = e => {
this.setState({
[e.target.id]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
let localProject = this.state;
let docId = this.props.docId;
this.props.editProject(localProject, docId);
const projectDetailURL = "/project/" + docId;
this.props.history.push(projectDetailURL);
};
render() {
const { project, auth } = this.props;
if (!auth.uid) return <Redirect to="/signin" />;
if (project) {
return (
<div className="container section project-details">
<div className="card z-depth-0">
<div className="card-content">
<form onSubmit={this.handleSubmit} className="white">
<h5 className="grey-text text-darken-3">Edit Project</h5>
<div className="input-field">
<label htmlFor="title" className="active">
Title
</label>
<input
onChange={this.handleChange}
type="text"
id="title"
defaultValue={project.title}
/>
</div>
<div className="input-field">
<label htmlFor="content" className="active">
Edit Project Content
</label>
<textarea
id="content"
onChange={this.handleChange}
className="materialize-textarea"
defaultValue={project.content}
/>
</div>
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">
Update
</button>
</div>
</form>
</div>
<div className="card-action grey lighten-4 grey-text">
<div>
Posted by {project.authorFirstName} {project.authorLastName}
</div>
<div>{moment(project.createdAt.toDate()).calendar()}</div>
<div className="right-align" />
</div>
</div>
</div>
);
} else {
return (
<div className="container center">
<p>Loading project...</p>
</div>
);
}
}
}
const mapStateToProps = (state, ownProps) => {
//id = the document id of the project
const id = ownProps.match.params.id;
const projects = state.firestore.data.projects;
const project = projects ? projects[id] : null;
return {
project: project,
auth: state.firebase.auth,
docId: id
};
};
const mapDispatchToProps = dispatch => {
return {
editProject: (project, docId) => dispatch(editProject(project, docId))
};
};
export default compose(
connect(
mapStateToProps,
mapDispatchToProps
),
firestoreConnect([
{
collection: "projects"
}
])
)(EditProject);
Upon visiting the edit page, I would like the data to remain unchanged if a user does not make any changes to an input field.
I was able to properly update my local state by using React Router to pass props to my EditProject component from its "parent component". I used the React router to do this since the EditProject component is not actually nested inside this "parent component".
Here's how you can pass props to other components using React Router:
Specify where you want to send your props and what you want to send:
//ProjectDetails Component
<Link to={{
pathname: "/edit/" + docId,
state: {
title: project.title,
content: project.content
}
}}>
<button className="btn">Edit</button>
</Link>
Aquire props in the componentDidMount() lifecycle method and update the local state using setState().
//EditProject Component (component recieving props from ProjectDetails)
class EditProject extends Component {
state = {
title: "",
content: ""
};
componentDidMount = () => {
//Aquire proprs from React Router
const title = this.props.location.state.title
const content = this.props.location.state.content
//Update the local state
this.setState({
title: title,
content: content
})
}
I hope this helps!
What I want to do is to be able to toggle an active class on my elements that are dynamically created, as to be able to change the css for the selected checkbox, giving the impression that a certain filter is selected. I have looked at so many solutions and guides to make this work for my app, but I can't seem to implement it correctly. Any help would be appreciated.
Checkboxes component
import React from 'react';
const Checkbox = (props) => {
const { label, subKey } = props;
const sub1 = `${subKey}1`;
return (
<label htmlFor={sub1} className="check_label">
{label}
<input
type="checkbox"
id={sub1}
checked={props.isChecked}
onChange={props.handleCheck}
onClick={() => console.log(label)}
value={`${label.toLowerCase()}/?search=`}
/>
</label>
);
};
export default Checkbox;
and the Search component that implements checkboxes
import React, { Component } from 'react';
import Checkbox from './Checkbox';
const APIQuery = 'https://swapi.co/api/';
const searchLabels = ['Planets', 'Starships', 'People', 'Species', 'Films', 'Vehicles'];
export default class Searchbutton extends Component {
constructor(props) {
super(props);
this.state = {
endpointValue: '',
searchValue: '',
};
}
/* Funcionality to handle form and state of form */
/* Changes state of value whenever the form is changed, in realtime. */
handleChange(event) {
this.setState({ searchValue: event.target.value });
}
/* Prevents default formsubmit */
handleSubmit(event) {
event.preventDefault();
}
/* Handles state of checkboxes and sets state as to prepend necessary filter for request */
handleCheck(event) {
this.setState({ endpointValue: event.target.value });
if (this.state.endpointValue === event.target.value) {
this.setState({ endpointValue: '' });
}
}
/* Creates the checkboxes dynamically from the list of labels. */
createBoxes() {
const checkboxArray = [];
searchLabels.map(item => checkboxArray.push(
<Checkbox
key={item}
className="madeBoxes"
subKey={item}
endpointValue={this.state.endpointValue}
handleChange={e => this.handleChange(e)}
handleCheck={e => this.handleCheck(e)}
label={item}
/>,
));
return checkboxArray;
}
render() {
return (
<div className="search_content">
<div className="search_wrapper">
<form onSubmit={this.handleSubmit} method="#">
<label htmlFor="searchBar">
<input type="text" id="searchbar" className="search_bar" value={this.state.searchValue} onChange={e => this.handleChange(e)} />
</label>
<div>
<input type="submit" className="search_button" value="May the Force be with you." onClick={() => this.props.searchWithApi(APIQuery + this.state.endpointValue + this.state.searchValue)} />
</div>
</form>
</div>
<div className="checkboxes">
{this.createBoxes(this.labels)}
</div>
<div className="sort_filters">
{' '}
{/* These are options that the user can make in order to sort and filter the results.
The idea is to make it so that changing the value auto-perform a new request */}
{/* For sorting the returned objects based on user choice */}
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid, until href added */}
Choose sort method
<ul className="sorting">
<li className="sort_optn" href="#" value="lexicographical">Alphabetically</li>
<li className="sort_optn" href="#" value="by_added_date">By added date</li>
<li className="sort_optn" href="#" value="by_added_date_rev">By added date reversed</li>
</ul>
</div>
</div>
);
}
}
You don't really have to do it with react. You can reformat your code a little bit and solve it with CSS :checked pseudo-class.
In particular, don't wrap your checkbox within a label, but instead put the label after the input. Check this fiddle for example: https://jsfiddle.net/8c7a0fx5/
You can use the styled-component package. check the example below on how to use it:
import { Component } from 'react'
import { render } from 'react-dom'
import styled from 'styled-components'
const StyledCheckbox = styled.div`
label {
background: ${props => props.active ? 'red': 'white'}
}
`
class MyAwesomeComponent extends Component {
constructor(){
super()
this.state = {
isChecked: false
}
this.handleOnChange = this.handleOnChange.bind(this)
}
handleOnChange = ()=>{
this.setState({
isChecked: !this.state.isChecked,
})
}
render(){
const { isChecked } = this.state
return(
<StyledCheckbox active={isChecked}>
<label>Names</label>
<input type="checkbox" onChange={this.handleOnChange} />
</StyledCheckbox>
)
}
}
render(<MyAwesomeComponent/>, document.getElementById('root'))
Working code on codepen.io
I have a wizard that has many forms, at the end of the wizard I want to take them back to the first step. However every form is filled in with the previous values.
I just want to unmount and remount it to wipe everything clean. How do I do this in reactjs?
<StepWizard>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
<Step>
<NewComponent/>
</Step>
</StepWizard>
so how would I trigger something to just get "StepWizard" to render in a fresh state?
My components look something like this, I removed code that switches to the next step in the wizard.
export default class NewComponent extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
render() {
return (
<Formik
initialValues={{
name: "",
website: "",
}}
validationSchema={Yup.object().shape({
name: Yup.string().required('Company Name is Required'),
website: Yup.string().url('Company Url is Invalid'),
})}
onSubmit={(
values,
{ setSubmitting, setErrors}
) => {
}}
render={({
values,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched
}) => (
<form onSubmit={handleSubmit}>
<div className="field">
<label className="label">Name</label>
<div className="control">
<input
className="input"
type="text"
name="name"
maxLength="50"
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
/>
<ErrorMessage name="name"/>
</div>
</div>
<div className="field">
<label className="label">Website</label>
<div className="control">
<Field className="input" name="website" type="text" />
<ErrorMessage name="website"/>
</div>
</div>
</form>
)}
/>
);
}
}
I am using Mbox State Tree, so I could store something in my store that could be used to trigger whatever needs to be triggered to cause a reset.
Edit
I should mention that I am using this plugin: https://github.com/jcmcneal/react-step-wizard
So I am not sure if stopping a step from rendering is an option, also then that would mean I would have to handle the previous step state everytime.
I am more looking for something that just blows away everything if possible as I spent already too much time on this area and don't want to rework tons.
Highlighting the above methods you can also do something like this. Lift the default state into an object that can be pre-filled by whatever, hydrate it into the state and then when you call a reset you can control how much you reset the state back to. This is a very generic example but it's one way to overcome your issue.
Click here to view a working example
import React from "react";
import ReactDOM from "react-dom";
// generic stage renderer
const Stage = ({ step, currentStep, children }) => {
return step === currentStep ? <div>{children}</div> : null;
};
// generic input controller
const Input = ({ stateKey, value, handleOnChange }) => (
<input
value={value}
onChange={evt => handleOnChange(stateKey, evt.target.value)}
/>
);
// default state that is used to reference
const defaultState = {
firstName: '',
lastName: '',
// default state can also be prefilled with data..
telephoneNumber: '0123456789',
}
class App extends React.Component {
state = {
step: 1,
...defaultState
};
handleStateUpdate = (key, value) => {
this.setState({
[key]: value
});
};
incrementStep = () => {
if (this.state.step < 3) {
this.setState({
step: this.state.step + 1
});
}
};
goBack = () => {
const { step, lastName, telephoneNumber } = this.state;
this.setState({
step: 1,
// always reset this one
firstName: defaultState.firstName,
// only reset this one if it's step 3
lastName: step > 2
? defaultState.lastName
: lastName,
// last step blargh, let's reset anyway
telephoneNumber: step === 3
? defaultState.telephoneNumber
: telephoneNumber,
});
}
render() {
const { step, firstName, lastName, telephoneNumber } = this.state;
return (
<div>
{JSON.stringify(this.state)}
<h1>Step Wizard - {step}</h1>
<Stage step={1} currentStep={step}>
<Input
stateKey="firstName"
value={firstName}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<Stage step={2} currentStep={step}>
<Input
stateKey="lastName"
value={lastName}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<Stage step={3} currentStep={step}>
<Input
stateKey="telephoneNumber"
value={telephoneNumber}
handleOnChange={this.handleStateUpdate}
/>
</Stage>
<button onClick={this.goBack}>Go Back to Step 1</button>
<button onClick={this.incrementStep}>Next</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can achieve that by storing the current state of the wizard in, you guessed it, state object. That state and actions to mutate it can be passed to children as props. After that, when you need to reset the wizard, you just reset the state.
Here's an oversimplified example:
class StepWizard extends React.Component {
constructor(props) {
super(props);
this.state = {
step1: {},
step2: {}
};
}
setStep(step, data) {
this.setState({ `step${ step }`: data });
}
resetWizard() {
this.setState({
step1: {},
step2: {}
});
}
render() {
return (
<React.Fragment>
<Step
data={ this.state.step1 }
setData={ (data)=> this.setStep(1, data) }
/>
<Step
data={ this.state.step2 }
setData={ (data)=> this.setStep(2, data) }
/>
</React.Fragment>
);
}
}
Now, call resetWizard whenever you'll need to reset the wizard.
How about creating a Step object that would have the render logic for each step? I understand your use case correctly, since you would want to render only one step at a time why not only render which is relevant at that particular step?
Something like below.
class Wizard {
constructor(props) {
super(props);
this.stepMap = {
first: <FirstStep />,
second: <SecondtStep />,
third: <ThirdStep />,
fourth: <FourthStep />
}
this.state = {
activeStep: "first"
}
}
changeStep = (stepId) => {
this.setState({activeStep: stepId});
}
render() {
const activeStepCmp = this.stepMap[this.state.activeStep];
return (
<StepWizard>
{activeStepCmp}
</StepWizard>
)
}
}
I have a page where a user can search a database for a given condition, then the data is returned with another button that the user can use to add information back to the database. However whenever I click on the second button, the page reloads. I can't get so much as a console.log to go in. I'm new to react and could use any help at all.
import React , { Component } from 'react';
import { database } from '../firebase';
const byPropKey = (propertyName, value) => () => ({
[propertyName]: value,
});
class Search extends Component{
constructor(props) {
super(props);
this.state={
users: null,
searchCondition: "",
friend: ""
}
// this.setState = this.setState.bind(this);
}
onSubmit = (event) => {
let {
searchCondition,
friend
} = this.state;
database.searchConditions(searchCondition).then(snapshot =>
this.setState(() => ({ users: snapshot.val() }))
);
event.preventDefault();
}
messageSubmit = (event) => {
console.log("Click")
}
render(){
let {
users,
searchCondition,
friend
} = this.state;
return(
<div>
<h1>Search for conditions</h1>
<form onSubmit={this.onSubmit}>
<div className="search">
<input
value={searchCondition}
onChange={event => this.setState(byPropKey('searchCondition', event.target.value))}
type="text"
placeholder="Condition to Search For"
/>
<button className="friendButton"
onClick="x"
type="submit">
Search
</button>
</div>
</form>
{!!users && <UserList users={users} />}
</div>
)
}
}
let UserList = ({ users, message }) =>
<div>
<h2>List of Usernames and Conditions of your Search</h2>
{Object.keys(users).map(key =>
<div key={key}>{users[key].username} : {users[key].condition}
<form>
<div className="search">
<input
value={message}
onChange={console.log("test")}
type="text"
placeholder="Message for this User"
/>
<button className="messageButton"
onClick={console.log(message)}
type="submit">
Message
</button>
</div>
</form>
</div>
)}
</div>
export default Search;
Have you tried to place the event.preventDefault() at the beginning of the event handler?
It should prevent the default behaviour imediately as the event gets fired.
Hope it works!
a couple things i can see, youre even.preventDefault() should be at the top of the page, you said it was reloading so thats unwanted behavior. second you should set state within the then, generally speaking in my experience that doesnt work- i believe due to setState being asynchronous or something of that nature.
i would rewrite your submit like this
onSubmit = (event) => {
event.preventDefault();
let {
searchCondition,
friend
} = this.state;
let value;
database.searchConditions(searchCondition).then(snapshot =>
value = snapshot.val
);
this.setState(() => ({ users: value) }))
}
also likely the reason your "messageSubmit()" was not console logging is because youre using a submit handler not a click handler so everytime your clicked you were reloading the page.
cheers