How does Redux change UI in React? - javascript

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.

Related

Understanding mapStateToProps & mapDispatchToProps in React-Redux

I'm trying to understand the connect() method of react-redux. Usually it takes two function as argument: mapStateToProps() & mapDispatchToProps(). I write a example for myself, here is connect() section of my User component:
//imports...
class User extends Component {
/* constructor, JSX, other functions... */
}
const mapStateToProps = (state) => {
return {
users: state.UserReducer
};
};
const mapDispatchToProps = (dispatch) => ({
deleteUser: (id) => dispatch(deleteUser(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(User);
According to Docs I have taken the following two conclusions about mapStateToProps() & mapDispatchToProps():
mapStateToProps(): it makes that state available in our component. i.e. it is used to pass reducer to component.
mapDispatchToProps(): it maps component relevant functions to action functions, i.e. with this function We can perform the action that we want in our component.
is my conclusions correct?
React components accept data from outside via props.
maptStateToProps and mapDispatchToProps, literally, pass the selected state properties and actions that are needed inside your component as props.
The state values and actions passed to the component are available in the props of the component.
In your example, you can use this.props.users or this.props.deleteUser().

Connect a normal function to Redux's mapDispatchToProps

I'm getting my head around Redux and while I understand how it functions I'm not entirely sure if I'm using it correctly.
The Redux Documentation gives plenty of example on how to connect a functional component to my actions. But is there any way to call my action directly from within a function?
Is it possible to get something like this to work?
function mapDispatchToProps(dispatch) {
return {
saveUserData: user => dispatch(saveUserData(user))
};
}
function ConnectedUserInfo(){
console.log("fetching user info")
fetch('endpoint', 'headers',
}).then(response =>
response.then(user => {
this.props.saveUserData(user)
})
)
}
const getUserInfo = connect(
null,
mapDispatchToProps
)(ConnectedgetUserInfo);
export default getUserInfo;
I tried setting my redux state directly with saveUserData(user) but couldn't get the Store to change.
Connecting the two doesn't seem to do anything, unless I'm doing something wrong.
I'm unsure if this is the solution I'm looking for or if Redux wants me to mapDispatchToProps every time I want to change the state.
if you read the react redux documentation , you can see that connect method returns an HOC which accepts only react components.
in your code ConnectedgetUserInfo is not a react compoenent.
react-redux documentation: react-redux
react component defintion: https://reactjs.org/docs/react-component.html
also you have to name react component with starting character Capital.
I recommend instead of mapdispatch or mapstate use useDispatch and useSelector from react-redux for functional components
import {useSelector,useDispatch} from 'react-redux;
const dispatch=useDispatch();
const stateSelector=useSelector(state=>({
your states
}));

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.

Why does my Container component get state object from other reducers?

I have two container components in my app. I just started react, redux and thought that a container component gets state object from its respective reducer so that it can be mapped to the component's props.
I console logged the state sent by the reducer and saw that the state object contains the data sent by the other reducer in my app as well. I thought it's supposed to get data only from its corresponding reducer because the reducer is what sends data to a container component.
I don't know if I'm doing something wrong or if that's how it is.
The reducer:
export default function(state=null, action){
console.log(action);
switch(action.type){
case 'BOOK_SELECTED':
return action.payload
}
return state;
}
The container component:
function mapStateToProps(state){
console.log("state from reducer",state); //contains data from the other reducer as well
return {
bookDetail : state.bookDetail
}
}
export default connect(mapStateToProps)(BookDetail);
Containers and reducers are not "connected". That's why mapStateToProps exists.
Redux doesn't know what parts of the state a container needs, so, in mapStateToProps, all the state is provided and each one takes whatever it needs. In your case, your container needs state.bookDetail.
Hope it helps.
If you check react-redux documentation, you'll see that mapStateToProps receives the full store data as first argument. It's your job to slice that to the props needed by the connected component, as you are already doing. Your component will only receive bookDetail as a prop from the state.
So your code is fine, although I would rewrite that switch statement in the reducer with a default clause.

Save local state in React

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);
}
}

Categories

Resources