Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WRO-9096: Update react-redux related documentation #3081

Merged
merged 26 commits into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 35 additions & 32 deletions docs/redux/redux-async.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,25 @@ render(
Store is configured to accept thunk middleware

```js
import {createStore, applyMiddleware} from 'redux';
import {configureStore, createSlice} from '@reduxjs/toolkit';
import thunkMiddleware from 'redux-thunk';
import systemSettingsReducer from '../reducers';
import rootSlice from '../reducers';

export default function configureStore (initialState) {
const store = createStore(
systemSettingsReducer,


export default function configureAppStore (initialState) {
return configureStore({
reducer: rootSlice.reducer,
initialState,
applyMiddleware(thunkMiddleware) // lets us dispatch functions
);
return store;
middleware: [thunkMiddleware]
vladut-clapou-lgp marked this conversation as resolved.
Show resolved Hide resolved
});
}
```

Here we create a thunk action creator which returns a function instead of a plain object. It is also possible to dispatch an action or request at the beginning.

```js
import LS2Request from '@enact/webos/LS2Request';
function receiveSystemSettings (res) {
return {
type: 'RECEIVE_SYSTEM_SETTINGS',
payload: res
};
}
// function returning function!
export const getSystemSettings = params => dispatch => {
// possible to dispatch an action at the start of fetching
Expand All @@ -98,35 +93,43 @@ export const getSystemSettings = params => dispatch => {
Reducer receives a payload and creates a new state.

```js
export default function systemSettingsReducer (state = {}, action) {
switch (action.type) {
case 'RECEIVE_SYSTEM_SETTINGS':
const rootSlice = createSlice({
name: 'systemReducer',
initialState: {},
reducers: {
receiveSystemSettings: (state, action) => {
return Object.assign({}, state, action.payload.settings);
},
updateSystemSettings: (state, action) => {
return Object.assign({}, state, action.payload.settings);
default:
return state;
}
}
}
});

export const {receiveSystemSettings, updateSystemSettings} = rootSlice.actions;
export default rootSlice;
```

Connected container dispatches ``getSystemSettings`` on componentDidMount and renders a ``pictureMode`` prop that's been hooked up with a redux store.
Connected container dispatches ``getSystemSettings`` on component mout and renders a ``pictureMode`` prop that's been hooked up with a redux store.
vladut-clapou-lgp marked this conversation as resolved.
Show resolved Hide resolved

```js
import {Component} from 'react';
import {connect} from 'react-redux';
import {getSystemSettings} from '../actions';
import {useDispatch, useSelector} from 'react-redux';
import {getSystemSettings} from '../reducers';

const App = () => {
const pictureMode = useSelector(store => store.pictureMode);
const dispatch = useDispatch();

class App extends Component {
componentDidMount () {
this.props.dispatch(getSystemSettings({
useEffect(() => {
dispatch(getSystemSettings({
category: 'picture',
key: 'pictureMode',
subscribe: true
}));
}
render () {
return <p>{this.props.pictureMode}</p>;
}
}, []);

return <p>{pictureMode}</p>;
}

export default connect()(App);
export default App;
```
178 changes: 92 additions & 86 deletions docs/redux/redux-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ If you want to change the state, you have to **dispatch** an **[action](https://

To describe state mutations you have to write a function that takes the previous state of the app and the action being dispatched, then returns the next state of the app. This function is called the [Reducer](https://redux.js.org/understanding/thinking-in-redux/glossary#reducer).

Please find more information [here](https://redux.js.org/understanding/thinking-in-redux/three-principles).

#### What You Need

* Actions - what your app can do
Expand Down Expand Up @@ -59,6 +61,7 @@ An action is just a POJO (unless you use middleware as described) that contains
}
```


##### Reducers

A reducing function (reducer) returns the next state tree, given the current state tree and an action to handle. Reducers are run in response to actions that are made against the store. Reducing functions should be pure (given the same arguments, they should always return the same value) and perform no side effects (API calls, routing transitions, etc.) or call other non-pure functions (i.e. `Date.now()` or `Math.random()`).
Expand Down Expand Up @@ -99,6 +102,7 @@ const todo = (state, action) => {
}
```


##### Store

The store is where the state tree is stored. It is configured with a reducer. It can also be given an optional initial state tree and optional enhancer functions. We use the enhancer functions to be able to handle async actions through `applyMiddleware` (provided by Redux). The store is created via the [`createStore()`](https://redux.js.org/api/createstore) method of the Redux module. The store allows access to the state via [`getState()`](https://redux.js.org/api/store#getstate) method. It only allows updates to the state by using the [`dispatch()`](https://redux.js.org/api/store#dispatchaction) method (i.e. `dispatch(action)`). It can register listeners via [`subscribe(listener)`](https://redux.js.org/api/store#subscribelistener) and handles unregistering of listeners with the function returned by `subscribe()`.
Expand Down Expand Up @@ -150,10 +154,10 @@ Live demo: [http://jsbin.com/keyahus/edit?html,js,output](http://jsbin.com/keyah
#### React

```js
import {createRoot} from 'react-dom/client';
import {createStore} from 'redux';
import PropTypes from 'prop-types';
import {Component} from 'react';
import ReactDOM from 'react-dom';
import { useDispatch, useSelector } from 'react-redux';

// reducer
function counter (state = 0, action) {
switch (action.type) {
Expand All @@ -163,111 +167,113 @@ function counter (state = 0, action) {
return state;
}
}
vladut-clapou-lgp marked this conversation as resolved.
Show resolved Hide resolved
class Counter extends Component {
render () {
const {value, onIncrement} = this.props;
return (
<p>
Clicked: {value} times
{' '}
<button onClick={onIncrement}>
+
</button>
</p>
);
}
}
Counter.propTypes = {
onIncrement: PropTypes.func.isRequired,
value: PropTypes.number.isRequired
const Counter = () => {
const value = useSelector((state) => state.counter);
const dispatch = useDispatch();

const incrementHandler = () => {
dispatch({ type: 'INCREMENT' });
};

return (
<p>
Clicked: {value} times <button onClick={incrementHandler}>+</button>
</p>
);
};

const store = createStore(counter);
function render () {
ReactDOM.render(
<Counter
value={store.getState()}
onIncrement={() => store.dispatch({type: 'INCREMENT'})}
/>,
document.getElementById('root')
);
const render = () => {
const appElement = (
<Counter/>
);
createRoot(document.getElementById('root')).render(appElement);
}
render();
store.subscribe(render);
```

Live Demo: [http://jsbin.com/nemofa/edit?html,js,output](http://jsbin.com/nemofa/edit?html,js,output)
Live Demo: [https://stackblitz.com/edit/react-u1qccn?file=src%2Fstore%2Findex.js,src%2Findex.js,src%2FApp.js](https://stackblitz.com/edit/react-u1qccn?file=src%2Fstore%2Findex.js,src%2Findex.js,src%2FApp.js)

### Redux and React
### Redux Toolkit

As mentioned above Redux can be used without React. React bindings for redux is available from [react-redux](https://github.com/reduxjs/react-redux), which is a generic library that connects React components to a Redux store. More on how to use it is available [here](https://redux.js.org/tutorials/fundamentals/part-5-ui-react).
The patterns shown above, unfortunately, require lots of verbose and repetitive code. To make it easier to write Redux applications in general, Redux team introduced [Redux Toolkit](https://redux-toolkit.js.org/).
MikyungKim marked this conversation as resolved.
Show resolved Hide resolved
It is the official recommended approach for writing Redux logic as of now.

It includes utilities that help simplify many common use cases, including store setup, creating reducers and writing immutable update logic, and even creating entire "slices" of state at once. It also includes the most widely used Redux addons, like Redux Thunk for async logic and Reselect for writing selector functions, so that you can use them right away.

#### Presentational and Container Components
Redux Toolkit provides two key APIs that simplify the most common things you do in every Redux app.

There's a simple and very useful pattern in React apps called **presentational and container components**. The idea is to separate concerns of **_how components should look_** and _**what components should do**_. By following this pattern, you get better separation of concerns, better reusability, and you can handle UI more easily. See [here](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.6j9fz9g5j) to find out more about it. (STRONGLY SUGGESTED!)
* [configureStore](https://redux-toolkit.js.org/api/configureStore) sets up a well-configured Redux store with a sing function call, including combining reducers, adding the thunk middleware, and setting up the Redux DevTools integration.

Redux embraces the separation of presentational and container components idea and it's easy to do with `react-redux`. Essentially, presentational components don't know about Redux. They get their data through standard React props and emit changed data by invoking callbacks passed in through props. Container components are where the hook-up to Redux and app state is done and where Redux actions are dispatched. In other words, you would normally create components as a presentational component, and when you find you need to hook up to data, you would then need to create a container component for it.
* [createSlice](https://redux-toolkit.js.org/api/createSlice) helps you write reducers that use the [Immer](https://immerjs.github.io/immer) library to enable writing immutable updates using "mutating" JS syntax like `state.value = 123`, with no spreads needed. It also automatically generates action creator functions for each reducer, and generates action type strings internally based on your reducer's names.

Please see [here](https://redux.js.org/introduction/why-rtk-is-redux-today) to find out more about Redux Toolkit.
Also, see [here](https://react-redux.js.org/tutorials/quick-start) for a greate tutorial.

### Redux and React

As mentioned above Redux can be used without React. React bindings for redux is available from [react-redux](https://github.com/reduxjs/react-redux), which is a generic library that connects React components to a Redux store. More on how to use it is available [here](https://redux.js.org/tutorials/fundamentals/part-5-ui-react).

#### What `react-redux` does

`react-redux` allows you to specify how react components get data from the redux store and how they behave by specifying props and the actions to dispatch. We use `react-redux` module's [connect()](https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md) method to connect the relevant container component to its presentational one.
`react-redux` allows you to specify how react components get data from the redux store and how they behave by calling its own custom hooks. We use `react-redux` module's [useSelector()](https://github.com/reduxjs/react-redux/blob/master/docs/api/hooks.md#useselector) hook to let our React components read data from the Redux store.

Back in the days, we had [connect()](https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md) and `mapStateToProps()` method to get data from the redux store. They are also available but we recommend using `useSelectors()` hook instead.

An optional `mapStateToProps()` method will map a key of the state tree to the connected presentational component's props (i.e. when you do `connect(mapStateToProps)(PresentationalComponent)`). Container components can also dispatch actions by using the `mapDispatchToProps()` method. It allows you to pass callback props to the presentational component.
`useSelector()` accepts a single function, which we call a selector function. A selector is a function that takes the entire Redux store state as its argument, reads some value from the state, and returns that result.

Container components need access to the Redux store so they can subscribe to it. This can be cumbersome as your number of components grows and you have to manually pass store around. `react-redux` incorporates [context](https://reactjs.org/docs/context.html) in React and provides a [`<Provider />`](https://github.com/reduxjs/react-redux/blob/master/docs/api/Provider.md) component to make store available to all container components without passings stores around by hand. You only need to use it once at the `render()` of root component.
Also, we use [useDispatch()](https://github.com/reduxjs/react-redux/blob/master/docs/api/hooks.md#usedispatch) hook to dispatch actions. It gives you the store's `dispatch` method as its result so that you can call it with some `action` to dispatch.

#### Example
Our components need access to the Redux store so they can subscribe to it. This can be cumbersome as your number of components grows and you have to manually pass store around. `react-redux` incorporates [context](https://reactjs.org/docs/context.html) in React and provides a [`<Provider />`](https://github.com/reduxjs/react-redux/blob/master/docs/api/Provider.md) component to make store available to all components without passings stores around by hand. You only need to use it once at the `render()` of root component.

#### example
vladut-clapou-lgp marked this conversation as resolved.
Show resolved Hide resolved

```js
import {createRoot} from 'react-dom/client';
vladut-clapou-lgp marked this conversation as resolved.
Show resolved Hide resolved
import {createStore} from 'redux';
vladut-clapou-lgp marked this conversation as resolved.
Show resolved Hide resolved
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Component} from 'react';
import {render} from 'react-dom';
import {Provider, useDispatch, useSelector} from 'react-redux';

// reducer
function counter (state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
}
// presentational counter component
class Counter extends Component {
render () {
const {value, onIncrement} = this.props;
return (
<p>
Clicked: {value} times
{' '}
<button onClick={onIncrement}>
+
</button>
</p>
);
}
}
const mapStateToProps = (state) => {
return {value: state};
const counterReducer = (state = { counter: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { counter: state.counter + 1 };
default:
return state;
}
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: () => dispatch({type: 'INCREMENT'})
};

//store
const store = createStore(counterReducer);
vladut-clapou-lgp marked this conversation as resolved.
Show resolved Hide resolved

// presentational counter component
const Counter = () => {
const value = useSelector((state) => state.counter);
const dispatch = useDispatch();

const incrementHandler = () => {
dispatch({ type: 'INCREMENT' });
};

return (
<p>
Clicked: {value} times <button onClick={incrementHandler}>+</button>
</p>
);
};
// creates a connected container component with `connect`
const EnhancedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);
const store = createStore(counter);
class App extends Component {
render () {
return (
<Provider store={store}>
<EnhancedCounter />
</Provider>
);
}

const App = () => {
return <Counter />;
}
render(<App />, document.getElementById('root'));

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);

root.render(
<Provider store={store}>
<App />
</Provider>
);
```

Live Demo: [http://jsbin.com/zukojok/1/edit?html,js,output](http://jsbin.com/zukojok/1/edit?html,js,output)
vladut-clapou-lgp marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -276,6 +282,6 @@ Live Demo: [http://jsbin.com/zukojok/1/edit?html,js,output](http://jsbin.com/zuk

[Official Redux documentation](http://redux.js.org/)

[Egghead tutorial - Getting Started with Redux](https://egghead.io/courses/getting-started-with-redux)
[Official React Redux documentation](https://react-redux.js.org/tutorials/quick-start)

[Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.6j9fz9g5j)
[Egghead tutorial - Getting Started with Redux](https://egghead.io/courses/getting-started-with-redux)