Save local state in React - javascript

In my App I use several Container components. In each Container, there are Buttons.
Depending on the state of the App, the Buttons are clickable (or not). Whether a Button is disabled or not, is managed in the local state of each Container.
The results and state of the App can be saved and loaded.
And here comes the problem:
When I save (or load) the App, its rather hard to "extract" the state of the Buttons from each Container. Saving in the global state (Redux)is rather easy.
But how can I save the local state from each Container and how can I feed it back to each Container?
Reading the local state is managed through a parent Component which calls methods from a child Component. I am aware that this is an antipattern, but it works.
export class SomeConmponent {
....
onClickSaveProjecthandler(event) {
const localStateProjectSettings = this.childProjectSettings.getLocalState();
const localStateLayerFilter = this.childLayerFilter.getLocalState();
return {
"ProjectSettings": localStateProjectSettings,
"Filter": localFilter
};
}
render() {
return(
<ProjectSettingsContainer onRef={ref => (this.childProjectSettings = ref)}/>
)
}
}
Any better suggestions?

As you already mentioned, using redux to have a single point of truth is a great ideia. And to "feed" the state back to containers, you have to map state and props to your components.
This is a container example brought from the oficial doc:
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
}
}
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(Link)
export default FilterLink
The connect does all the magic.

Sounds like you could use Redux and you somehow miscomprehended its architecture as being synonymous with global state only. One of the reasons Redux was made was to address the issue of saving states of multiple independent components.
However, here's an entirely different take on the answer: why not use a global state serializer and connect each component to it? If you don't like the idea of referring to a global variable, a better alternative would be to create some sort of dependency injection (DI) framework that works with React. I've created a library a while back, called AntidoteJS, that does exactly this. You don't need to use it, but it shows how you how it can be done. Just take a look at the source.
Here is something quick and dirty. I haven't tested it, but it shows the basic idea:
import { inject } from "antidotejs"
export class MyComponent extends React.Component {
#inject("StateSerializer")
serializer
constuctor(props, children) {
super(props, children);
this.serializer.load(props.id);
}
setState(newState) {
super.setState(newState);
this.serializer.save(newState);
}
}

Related

How not to pass down props using Redux?

