React/Reflux data flow: One component twice in DOM - javascript

Suppose I have the following Tree:
<LandingPage>
<PageHeader>
<Menu>
<ShoppingCart>
</Menu>
</PageHeader>
<MainPage>
<ShoppingCart>
</MainPage>
</LandingPage>
The component we care about is the ShoppingCart.
Upon mounting it (componentDidMount) ShoppingCart triggers an action, so that the ShoppingCartStore makes a request to a server and returns a list of articles - triggering a rerender of ShoppingCart .
The way it is set up now, there will always be two requests and two rerenders, because both components are in the dom.
One solution would be to have a common root trigger these requests, but that would be the LandingPage - and one would have to pass the data through PageHeader and Menu and MainPage.
Is there a better solution? Is that good enough?

I use an api.store for all data requests. I call the api.store in the entry app.js. Then I use an action that the api.store listens to for the initial data requests.
app.js
'use strict';
import React from 'react';
import ReactDom from 'react-dom';
import AppCtrl from './components/app.ctrl.js';
import Actions from './actions/api.Actions';
import ApiStore from './stores/Api.Store';
window.ReactDom = ReactDom;
Actions.apiInit();
ReactDom.render( <AppCtrl />, document.getElementById('react') );
api.store
import Reflux from 'reflux';
import Actions from '../actions/api.Actions';
import ApiFct from '../utils/sa.api';
let ApiStoreObject = {
newData: {
"React version": "0.14",
"Project": "ReFluxSuperAgent",
"currentDateTime": new Date().toLocaleString()
},
listenables: Actions,
apiInit() { ApiFct.setData(this.newData); },
apiInitDone() { ApiFct.getData(); },
apiSetData(data) { ApiFct.setData(data); }
}
const ApiStore = Reflux.createStore(ApiStoreObject);
export default ApiStore;
In the component that connects to a store the initial state calls the store data so if the data is already there you get it.
import React from 'react';
import BasicStore from '../stores/Basic.Store';
let AppCtrlSty = {
height: '100%',
padding: '0 10px 0 0'
}
const getState = () => {
return {
Data1: BasicStore.getData1(),
Data2: BasicStore.getData2(),
Data3: BasicStore.getData3()
};
};
class AppCtrlRender extends React.Component {
render() {
let data1 = JSON.stringify(this.state.Data1, null, 2);
let data2 = JSON.stringify(this.state.Data2, null, 2);
let data3 = JSON.stringify(this.state.Data3, null, 2);
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
React 0.14 ReFlux with SuperAgent<br/><br/>
Data1: {data1}<br/><br/>
Data2: {data2}<br/><br/>
Data3: {data3}<br/><br/>
</div>
);
}
}
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
}
componentDidMount = () => { this.unsubscribe = BasicStore.listen(this.storeDidChange); };
componentWillUnmount = () => { this.unsubscribe(); };
storeDidChange = (id) => {
switch (id) {
case 'data1': this.setState({Data1: BasicStore.getData1()}); break;
case 'data2': this.setState({Data2: BasicStore.getData2()}); break;
case 'data3': this.setState({Data3: BasicStore.getData3()}); break;
default: this.setState(getState());
}
};
}
From https://github.com/calitek/ReactPatterns React.14/ReFluxSuperAgent.

Related

Adding React Component child to another React Component at runtime

