A question about structure of class component in React - reactjs

As we know, the structure of a class component can be simplified as the following:
// Blank 1
class Books extends Component {
// Blank 2
render(){
// Blank 3
return()
}
export default Books;
So just for example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { updateFilters } from '../../../services/filters/actions';
import Checkbox from '../../Checkbox';
import GithubStarButton from '../../github/StarButton';
import './style.scss';
const availableSizes = ['XS', 'S', 'M', 'ML', 'L', 'XL', 'XXL'];
class Filter extends Component {
static propTypes = {
updateFilters: PropTypes.func.isRequired,
filters: PropTypes.array
};
componentWillMount() {
this.selectedCheckboxes = new Set();
}
toggleCheckbox = label => {
if (this.selectedCheckboxes.has(label)) {
this.selectedCheckboxes.delete(label);
} else {
this.selectedCheckboxes.add(label);
}
this.props.updateFilters(Array.from(this.selectedCheckboxes));
};
createCheckbox = label => (
<Checkbox
classes="filters-available-size"
label={label}
handleCheckboxChange={this.toggleCheckbox}
key={label}
/>
);
createCheckboxes = () => availableSizes.map(this.createCheckbox);
render() {
return (
<div className="filters">
<h4 className="title">Sizes:</h4>
{this.createCheckboxes()}
<GithubStarButton />
</div>
);
}
}
const mapStateToProps = state => ({
filters: state.filters.items
});
export default connect(
mapStateToProps,
{ updateFilters }
)(Filter);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchProducts } from '../../services/shelf/actions';
import { addProduct } from '../../services/cart/actions';
import Product from './Product';
import Filter from './Filter';
import ShelfHeader from './ShelfHeader';
import Clearfix from '../Clearfix';
import Spinner from '../Spinner';
import './style.scss';
class Shelf extends Component {
static propTypes = {
fetchProducts: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
addProduct: PropTypes.func.isRequired,
filters: PropTypes.array,
sort: PropTypes.string
};
state = {
loading: false
};
componentWillMount() {
const { filters, sort } = this.props;
this.handleFetchProducts(filters, sort);
}
componentWillReceiveProps(nextProps) {
const { filters: nextFilters, sort: nextSort } = nextProps;
if (nextFilters !== this.props.filters) {
this.handleFetchProducts(nextFilters, undefined);
}
if (nextSort !== this.props.sort) {
this.handleFetchProducts(undefined, nextSort);
}
}
handleFetchProducts = (
filters = this.props.filters,
sort = this.props.sort
) => {
this.setState({ loading: true });
this.props.fetchProducts(filters, sort, () => {
this.setState({ loading: false });
});
};
render() {
const { products } = this.props;
const p = products.map(p => {
return (
<Product product={p} addProduct={this.props.addProduct} key=
{p.id} />
);
});
return (
<React.Fragment>
{this.state.loading && <Spinner />}
<Filter />
<div className="shelf-container">
<ShelfHeader productsLength={products.length} />
{p}
<Clearfix />
</div>
<Clearfix />
</React.Fragment>
);
}
}
const mapStateToProps = state => ({
products: state.shelf.products,
filters: state.filters.items,
sort: state.sort.type
});
export default connect(
mapStateToProps,
{ fetchProducts, addProduct }
)(Shelf);
Except for state and life cycle methods, sometimes we define other types of attributes and functions in Blank 1, sometimes in Blank 2, sometimes in Blank 3. So I am wondering when we are going to define attributes and functions, which part should we choose? Is there a convention or something like that?

Block 1 is for defining variables and functions which are not depended on component ,these are general variables and functions which could be used in the component and can even be exported in another files.
Block 2 is for defining component specific variables and methods, define lifecycle methods.variables and methods defined in block 2 could be accessed using this keyword.
Block 3 is used when we want to execute certain piece of code,every time when render method is executed.Apart from initial render, render method is executed every time when setState is performed,so avoid writing code in block 3 as it's excessive.
Hope this helps,
Cheers !!

Related

React Speech Recognition - inserting the text to the memory by updating the state