I just learned that we can reduce the complexity of a react project using redux. With the single source of truth (store), we don't need to pass down states to components that don't need them. I'm struggling with understanding this statement.
Say I have three components, A, B and C. A is a container with a state called text. B is a custom button and C only displays the text. Whenever B is clicked, it updates the state in A. Then C will display the updated text.
A
/ \
C B
I have tried to apply redux to the app and found that I still need to pass down the props. The only difference is that I am passing down this.props.text instead of this.state.text.
I can't see how redux can benefit an app like this.
App.js
import React, { Component } from "react";
import { connect } from 'react-redux';
import MyButton from "./MyButton";
import { handleClick } from "./actions";
import Display from "./Display"
class App extends Component {
render() {
return (
<div className="App">
<MyButton onClick={()=>this.props.handleClick(this.props.text)} />
<Display text={this.props.text} />
</div>
);
}
}
const mapStateToProps = state => ({
text: state.text.text
})
const mapDispatchToProps = dispatch => ({
handleClick: (text) => dispatch(handleClick(text))
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
Also, if we have another app with structure shown below. Say B doesn't care about A's state but C needs it to display the text. Can we skip B and just let C use A's state?
A
|
B
|
C
I think I found the solution. I simply created a file stores.js and
export the store. So I can import it and retrieve the state by
invoking store.getState() whenever a child component needs the it.
You shouldn't do that.
Instead you should use the connect function with each component, everywhere in the structure, that needs access to a property of your store.
But, if you only have three components, you probably don't need Redux or a global store for your app state.
Redux comes with a lot of opinions on how to handle your global state that are meant to secure your data flow.
Otherwise, if you only need to avoid prop drilling (i.e. passing down props through many levels, as in your second exemple) you may use the native React context API that does just that: reactjs.org/docs/context.html
Edit
Things should be clearer with an exemple:
import React, { Component } from "react";
import { connect } from 'react-redux';
import MyButtonCmp from "./MyButton";
import DisplayCmp from "./Display"
import { handleClick } from "./actions";
// I am doing the connect calls here, but tehy should be done in each component file
const mapStateToProps = state => ({
text: state.text.text
})
const Display = connect(mapStateToProps)(DisplayCmp)
const mapDispatchToProps = dispatch => ({
onClick: (text) => dispatch(handleClick(text))
})
const MyButton = connect(null, mapDispatchToProps)(MyButtonCmp)
class App extends Component {
render() {
return (
<div className="App">
{/* No need to pass props here anymore */}
<MyButton />
<Display />
</div>
);
}
}
// No need to connect App anymore
// export default connect(mapStateToProps, mapDispatchToProps)(App)
export default App
In this example, you may map app state to props using redux.
I don't see why you would process the information this way(with redux) unless you were planning on using the data in multiple parts of the application and wanted to re-use the action code.
See more:
https://react-redux.js.org/using-react-redux/connect-mapstate
2nd question
Also, if we have another app with structure shown below. Say B doesn't care about A's state but C needs it to display the text. Can we skip B and just let C use A's state?
In Redux, yes.
With React Hooks, yes.

How can i outsource functions that use state and do setState

I have a class which has many functions, i wish to outsource these functions and put each group of function inside a file of their own and then use them by importing and calling them.
Usually this is very simple, you simply put the function bodies inside another file and then export them, but in my case i use this.state and this.setState, is it still possible to outsource these function? if not, is there a better practice?
Thank you.
I just knocked this up real quick with no testing whatsoever, tell me if this is kinda what you were looking for?
window._stateManager = new StateManager();
class X extends React.Component {
constructor(props) {
super(props);
_stateManager.addStateFunction(this.setState.bind(this));
}
}
class StateManager() {
constructor() {
this.stateFunctions = [];
}
addStateFunction(ss) {
this.stateFunctions.push(ss);
}
updateStates(obj) {
this.stateFunctions.forEach(ss => {
ss((state => {
// here you have access to "this.state" as state
return Object.assign({}, state, obj);
// whatever you return from this function changes the state of the react component
}))
})
}
}
Why you want to update the state by the function that doesn't belong to your component or child component. It doesn't make sense. You want to use redux there will be a single source of state of your entire App stored in the store. All your state update logic will be separated in reducer and you would have access to action creator that can update your App's state.

How does Redux change UI in React?

I've been trying hard to wrap my head around this concept but with no luck.
The official React tutorial is really good but for me it's way too complex and just simply a little bit too hard.
I'm trying to understand Redux and so far I can create actions, reducers, I can dispatch an action and then see how the store state changes after dispatching it. I also managed to understand connect of react-redux and it works perfectly well and I'm able to trigger dispatches from any place in my app. So I think I almost got it figured out. Almost, because here's the elephant in the room - I dispatch the action, I see the Redux state change but HOW DO I CHANGE THE UI?
For example I have text object in my initial state with value Initial text and once a button is clicked I want to change the text to Clicked text and DISPLAY the text somewhere in the UI (let's say on the button).
How do I "access" the Redux state in React and how do I dynamicaly change it?
It seems to be very simple without React, e.g..: https://jsfiddle.net/loktar/v1kvcjbu/ - render function handles everything, I understand everything that happens here.
But on the other side "todo" from official React+Redux tutorial looks like this: https://redux.js.org/docs/basics/ExampleTodoList.html , it's so sophisticated I have no idea where to look.
The Add Todo button submits a form that dispatches dispatch(addTodo(input.value)) action. The action itself does nothing just increases the ID and passes the text to the store and the reducer just returns the new state. Then how the todo is being rendered on the page? Where? I'm lost at this point. Maybe there are simpler tutorials, I'd love to have an one-button Redux tutorial it still can be complicated with multiple layers of components :(
I suspect the magic happens in TodoList.js as they're mapping over something there but still I have no idea where todos come from there, and what it has to do with Redux (there's no simple reducer/action/dispatch in that file).
Thanks for any help!
I think the confusion you have is that part of reducer composition and selectors.
Let's look at it in a reverse order, from the UI back.
In the connected component containers/VisibleTodoList.js it gets the todos from the "state" (the global store object of redux) inside mapStateToProps, while passing it through the getVisibleTodos method.
Which can be called a selector, as it selects and returns only a portion of the data that it receives:
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
case 'SHOW_ALL':
default:
return todos
}
}
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
The state (redux store) that passed to mapStateToProps came from the root reducer reducers/index.js and is actually a single reducer (object) that represent the combination of all other reducers via the combineReducers utility of redux:
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'
const todoApp = combineReducers({
todos,
visibilityFilter
})
export default todoApp
As you can see, the todos reducer is there. so that's why inside the mapStateToProps we call it like this state.todos.
Here is the reducers/todos.js:
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id === action.id)
? {...todo, completed: !todo.completed}
: todo
)
default:
return state
}
}
export default todos
On each action of type 'ADD_TODO' it will return a new state with the new todo:
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
This the the action creator for it inside actions/index.js:
let nextTodoId = 0
export const addTodo = text => {
return {
type: 'ADD_TODO',
id: nextTodoId++,
text
}
}
So here is the full flow of redux (i omitted the button that calls the action as i assume this is obvious part for you).
Well, almost a full flow, none of this could have happened without the Provider HOC that wraps the App and inject the store to it in index.js:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Now when the redux state changes, a call to mapStateToProps is invoked that will return the new mapped props. connect will pass those new props and this will trigger a new render call (actually the entire react life cycle flow) to the connected component.
This way the UI will be re-rendered with the fresh new data from the store.
connect is typically used to connect react component and Redux state.connect is a higher order component. The component which are using connect function are wrapped inside it. The method signature is
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps has access to redux state and mapDispathToProps has access to store.dispatch. All the props are merged and passed as props to underlying component. Redux has only single state of truth. store that is passed as a props to Provider components has a method called store.getState().
So , keep one thing in mind react components are data driven . Data derives UI. React components rerender only when state is changed or props have been modified. you make change in any of two , components goes through various life cycle methods.

