I've been playing around with this for a while and am not sure if it's not possible or if I'm just missing something fundamental in ES6,React or MobX.
I want to have a mobx store in a separate file like below;
import { observable, action } from 'mobx';
export default class Store {
#observable data = [];
#action getAll(){
this.data = [{
itemTitle: 'Item One'
},{
itemTitle: 'Item Two'
}]
return this.data
}
#action pushItem(item){
this.data.push(item)
}
addAfter5Secs(){
setTimeout(() => {
console.log('Item Added')
this.pushItem({
itemTitle: 'Item Two'
})}, 5000)
}
constructor() {
console.log('Store Created')
this.getAll();
this.addAfter5Secs()
}
}
Then I want to import it in a view AND create an instance of that store at that time.
import React from "react";
import { List, Button } from 'semantic-ui-react';
import { observer, inject } from 'mobx-react';
import Store from '../Data/Store';
import DevTools from 'mobx-react-devtools';
const dataItems = new Store
#observer
export default class ScriptsHome extends React.Component {
render() {
const items = observer(dataItems)
return (
<List>
{items.data.map((reg, key) => {
return(
<List.Item key={key}>
<Button content={reg.itemTitle}/>
</List.Item>)
})}
<DevTools />
</List>
);
}
}
I do not want to pass it through a provider from the root component or have the store instantiated with the 'new' keyword in the export. I actually have dozens of stores so I only want them to be created as needed by views.
The above code will render the first 2 items, the 3rd item will be added to the store only after 5 seconds but the render is not fired on the component automatically so the item doesn't appear. When I force the render function to fire it will appear.
I am using a router to call this view so maybe that's also problematic?
I'm just learning Mobx, react, etc so any help is appreciated. All examples seem to use just one or two stores.
So after spending a bit of time with this it's turns out that it's not an issue at all with MobX.
The versions of react and react router seemed to be the problem.
Using react router dom now and it's fine.
Related
I run into a problem that is litterally blowing my mind.
I'm developing my web application using React and Redux, my application use a system of notification implemented with Firebase.
Every notification is structured as below:
var notification = {
from_user:{
name: 'John',
surname: 'Doe'
},
payload:{
message:'Lorem ipsum ...'
}
seen:false,
timestamp:1569883567
}
After fetched, notification is send to notificationReducer with:
dispatch({type:'FETCH_NOTIFICATION_OK',payload:notification})
And so far everything is ok.
My notificationReducer is structured as below:
const INITIAL_STATE = {
loading:false,
notification:{}
}
const notificationReducer = (state=INITIAL_STATE,action)=>{
switch(action.type){
case 'FETCHING_NOTIFICATION':
return {...state,loading:true}
case 'FETCH_NOTIFICATION_OK':
return {...state,loading:false,notification:action.payload} // I THINK PROBLEM IS HERE
default:
return state
}
}
export default notificationReducer;
The problem is that, when I pass state props to my presentational component, notification object is empty
My presentational component is reported below
import React from 'react';
import {getNotification} from '../actions/actioncreators';
import {connect} from 'react-redux';
class NotificationDetail extends React.Component {
componentWillMount(){
this.props.fetch_notification('9028aff78d78x7hfk');
console.log(this.props.notification) // IT PRINTS: {}
}
render(){
return(
<div>
'TODO'
</div>
)
}
}
const mapStateToProps = state =>{
return {
is_loading:state.notificationReducer.loading,
notification:state.notificationReducer.notification
}
}
const mapDispatchToProps = dispatch =>{
return {
fetch_notification: (id_notification)=>dispatch(getNotification(id_notification))
}
}
export default connect(mapStateToProps,mapDispatchToProps)(NotificationDetail)
Any suggestion ?
EDIT: In my reducer I tried to print the new state. I succesfully got this:
But, Anyway In my presentational component I got an empty object
I think the dispatch call hasn't fired yet. Try executing the below
componentDidMount() {
this.props.fetch_notification();
}
render() {
console.log(this.props.notification); // It should print an output here if your xhr/ajax/axios call is correct
}
Also, using componentWillMount is UNSAFE (according to the ReactJS current documentation). Avoid using this lifecycle in the future.
I'm currently learning react and redux and stumbled into a problem i can't really get my head around. Trying to implement the same functionality
as in this article: https://medium.com/#yaoxiao1222/implementing-search-filter-a-list-on-redux-react-bb5de8d0a3ad but even though the data request from the rest api i'm working with is successfull i can't assign the local state in my component to my redux-state, in order to be able to filter my results. Heres my component:
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as fetchActions from '../../actions/fetchActions'
import Stafflist from './Stafflist'
class AboutPage extends React.Component {
constructor(props) {
super(props)
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
this.updateSearch = this.updateSearch.bind(this)
}
updateSearch(event) {
let newlyDisplayed = this.state.currentlyDisplayed.filter(
(post) => {Â
return (
post.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
|| post.role.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
)}
)
console.log(newlyDisplayed)
this.setState({
search: event.target.value.substr(0, 20),
currentlyDisplayed: newlyDisplayed
})
}
render() {
return (
<div className="about-page">
<h1>About</h1>
<input type="text"
value={this.state.search}
onChange={this.updateSearch}
/>
//component for rendering my list of posts.
<Stafflist posts={this.state.currentlyDisplayed} />
</div>
)
}
}
// this is here i assign my api data to this.props.store.posts
function mapStateToProps(state, ownProps) {
return {
store: state
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(fetchActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AboutPage)
Comparing how i assign my stores state to my local component with how it works in the article, it seems to be done in the same way. Mine:
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
article:
this.state = {
searchTerm: '',
currentlyDisplayed: this.props.people
}
within react devtools i can see my data in as it should be in the store, but it won't work to assign it to my local state within the component in order to perform the filtering, and i don't really know how to debug this. My state in the local component just says
State
currentlyDisplayed: Array[0]
Empty array
also if i change
<Stafflist posts={this.state.currentlyDisplayed} />
to
<Stafflist posts={this.props.store.posts} />
the list renders as it should :)
Reducer:
import * as types from '../actions/actionTypes'
import initialState from './initialState'
export default function postReducer(state = initialState.posts, action) {
switch(action.type) {
case types.FETCH_POSTS_SUCCESS:
return action.posts.data.map(post => {
return {
id: post.id,
name: post.acf.name,
role: post.acf.role
}
})
default:
return state
}
}
Any ideas?
The problem with your code is that you do not handle how to get newly received props to your state. This means that when you receive the data from your api call only the props are updated while component state is not.
If you look carefully at the referenced article, in the onInputChange method they recalculate the state from the props whereas your updateState method only filters from the local state.
Setting the state in the constructor only ensures that the props are copied on the initial mount of the component. At that point in time the store only contains the initial state (initialState.posts in your reducer code). This is the price of keeping both component state and store; you must think of how to keep the two in sync after the initial load.
One solution is to update the state in componentWillReceiveProps:
componentWillReceiveProps(nextProps){
const nextFiltered = nextProps.store.posts.filter(your filtering code here);
this.setState({currentlyDisplayed: nextFiltered});
}
This will update your state whenever the component receives new props. Note react has phased out componentWillReceiveProps and you should use getDerivedStateToProps as of react 16.3. Refer here for more details.
I'm trying to figure out how to user the reducers with and inside my React-Component.
My goal is pretty easy - at least i thought so: I want to toggle a Drawer-Menu. I know I can solve this with React-Only, but I want to learn Redux.
So, I've got a Component…
import React, { Component } from 'react';
class Example extends Component {
// ???
render() {
return (
<button className="burgerbutton" onClick={this.toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
);
}
}
export default Example;
also a Reducer
const initialState = {
buttonstate: false
};
const example = (state = initialState, action) => {
switch (action.type) {
case 'TOGGLE_BTN':
return Object.assign({}, state, {
buttonstate: !state.buttonstate
})
default:
return state
}
}
export default example
and an Action (although I don't know where to put that since it's so simple)
export const toggleDrawer = () => {
return {
type: 'TOGGLE_DRAWER'
}
}
I read a lot of tutorials and most of them want me to seperate between "Presentational Components" and "Container Components". I can't really see how these concepts apply here.
So what do I have to do to do to make this work? Am I looking at this problem from the right angle or do I need 12 "Container Components" to solve this?
I really hope this question makes sense at all and/or is not a duplicate!
In redux you have to dispatch action to update reducer state. So normally a component is connected to the redux store and communication is done through dispatch.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toggleDrawer } from 'action file location';
class Example extends Component {
toggleDrawerHandler() {
this.props.dispatch(toggleDrawer())
}
render() {
// access button state from this.props.buttonstate
return (
<button className="burgerbutton" onClick={this.toggleDrawerHandler.bind(this)}</button>
<div className="drawerMenu isvisible" ></div>
);
}
}
export default connect((store) => {buttonstate: store.buttonstate})(Example);
First, I'm really enjoying using redux "ducks" which is basically a redux reducer bundle. You put your reducer, action constants, and action creators in one file (called a duck). Then you may have multiple ducks for different modules or pieces of state that you'd then combine with combineReducers.
While #duwalanise has the right idea, I'd rather see the second param of connect() be used to directly map the action to dispatch (and there's a good shortcut for it) instead of having to use this.props.dispatch
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toggleDrawer } from './duck';
class Example extends Component {
render() {
const { buttonstate, togglerDrawer } = this.props;
return (
<div>
<button className="burgerbutton" onClick={toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
</div>
);
}
}
const mapStateToProps = (state) => ({
buttonstate: state.buttonstate,
});
export default connect(mapStateToProps, { toggleDrawer })(Example);
One side note, if you have a handler method in your component, it's better to do .bind(this) inside the constructor instead of using an arrow function or .bind(this) inside the event, ie don't do this onClick={() => /* do something */ } or this onClick={this.myHandler.bind(this)} This is an interesting (and long) read on it.
To touch on the Container vs Presentational Component piece: The idea would be to put all of your logic, handlers, redux actions etc into your containers, and pass that through props to your simple (hopefully stateless/pure function) presentational components. Technically, your component the way it's written could be turned into a stateless component:
const Example = ({ buttonstate, togglerDrawer }) => (
<div>
<button className="burgerbutton" onClick={toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
</div>
);
I've gone through many of the Redux and ReactJS tuts. I understand setting actions => action creators => dispatch => store => render view (uni-directional flow) with more data substantial events. My problem is dealing with very simple events that change state. I know not all state always needs to be handled in Redux, and that local state events (set on React components) is an acceptable practice. However, technically Redux can handle all state events and this is what I am trying to do.
Here is the issue. I have a React component that renders a Button. This Button has an onClick event that fires a handleClick function. I set the state of the Button via the constructor method to isActive: false. When handleClick fires, setState sets isActive: true. The handleClick method also runs two if statements that, when either evaluate to true, run a function that either changes the background color of the body or the color of paragraph text. Clicking the same button again sets state back to false and will change back the body color or text color to the original value. This Button component is created twice within a separate component, Header. So long story short, I've got two buttons. One changes body color, the other changes p tag color after a click event.
Here's the code for the Button component:
import React, {Component} from 'react';
import {dimLights, invertColor} from '../../../actions/headerButtons';
import { connect } from 'react-redux';
import { Actions } from '../../../reducers/reducer';
const headerButtonWrapper = 'headerButton';
const headerButtonContext = 'hb--ctrls ';
const dimmedLight = '#333333';
const invertedTextColor = '#FFFFFF';
export default class Button extends Component {
constructor (props) {
super(props)
this.state = {
isActive: false
};
}
handleClick (e) {
e.preventDefault();
let active = !this.state.isActive;
this.setState({ isActive: active });
if(this.props.label === "Dim The Lights"){
dimLights('body', dimmedLight);
}
if(this.props.label === "Invert Text Color"){
invertColor('p', invertedTextColor)
}
}
render() {
let hbClasses = headerButtonContext + this.state.isActive;
return (
<div className={headerButtonWrapper}>
<button className={hbClasses} onClick={this.handleClick.bind(this)}>{this.props.label}</button>
</div>
);
}
}
Here's the code for the imported functions that handle changing the colors:
export function dimLights(elem, color) {
let property = document.querySelector(elem);
if (property.className !== 'lightsOn') {
property.style.backgroundColor = color;
property.className = 'lightsOn'
}
else {
property.style.backgroundColor = '#FFFFFF';
property.className = 'lightsOff';
}
}
export function invertColor(elem, textColor) {
let property = document.querySelectorAll(elem), i;
for (i = 0; i < property.length; ++i) {
if (property[i].className !== 'inverted') {
property[i].style.color = textColor;
property[i].className = 'inverted'
} else {
property[i].style.color = '#3B3B3B';
property[i].className = 'notInverted';
}
}
}
Here's the code for the reducers:
import * as types from '../constants/ActionTypes';
const initialState = {
isActive: false
};
export default function Actions(state = initialState, action) {
switch (action.type) {
case types.TOGGLE_LIGHTS:
return [
...state,
{
isActive: true
}
]
default:
return state
}
}
Here's the code for the actions:
import EasyActions from 'redux-easy-actions';
export default EasyActions({
TOGGLE_LIGHTS(type, isActive){
return {type, isActive}
}
})
If it helps, here's the Header component that renders two Button components:
import React, {Component} from 'react';
import Button from './components/Button';
const dimmer = 'titleBar--button__dimmer';
const invert = 'titleBar--button__invert';
export default class Header extends Component {
render() {
return (
<div id="titleBar">
<div className="titleBar--contents">
<div className="titleBar--title">Organizer</div>
<Button className={dimmer} label="Dim The Lights" />
<Button className={invert} label="Invert Text Color" />
</div>
</div>
);
}
}
Finally, here's the code containing the store and connection to Redux (NOTE: Layout contains three main components Header, Hero, and Info. The Buttons are created only within the Header component)
import React, { Component } from 'react';
import { combineReducers } from 'redux';
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import Layout from '../components/Layout';
import * as reducers from '../reducers/reducer';
const reducer = combineReducers(reducers);
const store = createStore(reducer);
// This is dispatch was just a test to try and figure this problem out
store.dispatch({
type: 'TOGGLE_LIGHTS',
isActive: true
})
console.log(store.getState())
export default class Organizer extends Component {
render() {
return (
<Provider store={store}>
<div>
<Layout />
</div>
</Provider>
);
}
}
What I am looking to do is remove the state logic from the local React component and into Redux. I feel like the functions I have imported need to act as dispatchers. I also feel like I am setting up my initial actions incorrectly. This is such an incredibly simple event that finding an answer anywhere online is difficult. Anyone have any thoughts on what I can do to fix this?
You're almost there. It looks like you've left out the code for Layout component, which I assume is the component that's rendering your Button. The critical piece here is going to be your container, which is the component that's wrapped with Redux's connect to link it to the store. Docs for this. More details here.
What you did:
// components/Button.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';
handleClick() {
dimLights();
}
What Redux wants you to do instead:
// containers/App.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';
class App extends Component {
render() {
// Pass in your button state from the store, as well as
// your connected/dispatch-ified actions.
return (
<Button
state={this.props.buttonState}
onClick={this.props.buttonState ? dimLights : invertColor}
/>
);
}
}
function mapStateToProps(state, ownProps) {
return {
buttonState: state.buttonState
};
}
export default connect(mapStateToProps, {
// Your action functions passed in here get "dispatch-ified"
// and will dispatch Redux actions instead of returning
// { type, payload }-style objects.
dimLights, invertColor
})(App);
Hope that helps! Redux has a lot of boilerplate for simple stuff like this, however, because most of the pieces can be expressed as pure functions, you gain a lot in unit testing flexibility, and get to use the devtools debugger.
I tried to make a reusable component in redux.
The idea behind this is that I am creating a smart combobox and place it several times inside an other component or smart component.
Lets assume the only job from this combobox is to display countries, allow to add new countries and tell the parent what country is selected.
The parent dont have to pass the available countries down to the combobox only the onValueChanged event so the parent knows what country is selected.
This results in the following structure (The items are not really countries to keep it simple but you should get the idea behind it):
//Constants (appConstants.ts)
export const SmartCombobox = {
ADD_ITEM: 'SMART_COMBOBOX/ADD_ITEM'
}
//Action creator (smartComboboxAction.ts)
import { SmartCombobox } from '../constants/appConstants';
export function AddItem() {
return {
type: SmartCombobox.ADD_ITEM
};
}
//Reducer (smartCombobox.ts)
import { SmartCombobox } from '../constants/appConstants';
const initialState = ['Item 1']
export default function items(state = initialState, action) {
switch (action.type) {
case SmartCombobox.ADD_ITEM:
let items = ['Item' + Math.random().toString()]
return state.concat(items);
default:
return state;
}
}
//Container (smartCombobox.ts)
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { default as SmartCombobox } from '../components/combobox';
import * as ComboboxActions from '../actions/smartComboboxAction';
function mapStateToProps(state) {
return {
items: state.items
};
}
function mapDispatchToProps(dispatch) {
return {
comboboxActions: bindActionCreators(<any>ComboboxActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SmartCombobox);
Then I am able to use it like this inside my component or smart component.
When I add a new item, every component that includes my smartCombobox would be synced and has the exact amout of items.
//Component (combobox.tsx)
import * as React from 'react';
interface SmartComboboxProps {
items?: Array<any>,
comboboxActions?: any,
onValueChanged: Function
}
export default class SmartCombobox extends React.Component<SmartComboboxProps, any> {
onValueChanged(event:any) {
let selectedValue = event.target.value;
const { onValueChanged } = this.props;
onValueChanged(selectedValue);
}
componentDidMount() {
// Call value changed for first selected item
this.props.onValueChanged(this.props.items[0]);
}
render() {
const { comboboxActions } = this.props;
let options = this.props.items.map(function (o) {
return <option key={o} value={o}>{o}</option>
});
return (
<div>
<select style={{ width: "200px" }} name="SmartCombobox" onChange={ this.onValueChanged.bind(this) } >
{ options }
</select>
<button onClick={ comboboxActions.AddItem }>Add item</button>
</div>
);
}
}
Final result (Image)
Is this the correct approach for reusable components?
Or are there maybe any pitfalls I might forgot?
There was also the idea that the combobox should be connected directly to an api because the app shouldn't know whats happening in here.
But this would break the idea of flux because I would need a state inside this component etc.
I was against that idea...
Is this the correct approach for reusable components?
Or are there maybe any pitfalls I might forgot
This approach is good.
There was also the idea that the combobox should be connected directly to an api because the app shouldn't know whats happening in here.
You are right here. The source of truth (or rather truth setter) only needs to be one and therefore cannot be the component.