I moved away from Alt to Redux and decided to take advantage of context type.
Somewhere in the mix, my ref is now undefined.
What would be the proper procedure for refs to be available with this code:
import React, { Component, PropTypes } from 'react';
// components
import TopNavMenuItem from './top-nav-menu-item';
export default class TopNavZone extends Component {
fetchMenu() {
const results = [];
const navItems = this.context.navItems;
navItems.map((item, index) => {
results.push(
<TopNavMenuItem key={ index }
clientId={ this.context.clientInfo._id }
item={ item }
index={ index }
parent={ this.refs.topNavList }
/>
);
});
return results;
}
render() {
return (
<section>
<nav className="top-nav" id="TopNavZone">
<ul ref="topNavList" className="">
{ this.fetchMenu() }
</ul>
</nav>
</section>
);
}
}
TopNavZone.contextTypes = {
navItems: PropTypes.array.isRequired,
clientInfo: PropTypes.object.isRequired
};
Thank you all.
I captured the ref in ComponentDidMount and made the data part of state instead of calling the function this.fetchMenu from render:
import React, { Component, PropTypes } from 'react';
// components
import TopNavMenuItem from './top-nav-menu-item';
export default class TopNavZone extends Component {
constructor(props) {
super(props); {
this.state = {
results: null
}
}
}
componentDidMount() {
var topNavList = this.refs.topNavList;
this.setState({ results: this.fetchMenu(topNavList) })
}
fetchMenu(topNavList) {
const results = [];
const items = this.context.navItems;
items.map((item, index) => {
results.push(
<TopNavMenuItem key={ index }
clientId={ this.context.clientInfo._id }
item={ item }
index={ index }
parent={ topNavList }
/>
);
});
return results;
}
render() {
return (
<section>
<nav className="top-nav" id="TopNavZone">
<ul ref="topNavList" className="">
{ this.state.results }
</ul>
</nav>
</section>
);
}
}
TopNavZone.contextTypes = {
navItems: PropTypes.array.isRequired,
clientInfo: PropTypes.object.isRequired
};
Related
I am using react-redux.
I have the following JSX (only relevant snippets included):
getQuestionElement(question) {
if (question) {
return <MultiChoice questionContent={this.props.question.question} buttonClicked={this.choiceClicked} />
}
else {
return (
<div className="center-loader">
<Preloader size='big' />
</div>
)
}
}
render() {
return (
<div>
<Header />
{
this.getQuestionElement(this.props.question)
}
</div>
)
}
function mapStateToProps({ question }) {
return { question };
}
export default connect(mapStateToProps, questionAction)(App);
When the action fires, and the reducer updates the question prop
this.props.question
I expect
{this.getQuestionElement(this.props.question)}
to be reloaded and the new question rendered.
However this is not happening. Am I not able to put a function in this way to get it live reloaded?
My MultiChoice component:
import React, { Component } from 'react';
import ReactHtmlParser from 'react-html-parser';
import './questions.css';
class MultiChoice extends Component {
constructor(props) {
super(props);
this.state = {
question: this.props.questionContent.question,
answerArray : this.props.questionContent.answers,
information: null
}
this.buttonClick = this.buttonClick.bind(this);
}
createButtons(answerArray) {
var buttons = answerArray.map((element) =>
<span key={element._id} onClick={() => { this.buttonClick(element._id) }}
className={"span-button-wrapper-25 " + (element.active ? "active" : "")}>
<label>
<span>{element.answer}</span>
</label>
</span>
);
return buttons;
}
buttonClick(id) {
var informationElement;
this.props.buttonClicked(id);
var buttonArray = this.state.answerArray.map((element) => {
if (element._id === id ){
element.active = true;
informationElement = element.information;
return element;
}
else{
element.active = false;
return element;
}
});
this.setState({
answerArray: buttonArray,
information: informationElement
})
}
render() {
return (
<div className="question-container">
<div className="question-view">
<div className="icon-row">
<i className="fa fa-code" />
</div>
<div className="title-row">
{this.state.question}
</div>
<div className="button-row">
{this.createButtons(this.state.answerArray)}
</div>
<div className="information-row">
{ReactHtmlParser(this.state.information)}
</div>
</div>
</div>
);
}
}
export default MultiChoice;
QuestionAction.js
import axios from "axios";
import { FETCH_QUESTION } from "./types";
export const fetchQuestion = (questionId, answerId) => async dispatch => {
let question = null;
if (questionId){
question = await axios.get("/api/question/next?questionId=" + questionId + "&answerId=" + answerId);
}
else{
question = await axios.get("/api/question/next");
}
console.log("question", question);
dispatch({ type: FETCH_QUESTION, payload: question });
};
questionReducer.js
import {FETCH_QUESTION } from "../actions/types";
export default function(state = null, action) {
switch (action.type) {
case FETCH_QUESTION:
console.log("payload", action.payload.data);
return { question: action.payload.data, selected: false };
default:
return state;
}
}
index.js (Combined Reducer)
import { combineReducers } from 'redux';
import questionReducer from './questionReducer';
export default combineReducers({
question: questionReducer
});
and my entry point:
index.js
const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
requested console.log response:
render() {
console.log("Stackoverflow:", this.props.question)
.....
and after clicking the button (and the reducer updating, the console.log is updated, but the
this.getQuestionElement(this.props.question)
does not get re-rendered
MultiChoice Component shouldn't store his props in his state in the constructor, you have 2 options here :
Handle props changes in componentWillReceiveProps to update the state :
class MultiChoice extends Component {
constructor(props) {
super(props);
this.state = {
question: this.props.questionContent.question,
answerArray : this.props.questionContent.answers,
information: null
}
this.buttonClick = this.buttonClick.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({
question: nextProps.questionContent.question,
answerArray : nextProps.questionContent.answers,
information: null
});
}
We have to keep using the constructor to set an initial state as from docs :
React doesn’t call componentWillReceiveProps() with initial props
during mounting.
2nd Option : Make it as a "dumb component" by having no state and only using his props to render something (some more deep changes in your component to do, especially to handle the "active" element, it will have to be handled by the parent component).
I'm trying to understand how to pass props down using the map function. I pass down the fruit type in my renderFruits function and in my Fruits sub-component I render the fruit type. I do not understand what is wrong with this code.
import React, { Component } from 'react';
import { render } from 'react-dom';
import Fruits from'./Fruits';
class App extends Component {
constructor(props) {
super(props);
this.state = {
fruits: [
{
type:'apple',
},
{
type:'tomato',
}
]
};
}
renderFruits = () => {
const { fruits } = this.state;
return fruits.map(item =>
<Fruits
type={item.type}
/>
);
}
render() {
return (
<div>
{this.renderFruits}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Fruits component where it should render two divs with the text apple and tomato.
class Fruits extends Component {
render() {
const { type } = this.props;
return(
<div>
{type}
</div>
);
}
}
export default Fruits;
You have two problems in you code
- you should call renderFruits in your render function: this.renderFruits()
- should use "key", when you try to render array
renderFruits = () => {
const { fruits } = this.state;
return fruits.map( (item, index) =>
<Fruits
key={index}
type={item.type}
/>
);
}
render() {
return (
<div>
{this.renderFruits()}
</div>
);
}
I have a simple Cart component and I want to show either a "Your cart is empty" message when there are no items in it.
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as CartActions from '../actions/cart'
import Shelf from './Shelf'
import EmptyCart from './EmptyCart'
/*
This is a container component
*/
class Cart extends Component {
constructor(props) {
super(props)
this.state = {
itemQuantity: props.cart.length
}
}
render() {
const CartItems = this.props.cart.map(
(item, idx) =><li key={idx}>{item.name} - ${item.price}</li>
)
const isCartEmpty = () => this.state.itemQuantity === 0
console.log("is cart empty? ", isCartEmpty(), "cart item quantity ", this.state.itemQuantity)
return(
<div className="Cart">
<Shelf addItem={this.props.action.addToCart} />
<h2>Cart Items</h2>
<ol>
{ isCartEmpty() ? <EmptyCart/> : {CartItems} }
</ol>
</div>
)
}
}
function mapStateToProps(state, prop) {
return {
cart: state.cart
}
}
function mapDispatchToProps(dispatch) {
return {
action: bindActionCreators(CartActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart)
My Shelf component looks like this:
import React, { Component } from 'react';
class Shelf extends Component {
constructor(props) {
super(props)
this.addItemToCart = this.addItemToCart.bind(this)
this.state = {
shelfItems: [
{ "name": 'shampoo', "price": 23 },
{ "name": 'chocolate', "price": 15 },
{ "name": 'yogurt', "price": 10 }
]
}
}
addItemToCart(item){
this.props.addItem(item)
}
render() {
const shelfItems = this.state.shelfItems.map((item, idx) => {
return <li key={idx}><button onClick={()=>this.addItemToCart(item)}>[+]</button>{item.name} - ${item.price}</li>
})
return(
<div>
<h2>Shelf</h2>
<ul>
{shelfItems}
</ul>
</div>
)
}
}
export default Shelf
Cart Reducer:
export default(state = [], payload) => {
switch (payload.type) {
case 'add':
return [...state, payload.item]
default:
return state
}
}
addToCart action:
export const addToCart = (item) => {
return {
type: 'add',
item
}
}
The empty message shows up but the list does not update when I add items. What am I doing wrong? The code works just fine if I remove the conditionals and just render CartItems
It's because you set only initial state. When you add item you don't set a new state. If you use redux there is no local state needed.
Try this:
class Cart extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
const CartItems = this.props.cart.map(
(item, idx) =><li key={idx}>{item.name} - ${item.price}</li>
)
const isCartEmpty = CartItems.length === 0
return(
<div className="Cart">
<Shelf addItem={this.props.action.addToCart} />
<h2>Cart Items</h2>
<ol>
{isCartEmpty ? <li>Your Cart is Empty</li> : CartItems}
</ol>
</div>
)
}
}
In my project, there are HomeIndexView and table component. So, when a user logs in to his account, in HomeIndexView, it shows all tables in the database. What I want to do is that make each table have a state so that it changes color of depends on its state(depends on child's state)... How can I do this?
My table component has a state like below.
const initialState = {
allTables: [],
showForm: false,
fetching: true,
formErrors: null,
};
EDIT ---1
HomeIndexView
class HomeIndexView extends React.Component {
componentDidMount() {
setDocumentTitle('Table_show');
}
componentWillunmount() {
this.props.dispatch(Actions.reset());
}
_renderAllTables() {
const { fetching } = this.props;
let content = false;
if(!fetching) {
content = (
<div className="tables-wrapper">
{::this._renderTables(this.props.tables.allTables)}
</div>
);
}
return (
<section>
<header className="view-header">
<h3>All Tables</h3>
</header>
{content}
</section>
);
}
_renderTables(tables) {
return tables.map((table) => {
return <Table
key={table.id}
dispatch={this.props.dispatch}
{...table} />;
});
}
render() {
return (
<div className="view-container tables index">
{::this._renderAllTables()}
</div>
);
}
}
EDIT--2
_handleClick () {
const { dispatch } = this.props;
const data = {
table_id: this.props.id,
};
if (this.props.current_order == null) {
dispatch(Actions.create(data));
Object.assign({}, this.state, {
tableBusy: true
});
}
else{
this.props.dispatch(push(`/orders/${this.props.current_order}`));
}
}
The state you shared above is part of the global state (where tableReducer use) not the table's component state, so what you need is to initialize component state in Table React component, so that you can check some values to render css differently something like this:
import React from "react";
class TableComponent extends React.Component {
componentWillMount() {
this.setInitialState();
}
setInitialState() {
this.setState({ isWhatever: false });
}
render() {
return (
<div>
<h1 classname={this.state.isWhatever ? 'css-class' : 'another-class'}>
{this.props.id}
</h1>
</div>
);
}
}
My handleTeamChange function is erroring and coming back as undefined when the renderTeamMethod runs. I tried passing the variable team into on like "this.handleTeamChange.bind(this, team)" as well but nothing. I've tried a ton of different ways to call teh handleTeamChange method but so far nothing but undefined. Any thoughts?
import React, { Component } from 'react';
import UserDropdown from './user-dropdown';
import { getTeams } from 'api/index.js';
let teams = [];
let selectedTeamID = null;
let selectedTeamName = 'all_teams';
let teamId = '';
export default class TopNav extends Component {
constructor(props, context) {
super(props, context);
// this.handleTeamChange = this.handleTeamChange.bind(this);
this.state = {
teams: [],
team: {},
selectedTeamID: null,
selectedTeamName: 'All Teams',
teamSelection: false
};
}
handleClick() {
this.setState({
teamSelection: true
});
}
componentWillMount() {
getTeams().then((response) => {
teams = response.data;
this.setState({teams: teams});
});
}
renderTeams() {
return teams.map(function(team) {
if (team.active === true) {
return (
<div
onClick={ () => { this.handleTeamChange(team) } }
className="team-filter-team"
key={team.id}
value={team.id} >{team.TeamName}
</div>
);
}
});
}
handleTeamChange(team) {
console.log(team);
}
render () {
return (
<nav className="nav-wrapper">
<img className="logo-medium nav-logo" src={"https://s3-us-west-2.amazonaws.com/mvtrak/MVTRAKbrandmark.png"} />
<div onClick={ this.handleClick.bind(this) } className="team-selected"> { this.state.selectedTeamName } </div>
<div className="team-filter-container">
{this.renderTeams()}
</div>
<UserDropdown />
</nav>
);
}
}
the function body where you're mapping teams is not bound to the component's scope, therefore this is undefined.
change teams.map(function (team) { ... }) to e.g. a fat arrow teams.map((team) => ... ):
return teams.filter(team => team.active).map((team) => (
<div
onClick={ () => { this.handleTeamChange(team) } }
className="team-filter-team"
key={team.id}
value={team.id}
>
{team.TeamName}
</div>
))