observer mobx-react seems not working in React - reactjs

I am a starter to React and mobx. Based on my design, data should be updated after click the button using observer, observable modules. Even though the console log displays the email changed whenever clicking the button, the view doesn't change at all. Could you give me any advice?
import Link from 'next/link';
import React, { useState } from 'react';
import ProfileImage from '../components/ProfileImage';
import faker from 'faker';
import { decorate, observable } from 'mobx';
import { observer } from "mobx-react"
class Data {
avartar = faker.image.avatar();
email = faker.internet.email();
name = {
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
};
}
decorate(Data, {
avartar: observable,
email: observable,
name: observable,
})
class Index extends React.Component {
data = new Data();
generate = () => {
this.data.email = faker.internet.email();
this.data.name.firstName = faker.name.firstName();
this.data.avartar = faker.image.avatar();
console.log("check: ", this.data.email);
}
render() {
return (
<>
<h1>Index</h1>
<button className="btn btn-primary" onClick={this.generate}>Change</button>
<div>
<dl className="row">
<dt className="col-sm-3">Avatar</dt>
<dd className="col-sm-9"><img src={this.data.avartar} /></dd>
<dt className="col-sm-3">Name</dt>
<dd className="col-sm-9">{this.data.name.firstName} {this.data.name.lastName}</dd>
<dt className="col-sm-3">Email</dt>
<dd className="col-sm-9">{this.data.email}</dd>
</dl>
</div>
</>);
}
}
Index = observer(Index);
export default Index;

You need to also decorate the Index.data property with #observable.
#observer
class Index extends React.Component {
#observable data = new Data();
}
// Or non-decorator syntax:
Index = observer(decorate(Index, { data: observable }));

Related

Having trouble rendering data in react component