I am super stuck with ReactJs in trying to add one Virtual Component to another Component at runtime and failing to do so. Here is what I am trying to do:
My App.js looks like this:
import React from 'react';
import './App.css';
// Components
import Header from './components/Header';
import Footer from './components/Footer';
import LeftSideSpace from './components/LeftSideSpace';
import RightSideSpace from './components/RightSideSpace';
import CenterSpace from './components/CenterSpace';
// main class name: App
class App extends React.Component {
// main function name: render
render() {
return (
<div className="App">
<Header title='My Blog'/>
<LeftSideSpace/>
<CenterSpace/>
<RightSideSpace/>
<Footer title='Welcome! This is my Blog site'/>
</div>
);
}
}
export default App;
My focus is on the component <CenterSpace/> which I am importing from here:
import React from 'react';
import PropTypes from 'prop-types'
class CenterSpace extends React.Component {
render() {
return (
<centerspace className="Site.CenterSpace">
<div id="Site.CenterSpace.Content">
{this.props.children}
</div>
</centerspace>
);
}
}
// props defaults
CenterSpace.defaultProps = {
title: 'Personal Blogger\'s site'
}
// props validations
CenterSpace.propTypes = {
title: PropTypes.string.isRequired
}
export default CenterSpace
Then I have a menu component like this, as of now, this is what I have in code, which I am sure contains bugs:
import React from 'react';
import PropTypes from 'prop-types'
import CenterSpace from '../CenterSpace'
import HomeLists from './HomeLists'
class MainMenu extends React.Component {
render() {
return (
<div className="Site.MainMenu">
<button onClick={this.props.onClickHome}>Home</button>
<button onClick={this.props.onClickBlogs}>Blogs</button>
<button onClick={this.props.onClickAboutMe}>About Me</button>
</div>
);
}
}
// props defaults
MainMenu.defaultProps = {
//control button clicks
onClickHome: () => {
var home_dom = new HomeLists();
var center_dom = new CenterSpace<String>("My Blog list");
console.log("say we went to home")
center_dom.appendChild(home_dom);
},
onClickBlogs:() => {
console.log("say we went to blogs")
},
onClickAboutMe:() => {
console.log("say we went to about me")
}
}
// props validations
MainMenu.propTypes = {
onClickHome: PropTypes.func.isRequired,
onClickBlogs: PropTypes.func.isRequired,
onClickAboutMe: PropTypes.func.isRequired,
}
export default MainMenu
This main-menu is used to dynamically add and remove components, but I am failing to do so. When I click Home button, the action I am trying achieveis to add <HomeList/> component to <CenterSpace/>. And futher, <HomeList/> is parsing some Json files and appending as child divs.
<HomeList/> looks like this (may have some issues, was not able to make it work, but that is something I can fix):
import React from 'react';
import PropTypes from 'prop-types'
class HomeLists extends React.Component {
render() {
const fs_obj = require('fs');
const fs_path = require('path');
const fs_jsons = fs_obj.readdirSync('../data').filter(file => fs_path.extname(file) === '.json');
fs_jsons.forEach(file => {
const file_data = fs_obj.readFileSync(fs_path.join('../data', file));
const json = JSON.parse(file_data.toString());
const blog_title = json.title;
var snippet_header = document.createElement('h3');
snippet_header.textContent(blog_title);
const blog_desp = json.blog.content[0].value;
var snippet_text = document.createElement('p');
snippet_text.textContent(blog_desp);
var snippet = document.createElement('div');
snippet.appendChild(snippet_header);
snippet.appendChild(snippet_text);
this.appendChild(snippet);
});
return (
<homelists className="Site.HomeLists">
<div id="Site.HomeLists.Content">{HomeLists}</div>
</homelists>
);
}
}
// props defaults
HomeLists.defaultProps = {
title: 'Personal Blogger\'s site'
}
// props validations
HomeLists.propTypes = {
title: PropTypes.string.isRequired
}
export default HomeLists
Right now when I click Home, all I get is the following error:
TypeError: center_dom.appendChild is not a function
onClickHome
src/components/complications/MainMenu.js:29
28 | console.log("say we went to home")
> 29 | center_dom.appendChild(home_dom);
| ^
30 | },
31 | onClickBlogs:() => {
32 |
console.log("say we went to blogs")
Can anyone help me get unblock from here.
Use the following component as an example for conditional rendering and it is based on your question as well.
import React from "react";
class MainMenu extends React.Component {
constructor(props) {
super(props);
this.state = { isHome: false, isBlogs: false, isAboutMe: false };
// Binding this keyword
this.onClickHome = this.onClickHome.bind(this);
this.onClickBlogs = this.onClickBlogs.bind(this);
this.onClickAboutMe = this.onClickAboutMe.bind(this);
}
onClickHome() {
this.setState({ isHome: true, isBlogs: false, isAboutMe: false });
}
onClickBlogs() {
this.setState({ isHome: false, isBlogs: true, isAboutMe: false });
}
onClickAboutMe() {
this.setState({ isHome: false, isBlogs: false, isAboutMe: true });
}
render() {
return (
<div className="Site.MainMenu">
<button onClick={this.onClickHome}>Home</button>
<button onClick={this.onClickBlogs}>Blogs</button>
<button onClick={this.onClickAboutMe}>About Me</button>
{this.state.isHome && <div>Home view is enabled</div>}
{this.state.isBlogs && <div>Blogs view is enabled</div>}
{this.state.isAboutMe && <div>AboutMe view is enabled</div>}
</div>
);
}
}
export default MainMenu;
Application View
Refer this link for more info on conditional rendering: https://reactjs.org/docs/conditional-rendering.html

getting issue while working with nested routes in react

I am getting issues while rendering contact data.
Here the case is when I click the continue button in my app it triggers the checkoutContinuedHandler() function that results in a change of URL but the ContactData component is not rendered and my CheckoutSummary component also vanishes as I am rendering it on the same page.
I Checked twice that export is done and there is no spelling mistakes.
I tried different solutions from the stack and discussed them with my mate still the issue is on...
import React, { Component } from "react";
import { Route } from "react-router-dom";
import CheckoutSummary from "../../components/Order/CheckoutSummary/CheckoutSummary";
import ContactData from "./ContactData/ContactData";
class Checkout extends Component {
state = {
ingredients: {
salad: 1,
meat: 1,
cheese: 1,
bacon: 1,
},
};
componentDidMount() {
const query = new URLSearchParams(this.props.location.search);
const ingredients = {};
for (let param of query.entries()) {
// ['salad','1']
ingredients[param[0]] = +param[1];
}
this.setState({ ingredients: ingredients });
}
checkoutCancelledHandler = () => {
this.props.history.goBack();
};
checkoutContinuedHandler = () => {
this.props.history.replace("/checkout/contact-data");
console.log(this);
};
render() {
return (
<div>
<CheckoutSummary
ingredients={this.state.ingredients}
checkoutCancelled={this.checkoutCancelledHandler}
checkoutContinued={this.checkoutContinuedHandler}
/>
<Route
path={this.props.match.path + "/contact-data"}
component={ContactData}
/>
</div>
);
}
}
export default Checkout;

react and redux, update state from store changes in a component

I'm trying to update my home componentstate by getting data from the redux store every time the store is updated. I'm not sure what's wrong with the code below. I can't listen to store changes in my `home component.
my dispatch function is handled in this class.
export class GanttFilter extends Component {
...
handleSubmit = () => {
this.gFilter.filterGanttData(this.formValues)
.then((result) => {
if (result.data)
this.props.dispatch(ganttActions.loadGanttData(result.data));
});
}
...
GanttFilter.propTypes = {
dispatch: PropTypes.func.IsRequired
};
function mapStateToProps(state) {
return {
ganttData: state.gantt.ganttData
};
}
export default connect(mapStateToProps)(GanttFilter);
What I would like to do every time dispatch is called and the data changes, is update the state in my home component. Here is the component.
export class Home extends Component {
constructor() {
super();
this.state = {
data: [],
links: []
};
}
render() {
return (
<div className="fill">
<Gantt data={this.state.data} links={this.state.links} />
</div>
);
}
}
Home.propTypes = {
data: PropTypes.IsRequired
};
function mapStateToProps(state) {
return {
data: state.gantt.ganttData
};
}
export default connect(mapStateToProps)(Home);
the function mapStateToProps is never hit when I set a break point. How can I listen to changes to the store from the home component and update state?
Edit: Here is the wrapper component
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
const baseUrl = document.getElementsByTagName("base")[0].getAttribute("href");
ReactDOM.render(
<ReduxProvider store={store}>
<AppContainer>
<BrowserRouter children={routes} basename={baseUrl} />
</AppContainer>
</ReduxProvider>,
document.getElementById("react-app")
);
}
reducers
const actionTypes = require('../actions/actionTypes');
const gantt = {
ganttData: [],
selectedTask: null
};
export default function ganttReducer(state = gantt, action) {
switch (action.type) {
case actionTypes.loadGanttData:
return { ...state, ganttData: [...action.ganttData] };
default:
return state;
}
}
root reducer
import { combineReducers } from 'redux';
import gantt from './ganttReducer';
const rootReducer = combineReducers({
gantt
});
export default rootReducer;
actions
const actionTypes = require('./actionTypes');
export function loadGanttData(ganttData) {
return { type: actionTypes.loadGanttData, ganttData };
}
export function getSelectedTask(ganttTask) {
return { type: actionTypes.setSelectedTask, ganttTask };
}
Error:
Make sure you import your Home component using import Home from '...' as opposed to import { Home } from '...', otherwise you'd be grabbing the unconnected component. In general, I would also avoid exporting the unconnected component.
Change this:
render() {
return (
<div className="fill">
<Gantt data={this.state.data} links={this.state.links} />
</div>
);
}
To
render() {
return (
<div className="fill">
<Gantt data={this.props.data} links={this.state.links} />
</div>
);
}
Your data is comming from your props (redux), not from your state.

Redux state updated but props still received as undefined

I have the following component which calls a Factory to create both heroes and cells, and once the array is full, pass it to the state variables with the same name.
Field.js
import React,{Component} from 'react';
import { connect } from 'react-redux';
import _ from "lodash";
import Factory from './../Factory/Factory';
import { addHero, addCell } from './../../store/actions/actionsCreator';
class Field extends Component {
componentWillMount(){
let heros = [],
cells = [];
//id=0 will throw a error, always start with 1
for (let i = 1; i < 3; i++) {
heros.push(this.callFactory('hero', i));
}
this.props.addHero(heros);
for (let i = 1; i < 4; i++) {
for (let j = 1; j < 12; j++) {
let cell = this.callFactory('cell', j, i);
cells.push(cell);
}
}
this.props.addCell(cells);
this.movePerson(1, {x: 2, y: 1});
}
callFactory(type, id, number){
return Factory.build(type, id, number)
}
render() {
const {heros,cells} = this.props;
if(heros === undefined) return null;
// console.log(this.props);
return (
<div>
<table>
<tbody>
<tr>
{cells[0]}
</tr>
</tbody>
</table>
{heros[0]}
</div>
);
}
}
const mapStateToProps = state => {
return {
heros: state.heros,
cells: state.cells
}
}
const mapDispatchToProps = dispatch => {
return {
addHero(hero) {
dispatch(addHero(hero));
},
addCell(cell) {
dispatch(addCell(cell));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Field);
There is my reducer file:
index.js (reducer file)
import {createStore } from 'redux'
const initialState = {
heros: [],
cells: []
};
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_HERO':
return {
...state,
heros: [...state.heros, action.hero]
}
case 'ADD_CELL':
return {
...state,
cells: [...state.cells, action.cell]
}
default:
return {
...state
}
}
}
export default createStore(reducer, initialState);
The file with all my actions:
actionCreator.js
const addHero = hero => {
return {
type: 'ADD_HERO',
hero
}
}
const addCell = cell => {
return {
type: 'ADD_CELL',
cell
}
}
export { addHero, addCell };
And my app entry point:
index.js
import registerServiceWorker from './registerServiceWorker';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './styles/index.css';
import Field from './components/Field/Field';
import store from './store/index';
ReactDOM.render(
<Provider store= {store}>
<Field/>
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
The key problem here is when I try to log my props value at any point it will be logged as undefined, but if I log the props inside render it will be called 2 times, the first one as undefined and the second one with the updated state.
Is there any way to handle this problem so I can use the props values outside render?
Please look at these two react component cycle.
First one is componentDidMount(). You should this method instead of componentWillMount().
Second one is componentWillReceiveProps(nextProps). With the help of this method, you can see the changes nextProps and currently prop you have.
As #brub and #yasinv pointed, the main problem was calling all the logic inside ComponentWillMount and not ComponentDidMount.
If someone is in the same spot, I decided to pass this information (heros and cells) as props to my Field component, and then trigger ComponentDidMount to treat this data.

General State Questions with ReactJS & Reflux

I'm pretty new to React and am trying to wrap my mind around Reflux.
Here's my scenario:
I currently have a very simple app that is calling some song data from a rest api, once retrieved, it is being stored in state.songs (this is happening within an Init function in the song store):
var SongStore = Reflux.createStore({
listenables: [SongActions],
getInitialState: function() {
return []
},
init: function() {
request('http://127.0.0.1:1337/api/songs', function(error, response, body) {
if (!error && response.statusCode == 200) {
var content = JSON.parse(body);
this.trigger(content);
}
}.bind(this));
},
onHandleClick: function(album_link) {
// this.state.songs is undefined here??
}
});
Here's the component that's rendering the song data:
var Home = React.createClass({
mixins: [Reflux.connect(SongStore, 'songs')],
render: function() {
return (
<div className={'react-app-home'}>
<div className="float-left">
<div className="song-data">
{this.state.songs.map(function(song) {
return <div><p>{song.lyrics}</p></div>
})}
</div>
</div>
<div className="float-right">
<AlbumList />
</div>
</div>
);
}
});
This is all working as intended.
I also have a list of albums, which is rendered in it's own component (and has it's own store), and i'm trying to hook up a click function on an album item, so once an album title is clicked, this.state.songs is filtered and the songs component is re-rendered.
The issue i'm having is when I try to access this.state.songs from the song store, it's undefined (see onHandleClick func in song store above).
Album list component:
var AlbumList = React.createClass({
mixins: [Reflux.connect(AlbumStore, 'albums'),
Reflux.connect(SongStore, 'songs')],
render: function() {
return (
<div className="album-list">
{this.state.albums.map(function(album) {
return <p onClick={SongStore.onHandleClick.bind(null, album.album_link)}>{album.album_name}</p>
})}
</div>
);
}
});
Album list store:
var AlbumStore = Reflux.createStore({
listenables: [AlbumActions],
getInitialState: function() {
return [];
},
onReload: function() {
request('http://0.0.0.0:1337/api/albums', function(error, response, body) {
if (!error && response.statusCode == 200) {
var content = JSON.parse(body);
this.trigger(content);
}
}.bind(this));
},
init: function() {
this.onReload();
}
});
Can anyone help me out? An answer with some explaining would be awesome so I can understand what the problem is.
Thanks!
You should be using actions for handleClick or any other call from the component to the store. I would also suggest separating your api calls from the store. Here is an example from https://github.com/calitek/ReactPatterns React.14/ReFluxSuperAgent.
app.js
'use strict';
import React from 'react';
import ReactDom from 'react-dom';
import AppCtrl from './components/app.ctrl.js';
import Actions from './actions/api.Actions';
import ApiStore from './stores/Api.Store';
window.ReactDom = ReactDom;
Actions.apiInit();
ReactDom.render( <AppCtrl />, document.getElementById('react') );
Note the action and api.store import.
Here is api.store.
import Reflux from 'reflux';
import Actions from '../actions/api.Actions';
import ApiFct from '../utils/sa.api';
let ApiStoreObject = {
newData: {
"React version": "0.14",
"Project": "ReFluxSuperAgent",
"currentDateTime": new Date().toLocaleString()
},
listenables: Actions,
apiInit() { ApiFct.setData(this.newData); },
apiInitDone() { ApiFct.getData(); },
apiSetData(data) { ApiFct.setData(data); }
}
const ApiStore = Reflux.createStore(ApiStoreObject);
export default ApiStore;
apiInit is doing a setData here but it could be the initial get calls.
Here it the api.
import request from 'superagent';
import apiActions from '../actions/api.Actions';
import saActions from '../actions/sa.Actions';
module.exports = {
getData() { request.get('/routes/getData').end((err, res) => { this.gotData(res.body); }); },
gotData(data) { saActions.gotData1(data); saActions.gotData2(data); saActions.gotData3(data); },
setData(data) { request.post('/routes/setData').send(data).end((err, res) => { apiActions.apiInitDone(); }) },
};
Here we use actions to pass the data to the store.
Here is the store.
import Reflux from 'reflux';
import Actions from '../actions/sa.Actions';
import AddonStore from './Addon.Store';
import MixinStoreObject from './Mixin.Store';
function _GotData(data) { this.data1 = data; BasicStore.trigger('data1'); }
let BasicStoreObject = {
init() { this.listenTo(AddonStore, this.onAddonTrigger); },
data1: {},
listenables: Actions,
mixins: [MixinStoreObject],
onGotData1: _GotData,
onAddonTrigger() { BasicStore.trigger('data2'); },
getData1() { return this.data1; },
getData2() { return AddonStore.data2; },
getData3() { return this.data3; }
}
const BasicStore = Reflux.createStore(BasicStoreObject);
export default BasicStore;
Note that reflux allows you to pass parameters in trigger.
Here is the component.
import React from 'react';
import BasicStore from '../stores/Basic.Store';
let AppCtrlSty = {
height: '100%',
padding: '0 10px 0 0'
}
const getState = () => {
return {
Data1: BasicStore.getData1(),
Data2: BasicStore.getData2(),
Data3: BasicStore.getData3()
};
};
class AppCtrlRender extends React.Component {
render() {
let data1 = JSON.stringify(this.state.Data1, null, 2);
let data2 = JSON.stringify(this.state.Data2, null, 2);
let data3 = JSON.stringify(this.state.Data3, null, 2);
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
React 0.14 ReFlux with SuperAgent<br/><br/>
Data1: {data1}<br/><br/>
Data2: {data2}<br/><br/>
Data3: {data3}<br/><br/>
</div>
);
}
}
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
}
componentDidMount = () => { this.unsubscribe = BasicStore.listen(this.storeDidChange); }
componentWillUnmount = () => { this.unsubscribe(); }
storeDidChange = (id) => {
switch (id) {
case 'data1': this.setState({Data1: BasicStore.getData1()}); break;
case 'data2': this.setState({Data2: BasicStore.getData2()}); break;
case 'data3': this.setState({Data3: BasicStore.getData3()}); break;
default: this.setState(getState());
}
}
}
We are not using mixins. React classes are deprecating mixins so now is a good time to do without. It may seen like overkill to use an extra store and api util but good practices are always good practice.
Map allows passing this with an optional parameter map(function(item){}, this);

Categories