There is a similar question but I can't comment on it so I opening a new one.
I am new to React and try to implement React SpeechRecognition component for my app. The text should be in an input box. the code for it (from react doc [https://www.npmjs.com/package/react-speech-recognition][1] - with span tag instead of an input):
import React, { PropTypes, Component } from 'react'
import SpeechRecognition from 'react-speech-recognition'
const propTypes = {
// Props injected by SpeechRecognition
transcript: PropTypes.string,
resetTranscript: PropTypes.func,
browserSupportsSpeechRecognition: PropTypes.bool
}
class Dictaphone extends Component {
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props
if (!browserSupportsSpeechRecognition) {
return null
}
return (
<div>
<button onClick={resetTranscript}>Reset</button>
<span>{transcript}</span>
</div>
)
}
}
Dictaphone.propTypes = propTypes
export default SpeechRecognition(Dictaphone)
Now I try to update a state of text (a string) by the transcript (the words that have been already recognized) but I can't make it.
from an earlier question, someone suggested this:
<input
type="text"
value={transcript}
onChange={event => this.onInputChange(event.target.value)}
/>
now when I speak, I do see the words in the input box,
so the final code should be :
import React, { Component } from "react";
import PropTypes from "prop-types";
import SpeechRecognition from "react-speech-recognition";
const propTypes = {
// Props injected by SpeechRecognition
transcript: PropTypes.string,
resetTranscript: PropTypes.func,
browserSupportsSpeechRecognition: PropTypes.bool
};
class Dictaphone extends Component {
constructor() {
super();
this.state = {
text: '',
events: []
}
}
onInputChange = (event) => {
console.log (event.target.value);
this.setState( {text: event.target.value} );
}
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props;
if (!browserSupportsSpeechRecognition) {
return null
}
return (
<div>
<button onClick={resetTranscript}>Reset</button>
<input
className='bg-light-blue'
type="text"
value={transcript}
onChange={event => this.onInputChange(event.target.value)}
/>
</div>
)
}
}
Dictaphone.propTypes = propTypes;
export default SpeechRecognition(Dictaphone);
but when I console.log(event.target.value) which is text - I see nothing so I'm doing something wrong.
Note that if I just write in the render func:
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props;
var x = transcript;
console.log('x is ',x);
console.log('x length is: ',x.length);
.....
it does console the transcript (x) but it's not what I want - I need to save it in text by updating the state.
any suggestion?
If you need to store the transcript prop in your state you should do something like this.
componentDidUpdate(prevProps){
if(prevProps.transcript !== this.props.transcript){
this.setState({
text: this.props.transcript
});
}
}
In your render method use this.state.text to show in the input value.
Also in your constructor do
this.state = {
text: props.transcript
}

redux state changed but connected component didn't update, can't understand mutation