I'm trying to render the following the 'dogName' value of the following array to the browser, but it's coming up as 'undefined':
[
{
"id": 1,
"dogName": "bruce"
},
{
"id": 2,
"dogName": "borker"
},
{
"id": 3,
"dogName": "henry"
}
]
So, first of all, the data is pulled from a database and set in state in the parent component, where's it's passed as props to the child component 'DogNameList' (which I've trimmed down to just the relevant bits):
import React from 'react';
import './styles.css'
import DogList from './DogList'
import Dogue from './Dogue'
import axios from 'axios'
import DogNameList from './DogNameList'
class App extends React.Component {
constructor(){
super()
this.state = {
**dogName:[]**
}
}
componentDidMount() {
axios.get('http://localhost:3000/dogs')
.then(res => {
this.setState({
**dogName:res.data**
})
})
.catch(error => {
console.log(error)
})
}
render() {
return (
<div>
<DogNameList **names = {this.state.dogName}**/>
<Dogue/>
</div>
);
}
}
export default App;
In DogNameList, the data is mapped over and then passed as props to the 'Dogue' component (stupid names, I know, but this is a personal project):
import React from 'react'
import Dogue from './Dogue'
const DogNameList = (props) => {
return(
<div>
{
props.names.map(name => {
console.log(name.dogName)
return <Dogue name = {name} key ={name.id}/>
})
}
</div>
)
}
export default DogNameList
finally, it's supposed to be rendered to the browser via the 'Dogue' component:
import React from 'react'
import axios from 'axios'
class Dogue extends React.Component {
constructor(props){
super(props)
this.state = {
}
}
render(){
return (
<div>
<img className = 'img' src = {this.props.dogList}/>
<br/>
<form className = 'form'>
<input type = 'text' placeholder = 'Enter dog name'/>
<br/>
<button>Submit</button>
</form>
**<h2>dog name: {this.props.name}</h2>**
</div>
)
}
}
export default Dogue
Any ideas why it's not working? I console logged the following and it returned the list of names (not as strings, I should add):
props.names.map(name => {
console.log(name.dogName)
First of all, replace this
<h2>dog name: {this.props.name}</h2>
with this
<h2>dog name: {this.props.name.dogName}</h2>
because you are creating a component with object, so name property actually holds the object, not the name property of the object.
return <Dogue name = {name} key ={name.id}/>
You also don't declare somewhere this property
{this.props.dogList}
Also to handle the undefined error messages, do this
{this.state.dogName && <DogNameList names ={this.state.dogName}/>}

Stripe - how do I save card element in react?

I'm trying to save card details for use later.
I have generated the SetupIntent client secret
I'm trying to use confirm card setup.
I'm following the docs here for react.
The following line:
const cardElement = this.props.elements.getElement('card')
is throwing me this error:
TypeError: Cannot read property 'getElement' of undefined
Where am I going wrong? My code is below:
This is the relevant portion of the main component:
import React from "react";
import { Elements, StripeProvider } from "react-stripe-elements";
import SaveCardForm from "./SaveCardForm";
<StripeProvider
apiKey={process.env.REACT_APP_API_STRIPE_PUBLISH}
>
<Elements>
<SaveCardForm/>
</Elements>
</StripeProvider>
And this is the SaveCardForm component
import React, { Component } from "react";
import { Stripe, CardElement, injectStripe } from "react-stripe-elements";
import axios from "axios";
class SaveCardForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit = e => {
e.preventDefault()
const cardElement = this.props.elements.getElement('card');
axios.get(`${process.env.REACT_APP_API}/saveCardDetails`).then(res => {
console.log('res.data', res.data)
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card: cardElement,
},
}).then( confirmCardSetupRes => {
console.log('confirmCardSetupRes', confirmCardSetupRes)
})
})
}
render() {
return (
<div>
<CardElement />
<button onClick={this.submit}>
Bid For Tickets
</button>
</div>
);
}
}
export default injectStripe(SaveCardForm);
Given your components, there is no prop named elements passed into SaveCardForm. If it's access to CardElement you are after, use a ref which will give you a direct reference to that component e.g.
constructor(props) {
...
this.cardEl = React.createRef();
}
submit = e => {
...
const card = this.cardEl.current.<accessDomHere>;
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card
},
}).then(...)
}
render() {
...
<div>
<CardElement ref={this.cardEl} />
...
</div>
}
Switch out <accessDomHere> for whatever DOM query you need to perform to get the information you need. There may even be a React property or function you can access (I'm not familiar with the component).
I resolved this by updating to the latest version of react-stripe-elements.
There is an error in the versions before 5.1.0

DetailsList component is not taking into account selection property

I have created the following sample application to try and simplify as much as possible an issue I am having as part of a larger application I am working on.
The issue has to do with the selection property of the DetailsList component in office-ui-fabric-react.
I have included the main components of the sample application below to try and illustrate my issue.
App.tsx
import * as React from 'react';
import './App.css';
import { ClickedItemsList } from './ClickedItemsList';
import { GenerateItemButton } from './GenerateItemButton';
import { ItemSelectionStore } from './ItemSelectionStore';
class App extends React.Component {
public itemSelectionStore: ItemSelectionStore = new ItemSelectionStore();
public render() {
return (
<div>
<GenerateItemButton store={this.itemSelectionStore} />
<ClickedItemsList store={this.itemSelectionStore} />
</div>
);
}
}
export default App;
ItemSelectionStore.tsx
import * as React from 'react';
import './App.css';
import { ClickedItemsList } from './ClickedItemsList';
import { GenerateItemButton } from './GenerateItemButton';
import { ItemSelectionStore } from './ItemSelectionStore';
class App extends React.Component {
public itemSelectionStore: ItemSelectionStore = new ItemSelectionStore();
public render() {
return (
<div>
<GenerateItemButton store={this.itemSelectionStore} />
<ClickedItemsList store={this.itemSelectionStore} />
</div>
);
}
}
export default App;
GenerateItemButton.tsx
import * as React from 'react';
import { ItemSelectionStore } from './ItemSelectionStore';
export interface IGenerateItemButtonProps {
store: ItemSelectionStore
}
export class GenerateItemButton extends React.Component<IGenerateItemButtonProps> {
public render() {
return (
<button onClick={this.handleClick}/>
)
}
private handleClick = () =>
this.props.store.onAddItemButtonClicked();
}
ClickedItemList
import { observer } from 'mobx-react';
import {CheckboxVisibility, DetailsList, IColumn, IObjectWithKey, Selection, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
import * as React from 'react';
import { ItemSelectionStore } from './ItemSelectionStore';
export interface IClickedItemsListProps {
store: ItemSelectionStore
}
#observer
export class ClickedItemsList extends React.Component<IClickedItemsListProps> {
private columns: IColumn[] = [{ key: 'key', name: 'Extracted key', fieldName: 'key', minWidth: 150 }];
public render() {
const { store } = this.props;
const selectedItems = store.selectedItems;
// creating items to display
const itemsToDisplay: IObjectWithKey[] = [];
selectedItems.forEach(k => itemsToDisplay.push({'key': k}));
// creating selection
const selection = new Selection();
selection.setItems(itemsToDisplay);
selection.setAllSelected(true);
return (
<DetailsList
items={itemsToDisplay}
selectionPreservedOnEmptyClick={true}
columns={this.columns}
checkboxVisibility={CheckboxVisibility.always}
selectionMode={SelectionMode.multiple}
selection={selection}
/>
)
}
}
When a user clicks on the button a random string is added to a list of items which are dynamically displayed by the ClickedItemsList component.
The items appear as they should on every click but I am having trouble with the selection property.
I want all items added to the list to also be in a selected state but my sample application fails to do so and all items simply appear in a non selected state.
Thoughts ?

How to manage select box (representing a relation between entities) with Meteor React?

I'm using Meteor/React to build a social App. Users are related to others by a ManyToOne relation (a user have a representative).
So I made a Member Component which return a template with a form inside it to select the representative, and created an API to execute some actions on users.
Everything works quite well but the delete function do not update the client side (I opened another window with the application).
I feel like i'm missing the point with the Component notion, should that select box be a Component too ?
Here is Member.jsx
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { Members } from '../api/members.js';
// Member component - represents a single member item
export default class Member extends Component {
componentDidMount() {
ReactDOM.findDOMNode(this.refs.select_representant).value = this.props.member.representant;
}
componentDidUpdate() {
ReactDOM.findDOMNode(this.refs.select_representant).value = this.props.member.representant;
}
setRepresentant() {
Meteor.call('members.setRepresentant', this.props.member._id, 'oo');
}
deleteThisMember() {
Meteor.call('members.remove', this.props.member._id);
}
renderRepresentants() {
let representants = Members.find().fetch();
return representants.map((representant) => (
<option key={representant._id} value={representant._id}>{representant.pseudo}</option>
));
}
handleSubmit(event) {
event.preventDefault();
// Find the text field via the React ref
const representantId = ReactDOM.findDOMNode(this.refs.select_representant).value.trim();
const representant = Members.findOne({ _id: representantId });
Meteor.call('members.setRepresentant', this.props.member._id, representantId);
}
render() {
return (
<div>
<h3 className="text">
{this.props.member.pseudo} <button className="delete" onClick={this.deleteThisMember.bind(this)}>×</button>
</h3>
<form className="form-horizontal">
<div className="form-group">
<label htmlFor="select_representant" className="col-sm-3 control-label">Représentant</label>
<div className="col-sm-7">
<select ref="select_representant" className="form-control custom-select" name="select_representant" onChange={this.handleSubmit.bind(this)}>
{this.renderRepresentants()}
</select>
</div>
</div>
</form>
</div>
);
}
}
and members.jsx
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
export const Members = new Mongo.Collection('members');
Meteor.methods({
'members.insert'(pseudo) {
check(pseudo, String);
Members.insert({
pseudo
});
},
'members.remove'(memberId) {
check(memberId, String);
Members.remove(memberId);
represented = Members.find({ representant: memberId }).fetch();
for(representedItem in represented){
Members.update(representedItem, { $set: { representant: null } });
}
},
'members.setRepresentant'(memberId, representantId) {
check(memberId, String);
check(representantId, String);
Members.update(memberId, { $set: { representant: representantId } });
},
});
Try this:
deleteThisMember() {
Meteor.call('members.remove', this.props.member._id);
renderRepresentants();
}
Or, you tried to put your member list on thie.props?
Since you query for the Members within the React render function, it's not reactive with the changes in the database. You need to use createContainer from the react-meteor-data to see the changes in your front-end:
import { createContainer } from 'meteor/react-meteor-data';
...
export default class Member extends Component {
...
renderRepresentants() {
let representants = this.props.representants;
...
}
...
}
createContainer(() => {
return {
representants: Members.find().fetch()
};
}, Member);
Now the query should be properly reactively updated when there are changes in the Members collection.

Redux Simple Router pushPath not updating URL

I ported redux-simple-router into a boilerplate react/redux isomorphic kit (https://github.com/erikras/react-redux-universal-hot-example). I have a simple click event handler that calls 'pushPath' from redux-simple-router. However, pushPath doesn't seem to update my URL. I already implemented the initial port (syncReduxAndRouter) and other routes seem to work fine (other routes use updatePath). Is there something else I need to do to get this to work?
import React, {Component} from 'react';
import { pushPath } from 'redux-simple-router';
import {connect} from 'react-redux';
#connect(null,
{ pushPath })
export default class MyContainer extends Component {
constructor() {
super();
this.state = { links: [{key: 0, name: 'Link1'}, {key: 1, name: 'Link2'}, {key: 2, name: 'Link3'}] };
}
// pass in redux actions as props
handleClick(value) {
pushPath('/Links/' + value);
}
render() {
return (
<div className="container">
<div>Search bar here</div>
<div className={styles.tile_container}>
Tiles here
{this.state.links.map(source =>
<div name={link.name} key={link.key} className={styles.source_tile} onClick= {this.handleClick.bind(this, link.name)}>{link.name}</div>
)}
</div>
</div>
);
}
}
Here's the version of my code with the fix. I needed to use an instance of redux-simple-router that was connected to the store and then pass its methods to the component as a prop.
import React, {Component, PropTypes} from 'react';
import { pushPath } from 'redux-simple-router';
import {connect} from 'react-redux';
#connect(null,
{ pushPath })
export default class MyComponent extends Component {
static propTypes = {
pushPath: PropTypes.func.isRequired
};
constructor() {
super();
this.state = { links: [{key: 0, name: 'Link1'}, {key: 1, name: 'Link2'}, {key: 2, name: 'Link3'}] };
}
// pass in redux actions as props
handleClick(value) {
this.props.pushPath('/Links/' + value);
}
render() {
return (
<div className="container">
<div>Search bar here</div>
<div className={styles.tile_container}>
Tiles here
{this.state.links.map(source =>
<div name={link.name} key={link.key} className={styles.source_tile} onClick= {this.handleClick.bind(this, link.name)}>{link.name}</div>
)}
</div>
</div>
);
}
}
You are calling action creator pushPath instead of the bound method.
Action creator just returns plain object. To call bound method, you should do
handleClick(value) {
this.props.pushValue('/Links/' + value);
}
#connect create proper methods for dispatching and propagate it to you via props.

Resources