React componentDidUpdate method won't fire on inherited props change if connected to a store that didn't change

I want my component know if some library is already loaded. To know that from any context i connect it to the "library" reducer of my store to my component.
I also pass it a configuration object this.props.dataObject from the parent where the component has been called. Like this:
class GoogleButton extends Component {
render() {
if (this.props.libraries.google) {
return <a id='sharePost' className='google_icon'></a>
} else {
return null
}
}
componentDidUpdate() {
gapi.interactivepost.render('sharePost', this.props.dataObject)
}
}
function mapStateToProps(state) {
return { libraries: state.libraries }
}
export default connect(mapStateToProps)(GoogleButton)
The reducer that handles the libraries state is like this:
let newState = {...state}
newState[action.libraryName] = action.state
return newState
When I change the library state componentDidUpdate works. The problem is when i change the prop inherited by the parent this.props.dataObject. In that case is where componentDidUpdate wont fire. If i remove the connect from the component it works as espected. I'm missing something here?
Most likely some of your props are mutated outside the component.
For example, you might be rendering your component like this:
class Parent extends Component {
constructor() {
super()
this.state = { libraries: {} }
}
handleClick() {
// MUTATION!
this.state.libraries.google = true
// Normally this forces to update component anyway,
// but React Redux will assume you never mutate
// for performance reasons.
this.setState({ libraries: this.state.libraries })
}
render() {
return (
<div onClick={() => this.handleClick()}>
<GoogleButton libraries={this.state.libraries} />
</div>
)
}
}
Because Redux apps deal with immutable data, connect() uses shallow equality check for its props to avoid unnecessary re-renders. However, this won’t work if you use mutation in your app.
You have two options:
Don’t Mutate Anything
This is the best option. For example, instead of something like
handleClick() {
this.state.libraries.google = true
this.setState({ libraries: this.state.libraries })
}
you can write
handleClick() {
this.setState({
libraries: {
...this.state.libraries,
google: true
}
})
}
This way we are creating a new object so connect() wouldn’t ignore the changed reference. (I’m using the object spread syntax in this snippet.)
Disable Performance Optimizations
A worse alternative is to completely disable performance optimizations made by connect(). Then your props would update even if you mutate them in the parent, but your app will be slower. To do this, replace
export default connect(mapStateToProps)(GoogleButton)
with
export default connect(mapStateToProps, null, null, { pure: false })(GoogleButton)
Don’t do this unless absolutely necessary.
I solved it. I'm not 100% sure that this is accurate, but I will explain. If im wrong with something, please correct me.
I keep thinking about the shallow equality check that Dan said in his answer. The problem was there.
I was passing down an object from the parent and the nested elements of that object were the ones that changed. The object remain the same. So with the shallow equality check that connect brings the component will never update.
My solution was in the parent use Object.assign({}, dataObject) when I pass down the prop so I make another different object. Now shallow equality check could compare it and determinate that the props have changed and there before update the component.
i had same problem and i used object.assign for create new state but i use combineReducer and it cause multi level state ,in my case i pass whole state as props to component so shallow equality check can not detect my state change so componentDidUpdate didnot call,it is important to pass state in level it change when using combine reducer
in my case i pass it like this
const MapStateToProps=(state)=>{
return {
reportConfig:state.ReportReducer
}
};
and my state tree is like this
{
ReportReducer: {
reportConfig: {
reportDateFilter: 'this week',
reportType: null,
reportShopId: null,
updateShop: true
}
}
}
and in my reducer and return it like this as ReportReducer
export default combineReducers({reportConfig});
and my root reducer is like this
const rootReducer =combineReducers({ReportReducer});
const store = createStore(rootReducer ,{},enhancer);
Another option that you can use is to make a deep copy of the inherit prop this.props.dataObject on the child component, this in order for the componentDidUpdate to 'catch' the updated prop, you could use:
dataObject={JSON.parse(JSON.stringify(valueToPass))}
Use this where you are passing the prop from the parent component, this works for me in a similar problem (This applies when you don't have any function inside the prop).
I had this exact same problem with Components I used from an external library.
So I didn't had the option to modify the inherited property.
I only needed a part of the inherited property object (will use dataObject for simplicity). Solved it by adding it to the mapStateToProps function:
function mapStateToProps(state, ownProps) {
return { libraries: state.libraries, neededValue: ownProps.dataObject.value }
}
By which a shallow compare is enough to notice a value change. So use this.props.neededValue iso this.props.dataObject.value in the render() function.

Should mapDispatchToProps dispatch initialization actions?

Suppose a stateless, functional UserProfile component that displays user data for the given url. Suppose it is being wrapped with connect(mapStateToProps, mapDispatchToProps)(UserProfile). Finally, suppose a reducer that reduces into state.userProfile. Anytime the url changes, I need to re-initialize the state.userProfile, so a solution that comes to mind is to do so from within the mapDispatchToProps like so:
function mapDispatchToProps(dispatch, ownProps) {
dispatch(fetchUser(ownProps.userId))
return {
...
}
}
Provided that the thunked fetchUser ignores repeated calls by comparing with current state, is this an acceptable practice? Or are there problems associated with calling dispatch immediately from this map function?
This is unsupported and can break at any time.
mapDispatchToProps itself should not have side effects.
If you need to dispatch actions in response to prop changes, you can create a component class and use lifecycle methods for this:
class UserProfile extends Component {
componentDidMount() {
this.props.fetchUser(this.props.id)
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.props.fetchUser(this.props.id)
}
}
// ...
}

Categories

Resources