I know that problem is that there is a mutation. Because mostly there is no rerendering because of it. But, can't understand what's wrong in the way I'm doing this.
For data which I get from backend everything is fine, but if I try to change state from FE it's not working.
The problem is with groupDevicesBySelectedFilter(devicesGroups).
After action is done, I get response that state was changed in console, but as in the title no changings on FE.
Filter.tsx
import * as React from 'react'
import {IAppState} from '../../reducers'
import {connect} from 'react-redux'
import { Dropdown, Header, Icon } from 'semantic-ui-react'
import { INodeTreeFilters, INodeTreeDevicesInfo } from './type-definition';
import * as nodeTreeActions from '../../actions/node-tree';
import * as _ from 'lodash';
interface INodeTreeFilterProps{
filters: INodeTreeFilters;
selectGroupsFilter: any;
groupDevicesBySelectedFilter: typeof nodeTreeActions.groupDevicesBySelectedFilter;
devices: INodeTreeDevicesInfo
}
class NodeTreeFilter extends React.Component<INodeTreeFilterProps>{
public render() {
const {filters, selectGroupsFilter, groupDevicesBySelectedFilter, devices} = this.props;
const groupsFilterSelected = (event: React.SyntheticEvent<HTMLDivElement>, data: any) => {
selectGroupsFilter({id:data.value});
const devicesGroups=_.chain(devices).groupBy(data.value).map((v, i) => {
return {
id: i,
name: i,
devices: v
}
}).value();
groupDevicesBySelectedFilter(devicesGroups);
}
return (
<Header as='h4'>
<Icon name='filter' />
<Header.Content>
Group nodes by {' '}
<Dropdown
inline = {true}
options={filters}
onChange={groupsFilterSelected}
/>
</Header.Content>
</Header>
)
}
}
const mapStateToProps = (state: IAppState) => (
{
filters: state.sidebar.filters,
devices: state.sidebar.devices,
});
const mapDispatchToProps = {
selectGroupsFilter: nodeTreeActions.selectNodeTreeGroupFilter,
groupDevicesBySelectedFilter: nodeTreeActions.groupDevicesBySelectedFilter
};
export default connect(mapStateToProps, mapDispatchToProps)(NodeTreeFilter)
My reducer
export const devicesGroupsReducer = (state: IDevicesGroups = [], action: IActionWithPayload) => {
switch (action.type) {
case nodeTreeActions.GROUP_DEVICES_BY_SELECTED_FILTER:
return action.payload
default:
return state;
} };
export interface IActionWithPayload extends Action {
payload: any;
}
And finally my child component, which should rerendering.
import * as React from 'react'
import {List} from 'semantic-ui-react'
import {IAppState,} from '../../reducers'
import {connect} from 'react-redux'
import {INodeTreeDevicesInfo, INodeTreeDeviceInterfaces, IDevicesGroups} from './type-definition'
import * as nodeTreeActions from '../../actions/node-tree'
// import * as nodeTreeService from '../../services/node-tree'
import {requestError} from "../../actions/error";
interface INodeTreeProps{
devices: INodeTreeDevicesInfo ;
interfaces: INodeTreeDeviceInterfaces;
getDeviceInterfaces: typeof nodeTreeActions.getNodeTreeDeviceInterfaces;
requestError: typeof requestError;
deviceGroups: IDevicesGroups;
}
class NodeTree extends React.Component<INodeTreeProps> {
public generateParentTree = (array: any) => {
const tree = array.map((item:any) => (
<List.Item key={item.id}>
<List.Icon name={ "caret right"} />
<List.Content onClick={this.generateChildren} verticalAlign='middle'>
<List.Description>{item.name}</List.Description>
</List.Content>
</List.Item>
))
return tree
}
public generateChildren = () => {
console.log('I will generate children')
}
public render() {
const {devices, deviceGroups} = this.props;
const parentArray = deviceGroups !== undefined && deviceGroups.length !== 0 ? deviceGroups : devices;
const Tree = this.generateParentTree(parentArray)
console.log('')
return (
<div>
<List>
{Tree}
</List>
</div>
);
}
}
const mapStateToProps = (state: IAppState) => (
{
devices: state.sidebar.devices,
interfaces: state.sidebar.interfaces,
deviceGroups: state.sidebar.deviceGroups
});
const mapDispatchToProps = {
requestError,
getDeviceInterfaces: nodeTreeActions.getNodeTreeDeviceInterfaces
};
export default connect(mapStateToProps, mapDispatchToProps)(NodeTree)
Pls, never mind on public and private states in code
You are mutating your state in the reducer. You need to return a new state object and update it with your payload.
return {
...state,
IDevicesGroups: [...state.IDevicesGroups, action.payload]
}
Should be something like that.

Modal state with react/redux

