Consider this example:
class App extends Component {
constructor() {
super();
this.person = new Person("Tim", 23);
this.state = {
name: this.person.name
}
}
changeName() {
this.person.setName("Jane");
this.person.setAge(22);
setState({name: this.person.name});
}
render() {
return (
<div>
<div>Your name is: {this.state.name}</div>
<div>Your age is: {this.person.age}</div>
<div><button onClick={this.changeName.bind(this)}>Change Name</button></div>
</div>
)
}
}
What I'm querying here is when a variable should be added to the state. In this example, although this works age isn't in the state.
I run into this a lot when working with objects, I'm not sure if it's best practise to add any rendered object property to the state, or if I should only worry about adding properties to the state if they're potentially going to be updated. I'm quite sure what I'm doing in this example would be bad, as age is updating, but isn't being reflected in the state.
Any ideas on the "correct" way to do this?
React doesn't dictate how you manage your data. If you're using an object with getters/setters, then it might be simpler to store the entire object in state:
changeName() {
this.person.setName("Jane");
this.person.setAge(22);
this.setState({person: this.person});
}
In this manner, your object continues to be responsible for the data, and whatever internal processing this implies, while the resultant object itself is stored in the component state.
That said, using data objects like Person, while possible, is not idiomatic React. I would recommend using something like Redux, and setting up unidirectional data flow. This means creating a reducer to manage your state, and using action creators to communicate with the Redux store.
You can initialize your object's default values in the reducer. This is returned by default from the Redux store.
Your reducer would listen for an UPDATE_PERSON action, which would carry the payload for the entire updated Person object. This would be stored in state, as below:
reducers/person.js
const UPDATE_PERSON = 'UPDATE_PERSON';
const initialState = {
name: "Tim",
age: 23
}
const personReducer(state = initialState, action) {
switch (action.type) {
case UPDATE_PERSON:
return {
...state,
name: action.payload.name,
age: action.payload.name
}
default:
return state;
}
}
Your action creator is a simple function with a type property and some kind of payload:
(presumably) actions/person.js
export const updatePerson(data) {
return {
type: UPDATE_PERSON,
payload: data
}
}
You then connect the Redux store to your component, and use the action creator to dispatch the action to the store:
import { connect } from 'react-redux';
import * as PersonActionCreators from '../actions/person';
class App extends Component {
changeName() {
this.props.updatePerson({name: "Jane", age: 22});
}
render() {
return (
<div>
<div>Your name is: {this.props.person.name}</div>
<div>Your age is: {this.props.person.age}</div>
<div><button onClick={this.changeName.bind(this)}>Change Name</button></div>
</div>
)
}
}
const mapStateToProps = (state) => ({
person: state.person
});
const mapDispatchToProps = {
updatePerson: PersonActionCreators.updatePerson
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
The above code assumes you have a root reducer in the following format:
import { combineReducers } from 'redux';
import personReducer from './reducers/person';
const appReducer = combineReducers({
person: personReducer
})
const rootReducer = (state, action) => appReducer(state, action);
export default rootReducer;
You will need to create the store and connect your root reducer to it. Details on that can be found here.
The combineReducers function simply helps to construct the root reducer:
The combineReducers helper function turns an object whose values are
different reducing functions into a single reducing function you can
pass to createStore.
This is more boilerplate, but it is the established and most popular way of handling application state in React. It may seem like a lot to wrap your head around at first, but once you become familiar with reducers, action creators, and the connect function, it becomes very straightforward.
Redux uses a uni-directional data-flow, which means data streams downwards from the top-level components to child components. Stateful components are kept to a minimum; but where state is required, the connect function provides it. When a component needs to modify state, it does so through an action creator. The reducers listen to actions and update state accordingly.
See Dan Abramov's free egghead courses on this topic for an excellent introduction to Redux:
Getting started with
Redux
Building React Applications with Idiomatic
Redux
It's simple. The right way is using state for properties you want to display.
In this case, your code should be
setState({
name: this.person.name,
age: this.person.age
});
Why? Well, it's best practice, and using this.state is encouraged in docs. Attaching properties on the component (this), is generally usual for methods.
Also, consider the component methods shouldComponentUpdate(nextProps, nextState), componentWillUpdate(nextProps, nextState), componentDidUpdate(prevProps, prevState).
Obviously, if you need their arguments, you will not be able to retrieve the old/new properties you change, if them're not on state or props.
Related
I have a api call which takes in a varable from the state in my actions which goes to an axios get and it passes it there. I know this because if I console the variable in the axios get its there which goes to a route and to the controller but when I console loge the request in the controller its empty. I am trying to do a find() to a specific email. If I hard code it then it works perfectly which means my variable probably isn't passing there and I don't know why. I have a post that works perfectly
my action
export const getUser = (currentUser) => {
return(dispatch, getState) => {
API.getUserInfo({
emailaddress:currentUser.emailaddress,
password: currentUser.password
})
.then(res => {
dispatch({type:"USER_PROFILE",userPro:res.data})
})
.catch(err => console.log(err));
}
}
reducer
const initState ={
userProfile:[]
}
const userReducer = (state = initState,action)=>{
switch(action.type){
case "CREATE_USER" :
console.log("created User", action.newProfile)
return state;
case "USER_PROFILE":
console.log("User", action.userPro)
return {
userProfile: [...state.userProfile,action.userPro]
}
default:
return state;
}
}
root reducer
const rootReducer = combineReducers({
authR: authReducer,
userR:userReducer
})
mapstatetoprops
const mapStateToProps = (state)=>{
console.log(state)
return{
userInfo:state.userR.userProfile
}
}
export default connect( mapStateToProps ) (Layout);
Right, so you're now moving away from component state and into application state. This might end up being a long answer but in short I would suggest you read-up on the Redux and React-Redux documentation. https://redux.js.org/ https://react-redux.js.org/
Redux is all about persisting data in your application. Which is what you need if you want to take the data from one page and make it available for use in another page. It is essentially comprised of three parts:
Actions: Functions that are called to carry data from your components or APIs into a common place (reducers). The data is recognized as a "payload."
Reducers: Functions that are triggered by your actions. They use the "payload" to return a new app state (data).
Store: Just like the name suggests, it is an object that contains all your reducers; a collection of all your application states.
Now react-redux is simply middleware that let's your components communicate with your store.
There is some pretty standard mark-up to get this all to work. I'll show you examples with what I assume your code looks like.
So first let's define a reducer (a data maintainer for lack of better words) and lets store it in a file called authReducer.js
const authReducer = (state = {}, action) => {
switch(action.type){
CASE "SET_USER_CREDS":
return {
user: action.payload
}
default:
return state
}
}
export default authReducer
So digging into this code. We defined a function with two parameters, a state which we gave an initial value of {} and an action, which refers to the actions that get sent to this reducer. If there was an action with a type of "SET_USER_CREDS" then the reducer returns an object that will contain information on a user. As we can see, the only way it can get data is by consuming it from an action.
Now we need an action, a means to communicate with the reducer we just made. Let's create a file called authActions.js
export const recordUser = (userData) => {
return {
type: "SET_USER_CREDS":
payload: userData
}
}
Looks simple enough, we created a function that essentially is trying to meet the requirements of making our reducer to work. These action creators are actually used by our components, this is method in which we can get data from a component and keep it somewhere.
But wait, where do we keep this data? We talked about reducers, but where do they live? Well it's time to build our store.
store.js
import {createStore, combineReducers} from "redux"
import authReducer from "./authReducer"
const store = createStore(combineReducers({
auth: authReducer
}))
export default store
Alright we got a store now. Quick facts about the syntax. We used a few methods fromt the redux library. We used createStore() and we passed in combineReducers(), where the latter accepts an object. In the object we define a key-value pair for each reducer we want to put in our store. The key is typically the name/type of data the reducer is managing.
Cool, we've set up a store, a reducer and an action-creator. But as is, there is no way for React to communicate with your store. Well this is where react-redux comes in. In whereever you defined your react-router-dom routes, we'll need to make some modifications.
Let's just say this is your router file, you'll need to add the following.
import {Provider} from "react-redux"
import store from "./store"
<Provider store={store}>
//Routes
</Provider>
<Provider> is a component that accepts a redux-store as an argument. We can wrap our <Routes/> inside of it, thus providing the store to all our components.
Congrats, we're about 2 steps away from getting all this to work.
So now in your Header component, or wherever you're entering the user data you need to do a few things.
1) Bring in some dependencies.
import {connect} from "react-redux"
import {recordUser} from "./authActions"
2) Outside of your component. Define a function called mapDispatchToProps()
const mapDispatchToProps = (dispatch) => {
return {
recordUser: (userData) => {
dispatch(recordUser(userData))
}
}
}
In short, this is a function that will let us call your action-creators inside your component. The recordUser key is now an available prop inside your component.
3) Inside your component you need to define an event-handler to use our new prop. It will need to be triggered when the user is navigating to the other page.
handleRecordUser = () => {
const userData = {
email: this.state.email,
password: this.state.password
}
this.props.recordUser(userData)
}
So its doing as we promised, taking data from our component state and passing it off to an action creator. Remember, you need to call this event-handler to execute this action at the same time as the re-route to the new page. If you're using a <Link> to reroute, just do something like:
4) Modify the export of this component to use connect()
export default connect(null, mapDispatchToProps)(Header)
connect() gives our component access to methods like dispatch() which is a requirement to use your action-creators.
Last, but not least, consume your store.
In the component you routed to you need to do the following:
1) Bring in some dependencies.
import {connect} from "react-redux"
2) Outside your component, define a function called mapStateToProps()
const mapStateToProps = (state) => {
return {
auth: state.auth
}
}
mapStateToProps() let's you tap into the store state and enables you to choose which reducer data you want to bring into your component. state.auth should look familiar, since in our store.js file we defined a key-value pair of {auth: authReducer}. We're simply calling that key.
So by defining a key of auth, I'm now saying that I will have a prop in my component called this.props.auth and it's value will be the reducer ouput.
3) Lastly, connect your component
export default connect(mapStateToProps)(YourComponent)
Now you can utilize the data from your previous Login component by consuming the saved data in your redux store by making use of this.props.auth.
you can use redux over here or you can use the localstorage, cookie, sessions any one of these browser storage to set the values and when your component gets rendered you can retrieve this data from browser and make your API call. Though this is not the best approach but if you don't know how to use redux then you can apply this.
Been reading through Redux tutorials and a bit new to Redux. I am trying to access the current state that I have in my Redux Store. I can see the state object with my data in Redux State in dev tools but when I go to connect mapStateToProps the state returns undefined.
export class VersionDiff extends Component {
render() {
const { diffs } = this.props;
console.log(diffs, 'im the diffs');
return (
<div>
Empty Div
</div>
);
}
}
const mapStateToProps = state => {
return {
diffs: state.diffs,
};
};
export default connect(mapStateToProps)(VersionDiff);
The console log I have in the render method returns undefined when I thought I could access the items from my redux store on this.props in the render method. This is my reducer below.
export default (state = [], action) => {
switch (action.type) {
case 'SET_DIFFS':
return [...state, action.diffs];
default:
return state;
}
};
When I look at Redux devtools I can see the diffs object which I am trying to pull from the store. Im sure this is something very simple but just cannot seem to access this.
I wanted to try and provide some more clarity in case this might be helpful to anyone in the future. I believe what was happening here was since I am exporting my module as default in my connect function at the bottom of the file I would be able to use the standard import syntax. I believe I was trying to import my VersionDiff module as a named import so something like the following.
import { VersionDiff } from '../components/VersionDiff;
This wouldn't work as I was not able to access my component as it's a default export and therefore my props wouldn't be available to be passed into my component as the component would return undefined.
I believe this was a mistake on my end of not actually being able to find the component in my file before adding it into the connect function and try to pull state from the store.
Solution:
import VersionDiff from '../components/VersionDiff';
I have a react app (repo) that I want to use redux to store the state universally, so the root app can access it.
For example: one page has a GET API call that populates the page. That works fine and all, but I'm confused as to how to do a couple things.
How can I use variables in the redux action, to give the action say the ID of the model and have it return the model (API returns json).
How can I then pass that state up so that a higher ordered component (such as the base App.js) can access the state, so that I can use variables from the current page in the navigation.
What/when is the best way/time to update the redux state so that the changes reflect across anywhere using the redux state?
Specifically (in this project): If you are on localhost/spells/X with X being the model ID, how can I pass the state up from that page's container component (in this case LayoutSpellView) up to MaterialUIApp
index.js
|--App.js
|--MaterialUiApp
|--Router
|--LayoutSpellView (pass state up to MaterialUiApp)
With Redux you don't pass the state up or down. You update the global state with your action creators and reducers. Wherever you need to reach the state you connect your components to the state and use it. You have a store and it includes a global state. That global state may contain multiple different states.
You can use payload or any other name, variable with your action creator. In your reducer you can get those with action.payload, action.id, etc.
As I explained in the first paragraph, you update your state whenever you need. After that you connect any component to your state wherever you need.
There is no best time or best way to do that. This is up to your code and app logic.
Of course there are some best practices but we can't talk about them so broad. After you are getting involved with Redux you will see some of them around. For example I said "we don't pass up or down the state with Redux". This is true but sometimes to avoid so many connects around components we use container apps, connect that app to store (you reach state via store actually) and then pass the related state parts to the related components.
I recommend Redux's own documentation as starting point: https://redux.js.org/
To help you see the data flow, here's a sketch of how everything ties together. In my example code below, this is the data flow:
Clicking the "Load Comments" button dispatches a thunk with the parameter userId. (A thunk is an async action.)
The thunk uses the userId to make its async call, and then dispatches an action setComments(comments) with the received comments as its payload.
The Comments reducer catches that action and updates the Redux state with the comments array.
The Container to updates comments in mapStateToProps
The Component receives the updated comments, and displays them in the <ul>
// actions.js
export const SET_COMMENTS = "MyApp/setComments";
export const setComments = comments => ({
type: SET_COMMENTS,
payload: comments
});
// thunks.js
import { setComments } from './actions';
export const getCommentsAsync = id => dispatch => {
return axios
.get(`http://localhost:5000/comments/${id}`)
.then(comments => dispatch(setComments(comments)));
};
// reducer.js
import { SET_COMMENTS } from './actions';
const initialState = {
comments: []
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_COMMENTS:
const comments = action.payload;
return {
...state,
comments
};
default:
return state;
}
};
// components.js
export default function CommentsList({ comments, loadComments, userId }) {
return (
<div>
<ul>
{comments.map(comment => <li key={comment.id}>{comment.body}</li>)}
</ul>
<button onClick={() => loadComments(userId)}>Load Comments</button>
</div>
);
}
// containers.js
import { connect } from "react-redux";
import { getCommentsAsync } from "./thunks";
import CommentsList from "./components";
mapStateToProps = state => ({
comments: state.comments,
userId: state.user.id
});
mapDispatchToProps = {
loadComments: getCommentsAsync
};
export default connect(mapStateToProps, mapDispatchToProps)(CommentsList);
I am facing some issue while using Dispatcher in ReactJS. So, I try to remove this dispatcher from the store and still store works well. Store properly hold my data and change event works well.
Now I am bit confusing to use dispatcher in our application.
Here is the code
MenuList is my component in which I call MenuStore.getMenuFromAPI() and after I also added onChange event of MenuStore.
class MenuList extends React.Component{
constructor(){
super();
this.state = {open:false, menuList:"",numbering:-1}
}
componentWillMount(){
var that = this;
MenuStore.getMenuFromAPI();
MenuStore.on("change", ()=> {
that.setState({menuList:MenuStore.getMenu()});
})
}
componentWillReceiveProps(nextProps){
if(nextProps.show!=this.props.show){
this.setState({open:nextProps.show});
}
}
render(){
const { classes } = this.props;
return (
<div>My MEnu</div>
)
}
}
MenuStore
class MenuStore extends EventEmitter {
constructor() {
super();
this.menu = null;
}
getMenu(){
return this.menu;
}
getMenuFromAPI(){
var that = this;
$.ajax({
type: "POST",
url: LinkConstants.GETMENU,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data: "",
dataType :"json",
success: function(response) {
that.menu =response;
that.emit("change");
}.bind(this),
error: function(xhr, status, err) {
console.log(err);
}.bind(this)
});
}
// handleAction(action) {
// switch (action.type) {
// case ActionTypes.MENU: {
// this.getMenuFromAPI();
// break;
// }
// }
// }
}
const menuStore = new MenuStore;
//Dispatcher.register(menuStore.handleAction.bind(menuStore));
export default menuStore;
As you can see I commented out Dispatcher.register line and handleAction function.
Above code works properly fine but I wanted to know why to use Dispatcher over here ?
If I want to just store my data in the MenuStore and get it back from MenuStore on any of the component in the application. So it is necessary to use dispatchers and action or to just work with stores only.
Please clarify my doubts with proper example or case scenario (if possible) when to use dispatchers and action or when to work with stores only.
In your example your are not using Redux at all, you've just created a class that is used as a simple storage for the fetched data but your are not using any of Redux capabilities.
Redux is all about one store which is just a plain object which represents your application state tree. In order to change this state you dispatch actions. Actions are just simple objects which describe what happened. Each action has a type field which describes the action. Actions are treated by reducers which are functions that gets the current state and the dispatched action and decide on the next state of the application. This is Redux in few sentences.
Redux store has a method named dispatch which is used to dispatch actions. As mentioned in Redux documentation, this is the only way to trigger a state change.
Lets say we have a TODO list application. Our store may be represented as an array of strings (todo items).
To add a new item to the list we will define a simple action:
const addItemAction = (item = '') => ({
type: 'ADD_ITEM',
data: item,
});
Dispatching this action can be done from within one of your component's methods which will be attached to some keyboard/mouse event:
class TodoList extends React.Component {
...
// addNewItem is called with a value from a text field
addNewItem(item) {
store.dispatch(addItemAction(item));
}
...
}
As I mentioned above, state is changed by a reducer function. The reducer decides if to change the state and how to change it. If dispatched action shouldn't change the state it can just return the received state:
function todoReducer(state = [], action) {
switch(action.type) {
case 'ADD_ITEM':
return [...state, action.data];
break;
default:
return state;
}
}
Reducer is passed to createStore method:
import { createStore } from 'redux'
const store = createStore(todoReducer);
In the TodoList component you can subscribe to the store using store.subscribe method which accepts a callback function that will be called each time the store state changes. When detecting a change you can call setState of your component to set the list on the component state and to cause the component to rerender:
class TodoList extends React.Component {
....
componentDidMount() {
this.storeSubscription = store.subscribe((state) => {
// For the example I'm just setting the state (list of todos)
// without checking if it changed or not
this.setState({
todos: state,
});
});
}
render() {
return this.state.todos.map(todo => <div>{todo}</div>);
}
....
}
This is an almost complete example of using Redux. We used action to describe an event in our application, we used the store's dispatch method to dispatch the action to the store, Redux will invoke the reducer when it gets new actions, the reducer computes the new state and our component detects the change by using the store's subscribe method.
There are more things to consider and to take care of in a more complex application. You will probably have a more complex state tree so you will need additional reducers to take care of computing the state. Additionally in some step you would need to consider working with some helpers to reduce overhead of subscribing to state change and detecting changes.
In a more complex application you would probably connect your component to the store by a binding library such as react-redux so your component will receive the relevant parts of the store by props which will save the overhead of subscribing to the store changes and deciding on when to rerender the component.
I would recommend watching "Getting started with Redux" by Dan Abramov to get some more understanding of what is Redux and how to work with it.
Getting started with Redux
A React component OilBarrel connected my redux store to create a container OilBarrelContainer:
// ---- component
class OilBarrel extends Component {
render() {
let data = this.props.data;
...
}
}
// ---- container
function mapStateToProps(state) {
let data = state.oilbarrel.data;
...
}
const OilBarrelContainer = connect(mapStateToProps)(OilBarrel)
// ---- reducer
const oilbarrel = (state = {}, action) => {
let data = state.data;
}
const storeFactory = (server = false, initialState = {}) => {
return applyMiddleware(...middleware(server))(createStore)(
combineReducers({oilbarrel, otherReducer1, otherReducer2}),
initialState
)
}
I find it strange that mapStateToProps() receives the top level state object (the entire state of the application), requiring me to traverse state.oilbarrel.data, when the reducer (conveniently) only receives the branch of the state that belongs to this component.
This limits the ability to reuse this container without knowing where it fits into the state hierarchy. Am I doing something wrong that my mapStateToProps() is receiving the full state?
That is the mapStateToProps behavior. You have to think redux state as a single source of truth (by the way, that is what it really is) independently of the components you have in project. There is no way out, you have to know the exactly hierarchy of you especific data in the state to pass it to your container component.
No this is intentional, because you may want to use other parts of the state inside your component. One option is to keep the selector (mapStateToProps) in a separate file from your component, which will help you reuse the selector, if you app is very large and complex you can also checkout libraries such as reselect which helps you make your selectors more efficient.
Dan Abramov offers a solution for this in his advanced redux course under Colocating Selectors with Reducers.
The idea is that for every reducer, there is a selector, and the selector is only aware of it's reducer structure. The selectors for higher level reducers, wrap the lower level reducer, with their part of the state, and so on.
The example was taken from the course's github:
In the todos reducer file:
export const getVisibleTodos = (state, filter) => {
switch (filter) {
case 'all':
return state;
case 'completed':
return state.filter(t => t.completed);
case 'active':
return state.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
In the main reducer file:
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
Now you can get every part of your state without knowing the structure. However, it adds a lot of boilerplate.