how to syncState to firebase with re-base? - reactjs

I'm trying to persist data with re-base but with this setup, I'm getting nothing. When I try to addExpense I'm getting no errors and no new object.
My re-base setup here:
class ExpensesContainer extends Component {
constructor(props) {
super(props);
this.state = {
expenses: {
expenseList: []
}
}
}
componentDidMount(){
base.syncState(`/`, {
context: this,
state: 'expenses.expenseList',
asArray: true
});
}
addExpense = (expense) => {
this.setState({
expenses: this.state.expenses.expenseList.concat([expense]) //updates Firebase and the local state
});
}
If I'm not using componentDidMount then my addExpense action looks like this and everything works well:
// addExpense = (expense) => {
// const expenseList = [...this.state.expenses.expenseList, expense];
// this.setState({expenses: {expenseList}});
// }

expenses: this.state.expenses.expenseList.concat([expense])
on the .setState() method you're passing the wrong value, that in the end will look like { expenses: [<list here>] } instead should be:
expenses: { expenseList: this.state.expenses.expenseList.concat([expense])}
and re-base doesn't work only with .concat() (by reading the documentation I also though the same)
This said, you just need to .setState() the expected variable and it should update.

In case someone gets into a similar issue - this the solution that worked for me:
class ExpensesContainer extends Component {
constructor(props) {
super(props);
this.state = {
expenseList: []
}
}
componentDidMount(){
base.syncState(`/expenses`, {
context: this,
state: 'expenseList',
asArray: true
});
}
deleteExpense = (expenseToRemove) => {
const expenseList = [...this.state.expenseList].filter((expense) => {
return expenseToRemove !== expense.uid;
});
this.props.history.push("/expenses");
this.setState({expenseList});
}
addExpense = (expense) => {
this.setState({
expenseList: this.state.expenseList.concat([expense])
});
this.props.history.push("/");
}

Related

How can I chain asynchronous Firebase updates in my React app?

React & Firebase newbie here. I have a React component that needs to look up some stuff in Firebase before rendering. My database design requires first getting the correct doohick ids and subsequently looking up the doohick details, but I'm not sure how to do that with the asynchronous nature of Firebase database access. This doesn't work:
class Widget extends React.Component {
componentDidMount() {
firebase.database().ref(`/users/${username}/doohick-ids`).on('value', snapshot => {
this.setState({doohick_ids: doohick_ids});
});
this.state.doohick_ids.forEach(id => {
// ids don't actually exist at this point outside the callback
firebase.database().ref(`/doohick-details/${id}`).on('value', snapshot => {
// update state
});
});
render() {
if (this.state.doohick-ids) {
return null;
} else {
// render the Doohick subcomponents
}
}
}
I can think of a few solutions here, but none that I like. What's the recommended way to chain together Firebase calls, or perhaps redesign this to eliminate the problem?
I think you should split one component Widget to two WidgetList and WidgetItem.
WidgetItem
subscribe and unsubscribe to firebase.database().ref(/doohick-details/${id})
class WidgetItem extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
}
constructor(props) {
super(props);
this.state = {};
this.dbRef = null;
this.onValueChange = this.onValueChange.bind(this);
}
componentDidMount() {
const { id } = this.props;
this.dbRef = firebase.database().ref(`/doohick-details/${id}`);
this.dbRef.on('value', this.onValueChange);
}
componentWillUnmount() {
this.dbRef.off('value', this.onValueChange);
}
onValueChange(dataSnapshot) {
// update state
this.setState(dataSnapshot);
}
render() {
return (
<pre>{JSON.stringify(this.state, null, 2)}</pre>
);
}
}
WidgetList
subscribe and unsubscribe to firebase.database().ref(/users/${username}/doohick-ids)
class WidgetItem extends React.Component {
constructor(props) {
super(props);
this.state = { doohick_ids: [] };
this.dbRef = null;
this.onValueChange = this.onValueChange.bind(this);
}
componentDidMount() {
// Note: I've just copied your example. `username` is undefined.
this.dbRef = firebase.database().ref(`/users/${username}/doohick-ids`);
this.dbRef.on('value', this.onValueChange);
}
componentWillUnmount() {
this.dbRef.off('value', this.onValueChange);
}
onValueChange(dataSnapshot) {
this.setState({ doohick_ids: dataSnapshot });
}
render() {
const { doohick_ids } = this.state;
if (doohick_ids.length === 0) {
return 'Loading...';
}
return (
<React.Fragment>
{doohick_ids.map(id => <WidgetItem key={id} id={id} />)}
</React.Fragment>
);
}
}
And code that requires the data from the database needs to be inside the callback that is invoked when that data is available. Code outside of the callback is not going to have the right data.
So:
firebase.database().ref(`/users/${username}/doohick-ids`).on('value', snapshot => {
this.setState({doohick_ids: doohick_ids});
doohick_ids.forEach(id => {
// ids don't actually exist at this point outside the callback
firebase.database().ref(`/doohick-details/${id}`).on('value', snapshot => {
// update state
});
});
});
There's many optimizations possible here, but they all boil down to the code being inside the callback and updating the state when a value comes from the database.

React Hooks in class using timeout

I am quite new in React... I have page where window is showing with little delay..
it is made with Hooks:
export default function LoginPage() {
const [cardAnimaton, setCardAnimation] = React.useState('cardHidden');
setTimeout(function() {
setCardAnimation('');
}, 700);
<form>
<Card login className={classes[cardAnimaton]}>
Now I want to use classes in that page and I want to preserve the same effect..
So I am trying something like:
export default class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
cardHidden: true,
};
}
componentDidMount() {
this.timeout = setTimeout(() => {
this.setCardAnimation('');
}, 700);
}
setCardAnimation = () => {
this.cardAnimaton({ cardHidden: false });
};
I have no idea... got stuck there...
You just need to set your cardHidden: false inside componentDidMount and then you can add animation based on cardHidden state.
here is a working Demo which I used cardHidden state to show or hide different text on screen which you can use this method for adding different animation.
just click Run code snippet to see how it works
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
cardHidden: true,
};
}
componentDidMount() {
this.timeout = setTimeout(() => {
this.setState({
cardHidden: false
});
}, 700);
}
render() {
if(this.state.cardHidden){
return <div>I'm Hidden</div>
} else {
return <div>Haha, I'm Visible</div>
}
}
}
const rootDiv = document.getElementById('root');
ReactDOM.render(<LoginPage />, rootDiv);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
In class-based component, you need to use this.setState to update the state.
export default class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
cardHidden: true,
};
}
componentDidMount() {
this.timeout = setTimeout(() => {
this.setState({ cardHidden: false });
}, 700);
}
...
check https://reactjs.org/docs/react-component.html#setstate