I'm managing Todo lists in my app. The main view is a page with all the lists displayed as cards. If you click on one of them, you can modify, update, delete stuff through a modal that appears.
I have a TodoLists reducer that store all the TodoLists. I don't know how to handle the modal. Should I use redux or just local state?
import _ from "lodash";
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { listsActions } from "../duck";
import NewList from "./NewList";
import Card from "./Card";
import Modal from "./Modal";
class Lists extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
list: {}
};
this.hideModal = this.hideModal.bind(this);
this.renderModal = this.renderModal.bind(this);
}
componentDidMount() {
const { fetchByUserId, user } = this.props;
if (user !== undefined) {
fetchByUserId(user.id);
}
}
hideModal() {
this.setState({
modal: false
});
}
renderModal() {
this.setState({
modal: true
});
}
render() {
const { items } = this.props;
const { modal, list } = this.state;
return (
<div>
<NewProject />
<div className="columns">
{_.map(items, (l) => (
<div
key={l.id}
className="column"
>
<Card
list={l}
onClick={() => this.renderModal(l)}
/>
</div>
))}
</div>
<Modal
className={modal ? "is-active" : ""}
list={list}
onClose={this.hideModal}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
const { user } = state.authentication;
const { items, loading, error } = state.lists;
return {
user,
items,
loading,
error
};
};
export default connect(
mapStateToProps,
{ fetchByUserId: listsActions.fetchByUserId }
)(Projects);

trying to pass my arrays (props) into my publish function as selector

import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import React, {Component} from 'react';
import {check} from 'meteor/check';
export const Adressen = new Mongo.Collection('Phonebook');
if (Meteor.isServer) {
Meteor.publish('ArrayToExport', function(branches) {
check(branches, [Match.Any]);
if(branches.length > 10){
return this.ready()
};
return Adressen.find(
{branche: {$in: branches}}, {fields: {firmenname:1, plz:1}}
);
});
}
.
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
class ExportArray extends Component{
constructor(props){
super(props);
this.state = {
branches: this.props.filteredBranches
};
}
render(){
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
this.props.filteredBranche is a pure array,generated through controlled input field. this.props.filteredBranches changes as Input changes, in parent Component.
I thought I was sending my this.props.filteredBranches as an argument through withTracker function. But nothing is passed to the publish function.
if (Meteor.isServer) {
arrayExfct = function (array){
return {
find: {branche:{$in: array }},
fields: {firmenname:1, plz:1}
};
}
Meteor.publish('ArrayToExport', function (array) {
return Adressen.find(
arrayExfct(array).find, arrayExfct(array).fields);
});
}
.
export default withTracker( () => {
arrayExfct = function(array) {
return {
find: {branche: {$in: array}},
fields: {firmenname:1, plz:1}
}
}
var array = ['10555'];
Meteor.subscribe('ArrayToExport', array );
var arrayExfct = Adressen.find(arrayExfct(array).find, arrayExfct(array).fields);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
It would help if you also added an example of where you used this component and how you pass props to it, but I think I see your problem.
You expect the local state in your rendering component to get into the withTracker container, but that would be the other way around. When you make the withTracker container, you are really making another react component that renders your display component (ExportArray) and passes the data (ArrayToExport) down into it.
So, props go like this currently:
external render -> withTracker component -> ExportArray
What you need to do it to get the filteredBranches (which you pass from a parent component?) from the props argument in withTracker and pass that to the subscribtion,
class ExportArray extends Component{
exportArrays () {
const { ArrayToExport } = this.props;
}
render(){
const { ArrayToExport } = this.props;
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker(propsFromParent => {
const { filteredBranches } = propsFromParent;
Meteor.subscribe('ArrayToExport', filteredBranches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Hi the issue is with the code below. The parameter called branches is the props so branches.branches is the array you passed in.
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Try the following.
export default withTracker( ({branches}) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Notice all that changed was
(branches)
became
({branches})
I solved my problem with a combination of Session Variables and State.
//Client
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
import {Meteor} from 'meteor/meteor';
import { Session } from 'meteor/session';
class ExportArray extends Component{
constructor(){
super();
this.state = {
x: [],
y: []
};
this.exportArrays = this.exportArrays.bind(this);
}
exportArrays(e){
e.preventDefault();
this.setState({x: this.props.filteredBranches});
this.setState({y: this.props.filteredPostleitzahlen});
}
render(){
var selector = {branche: {$in: this.state.x},plz: {$in: this.state.y}};
Session.set('selector', selector);
return(
<div>
<button onClick={this.exportArrays}> Commit </button>
</div>
);
}
}
export default withTracker( () => {
const ArrayfürExport = Meteor.subscribe('ArrayToExport', Session.get('selector') );
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
//Server
Meteor.publish('ArrayToExport', function (selector) {
console.log('von mongodb', selector);
return Adressen.find(
selector
, {
fields: {firmenname:1, plz:1}
});
});
}

Implementing React Redux

I am slowly learning React and also learning to implement it with Redux. But I seem to have hit a road block. So this is what I have so far.
/index.jsx
import './main.css'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App.jsx'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import ShoppingList from './reducers/reducer'
let store = createStore(ShoppingList)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
/actions/items.js
import uuid from 'node-uuid'
export const CREATE_ITEM = 'CREATE_ITEM'
export function createItem(item) {
return {
type: CREATE_ITEM,
item: {
id: uuid.v4(),
item,
checked: false
}
}
}
/reducers/reducer.js
import * as types from '../actions/items'
import uuid from 'node-uuid'
const initialState = []
const items = (state = initialState, action) => {
switch (action.type) {
case types.CREATE_ITEM:
return {
id: uuid.v4(),
...item
}
default:
return state;
}
}
export default items
/reducers/index.js
UPDATE:
import { combineReducers } from 'redux'
import items from './reducer'
const ShoppingList = combineReducers({
items
})
export default ShoppingList
/components/Item.jsx
import React from 'react';
import uuid from 'node-uuid'
export default class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
isEditing: false
}
}
render() {
if(this.state.isEditing) {
return this.renderEdit();
}
return this.renderItem();
}
renderEdit = () => {
return (
<input type="text"
ref={(event) =>
(event ? event.selectionStart = this.props.text.length : null)
}
autoFocus={true}
defaultValue={this.props.text}
onBlur={this.finishEdit}
onKeyPress={this.checkEnter}
/>
)
};
renderDelete = () => {
return <button onClick={this.props.onDelete}>x</button>;
};
renderItem = () => {
const onDelete = this.props.onDelete;
return (
<div onClick={this.edit}>
<span>{this.props.text}</span>
{onDelete ? this.renderDelete() : null }
</div>
);
};
edit = () => {
this.setState({
isEditing: true
});
};
checkEnter = (e) => {
if(e.key === 'Enter') {
this.finishEdit(e);
}
};
finishEdit = (e) => {
const value = e.target.value;
if(this.props.onEdit) {
this.props.onEdit(value);
this.setState({
isEditing: false
});
}
};
}
/components/Items.jsx
import React from 'react';
import Item from './Item.jsx';
export default ({items, onEdit, onDelete}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
text={item.text}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</li>
)}</ul>
);
}
// UPDATE: http://redux.js.org/docs/basics/UsageWithReact.html
// Is this necessary?
const mapStateToProps = (state) => {
return {
state
}
}
Items = connect(
mapStateToPros
)(Items) // `SyntaxError app/components/Items.jsx: "Items" is read-only`
//////////////////////////////////////
// Also tried it this way.
//////////////////////////////////////
Items = connect()(Items)
export default Items // same error as above.
Tried this as well
export default connect(
state => ({
items: store.items
})
)(Items) // `Uncaught TypeError: Cannot read property 'items' of undefined`
UPDATE:
After many attempts #hedgerh in Gitter pointed out that it should be state.items instead. so the solution was
export default connect(
state => ({
items: state.items
})
)(Items)
credits to #azium as well.
/components/App.jsx
export default class App extends React.Component {
render() {
return (
<div>
<button onClick={this.addItem}>+</button>
<Items />
</div>
);
}
}
What am I missing here in order to implement it correctly? Right now it breaks saying that Uncaught TypeError: Cannot read property 'map' of undefined in Items.jsx. I guess it makes sense since it doesn't seem to be hooked up correctly. This is the first part of the app, where the second will allow an user to create a many lists, and these lists having many items. I will probably have to extract the methods from Item.jsx since the List.jsx will do pretty much the same thing. Thanks
You're missing connect. That's how stuff gets from your store to your components. Read the containers section from the docs http://redux.js.org/docs/basics/UsageWithReact.html
import React from 'react'
import Item from './Item.jsx'
import { connect } from 'react-redux'
let Items = ({items, onEdit, onDelete}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
text={item.text}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</li>
})
</ul>
)
}
export default connect(
state => ({
items: state.items
})
)(Items)
Also you seem to be expecting onEdit and onDelete functions passed from a parent but you're not doing that so those functions will be undefined.

Resources