How to receive props only after state of parent has updated?

I'm trying to build a little weather widget, where the geolocation of the user is captured in one component and then passed onto a child component which fetches the weather data (based on the location) and then eventually renders an icon indicating the current weather conditions.
I'm passing the longitude and latitude state as props to my WeatherWidget. Unfortunately, the WeatherWidget also receives the initial state null. How I can I avoid that?
Thank you for your help!
class GetGeolocation extends Component{
constructor(){
super();
this.state = {
lngt: null,
latd: null
}
}
componentDidMount(){
this.getLocation()
}
getLocation = () => {
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(position => {
this.setState({lngt: position.coords.longitude.toFixed(4)});
this.setState({latd:position.coords.latitude.toFixed(4)});
}
);
};
}
render(){
return (
<>
<WeatherWidget lngt = {this.state.lngt} latd = {this.state.latd} />
</>
)
}
class WeatherWidget extends Component{
constructor(props){
super(props);
this.state = {
weather:[]
}
}
componentWillReceiveProps(nextProps){
this.getWeather(nextProps)
}
getWeather = (location) => {
console.log(location)
// The console logs twice:
// First:
//{lngt: "-12.3456", latd: null}
//Then, the correct values:
//{lngt: "-12.3456", latd: "78,9999"}
}
Don't use componentWillReceiveProps, that will be deprecated in later versions of React.
But also, you can just setup conditional logic in your life-cycle methods to determine what code to execute.
componentWillReceiveProps(nextProps){
//condition says if both value are truthy then run code.
if(nextProps.lngt && nextProps.latd){
this.getWeather(nextProps)
}
}
You can also use componentDidUpdate()
componentDidUpdate(){
//condition says if both value are truthy then run code.
if(this.props.lngt && this.props.latd){
this.getWeather(this.props)
}
}
One option is to conditionally render in the parent component:
class GetGeolocation extends React.Component {
constructor(props) {
super(props);
this.state = {
lngt: null,
latd: null
};
}
componentDidMount() {
this.getLocation();
}
getLocation = () => {
// Simulate the network request
setTimeout(() => this.setState({ lngt: 100 }), 1000);
setTimeout(() => this.setState({ latd: 100 }), 1000);
};
render() {
const { lngt, latd } = this.state;
if (!lngt || !latd) return null;
return <WeatherWidget lngt={lngt} latd={latd} />;
}
}
class WeatherWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
weather: []
};
}
componentDidMount() {
this.getWeather(this.props);
}
getWeather = location => {
console.log(location);
};
render() {
return null;
}
}

ReactJS - Pass Updated Value To Sub-Component Method

I'm working on an environment that is basically set up with a Main Component like this:
class MainComponent extends Component {
constructor(props) {
super(props);
this.state = {
selectedValues: []
};
}
render() {
const { selectedValues } = this.state;
return (
// Other components
<SubComponent selectedValues = {selectedValues} />
// Other components
);
}
}
export default MainComponent;
And a Sub Component like this:
class SubComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isExporting: false,
selectedValues: props.selectedValues
};
}
performTask = () => {
this.setState({ isWorking: true });
const { selectedValues } = this.state;
console.log(`Selected Values: ${selectedValues}`);
fetch('/api/work', {
method: 'GET'
})
.then(res => res.json())
.then((result) => {
// Handle the result
this.setState({ isWorking: false });
})
.catch((error) => {
console.log(error);
this.setState({ isWorking: false });
});
};
render() {
const { isWorking } = this.state;
return (
<Button
bsStyle="primary"
disabled={isWorking}
onClick={() => this.performTask()}
>
{isWorking ? 'Working...' : 'Work'}
</Button>
);
}
}
SubComponent.propTypes = {
selectedValues: PropTypes.arrayOf(PropTypes.string)
};
SubComponent.defaultProps = {
selectedValues: []
};
export default SubComponent;
In the Main Component, there are other components at work that can change the selectedValues. The functionality I'd like to see is that when the performTask method fires, it has the most recent and up to date list of selectedValues. With my current setup, selectedValues is always an empty list. No matter how many values actually get selected in the Main Component, the list never seems to change in the Sub Component.
Is there a simple way to do this?
I would suggest you 2 of the following methods to check this problem:
Maybe the state.selectedItems doesn't change at all. You only declare it in the contractor but the value remains, since you didn't setState with other value to it. Maybe it will work if you will refer to this.props.selectedItems instead.
Try to add the function component WillReceiveProps(newProps) to the sub component and check the value there.
If this method doesn't call, it means the selectedItems doesnt change.
Update if some of it works.
Good luck.
selectedValues in SubComponent state has not updated since it was set in SubComponent constructor. You may need to call setState again in componentWillReceivedProps in SubComponent

Access another component's method and have defaultProps for it

I'd like to access another component's userLogout function.
I have read this react-js-access-to-component-methods. However, the only way that seems to work for me is what follows.
Does anyone know another way that would be easier, shorter? My goal is to get all the logic out of Base component.
#azium pointed out that I'm using a derived class. The goal was initially to have access to static defaultProps so the problem was approached the wrong way.
class Funcs extends React.Component {
// this is the derived class way I was hoping to have (much cleaner)
static defaultProps = {
text: 'hello'
}
constructor(props) {
super(props);
}
userLogout() {
console.log('userLogout');
}
render() {
return null;
}
}
class Base extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
MyWidget = (el, refCb) => {
ReactDOM.render(<Funcs ref={refCb} />, el);
};
componentDidMount() {
this.MyWidget(document.getElementById('nothing'), widget => {
console.log('there you are...', widget);
this.setState({
widget
});
// works too
this.widget = widget
});
}
render() {
console.log('widget', this.state.widget, this.widget);
return <div id="nothing" />
}
}
Here's the solution to have defaultProps on a non derived class.
class Funcs {
constructor(props) {
this.props = Object.assign({}, this.defaultProps, props);
}
defaultProps = {
userLogout: {
onCompleted: () => this.props.nextRouter.pushRoute('home_v01', { lng: this.props.nextRouter.query.lng }),
onError: err => console.log('An error occured, ', err)
}
};
userLogout = () => {
LogoutMutation.commit({
environment: this.props.environment,
onCompleted: this.props.userLogout.onCompleted,
onError: this.props.userLogout.onError
});
};
}
class Base extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
funcs = new Funcs({
environment: this.props.relay.environment,
nextRouter: this.props.nextRouter,
userLogout: {
onCompleted: () => console.log('LOG OUT')
}
});
render() {
return <div onClick={this.funcs.userLogout}>Log out</div>
}
}

Resources