I have a background saga that is watching the location and submits an action with the new location every time it changes and updates the location state. However, I have a component that I just want to grab the current state.user.location on a user interaction, but I don't want to use mapStateToProps because the component keeps re-rendering and I only want to grab the state when the user requests it and avoid constantly re-rendering.
I need the state in the background for other parts of the app.
reducer:
export const updateLocation = (state, { location }) => state.merge({ location })
selector:
getLocation : state => state.user.location && state.user.location.coords || null
Component:
class SearchScreen extends PureComponent {
render(){
const {location} = this.props
return(
<Button onPress={()=>searchWithCurrentLocation(location)} />
)}
}
const mapStateToProps = (state) => {
return {
location: UserSelectors.getLocation(state),
}
}
this is my current setup, but I'd like to not pass in the location and keep re-rendering as it's not used to display the component.
You can make the store accessible from anywhere like this:
file: storeProvider.js
var store = undefined;
export default {
init(configureStore){
store = configureStore();
},
getStore(){
return store;
}
};
file: App.js
import { createStore } from 'redux';
import rootReducer from './rootReducer';
import storeProvider from './storeProvider';
const configureStore = () => createStore(rootReducer);
storeProvider.init(configureStore);
const store = storeProvider.getStore();
const App = () =>
<Provider store={store} >
<Stuff/>
</Provider>
file: Component.js
import storeProvider from './storeProvider';
class SearchScreen extends PureComponent {
render(){
return(
<Button onPress={()=> searchWithCurrentLocation(UserSelectors.getLocation(storeProvider.getStore().getState())} />
)}
}
I don't think you need to be troubled about re-rendering if the data that has changed isn't directly affecting the components inside your render method. Let us remember that ReactDOM watches only those changed state and only update the DOM based on what's different. The render method maybe called but if nothing has really changed, it won't affect render performance at all.
After all that is what react is selling: reactive elements that updates changes if data changes in a smart way that it is optimized by updating only updated elements by using a virtual DOM which is ReactDOM.
Changes are first compared between the virtual DOM and the real DOM before updates are committed to the real DOM. You can read how ReactDOM does this here.
Related
According to the docs for the next-redux-wrapper:
When Next.js static site generator or server side rendering is involved, however, things start to get complicated as another store instance is needed on the server to render Redux-connected components.
I don't understand exactly where the complication arises.
SSR to SSG Redux State Persists During Page Navigation
I built a simple NextJs app that has one page that renders on the server, and another that is statically rendered. In production mode or in development mode, I can navigate between these two pages, and my redux state remains in-tact.
My understanding is that this isn't supposed to be the case, since a separate redux store must be created on the server and on the client; hence the creation of the next redux wrapper (also for injecting redux state into NextJs lifecycle methods, which I haven't needed).
I'm confused as to what mechanism is being used to persist the redux state between SSR and SSG pages. I'm using a simple redux provider, with a simple bit of state for testing.
Note: State persists between these two page components even without using redux-persist.
Redux Persist Also Works...
I have confirmed that redux persist also works without the next redux wrapper, saving to the local storage without issue, persisting successfully on page refresh.
Here's example code of my configuration:
page1.js (Statically rendered)
import Layout from './layout';
import { useSelector, useDispatch } from 'react-redux';
import { getTest, addBar } from '../redux/slices/topten';
export default function Page1() {
const dispatch = useDispatch();
const myState = useSelector(getTest);
return (
<Layout>
<h2>Statically Rendered</h2>
<div>
<span>
{ myState }
</span>
<button onClick={() => dispatch(addBar())}>
Add Bar to Foo
</button>
</div>
</Layout>
)
}
page2.js (Server-side rendered)
import Layout from './layout';
import { useSelector, useDispatch } from 'react-redux';
import { getTest, addBar } from '../redux/slices/topten';
export default function Page2() {
const dispatch = useDispatch();
const myState = useSelector(getTest);
return (
<Layout>
<h2>Server Side Rendered</h2>
<div>
<span>
{ myState }
</span>
<button onClick={() => dispatch(addBar())}>
Add Bar to Foo
</button>
</div>
</Layout>
)
}
export async function getServerSideProps() {
// Code for fetching data from external api.
return {
props: {
// Returned props
}
};
};
App.js
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from '../redux/store';
export default function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Component {...pageProps} />
</PersistGate>
</Provider>
)
}
topten.js (This is the redux slice with a simple reducer)
const initialState = {
topTen: {
test: "foo"
}
}
const topTenSlice = createSlice({
name: 'topten',
initialState,
reducers: {
addBar: (state) => {
state.topTen.test = "foobar";
}
},
});
// Export action.
export const { addBar } = topTenSlice.actions;
// Export slice reducer.
export default topTenSlice.reducer;
// Export state.
export const getTest = state => state.topten.topTen.test;
I left out the code for Layout and store, because it's redundant. It works without the wrapper... That's what I'm trying to understand.
I have verified here that state persists between the two components simply using the Provider HOC. According to the docs:
store is being replaced on navigation
I have also verified that state is persisting on page refresh using redux-persist.
I don't understand why I would need the wrapper unless I needed access to redux state within the NextJs lifecycle methods... Which can easily be avoided using redux useSelector hooks.
Any clarity on this would be appreciated.
I just started exploring a unit testing framework for my react application. I locked my choice at #testing-library/react instead of Enzyme as it is recommended by React and helps writing more robust tests. I'm also planning to use Jest for completeness.
But before I could proceed with React Testing Library, I'm stuck at few questions. Can someone please help?
For instance,
There is an Editor component which updates Redux store based on user input.
There is another component Preview which reads value directly from store.
There is a third component Container which contains both of the above components passing some props.
Questions:
How to test if user input in Editor component actually updates store?
How to test if Preview component can render value reading from store?
How to test if Container component passes props correctly to its children?
Container.jsx
import React from 'react';
import Editor from './Editor';
import Preview from './Preview';
const Container = ({editorType}) => {
return (
<div>
<Editor type={editorType} />
<Preview />
</div>
);
};
export default Container;
Editor.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {setName} from './actions';
const Editor = ({type}) => {
const name = useSelector(state => state.name);
const dispatch = useDispatch();
return (
<div>
<h1>Type: {type}</h1>
<input value={name} onChange={evt => dispatch(setName(evt.target.value))} />
</div>
);
};
export default Editor;
Preview.jsx
import React from 'react';
import { useSelector } from 'react-redux';
const Editor = ({type}) => {
const name = useSelector(state => state.name);
return (
<div>
<h1>Name: {name}</h1>
</div>
);
};
export default Preview;
Here is how I would do it :
How to test if user input in Editor component actually updates store?
Looking at your code, Editor does not update store, it does not call useDispatch. But assuming it does, using https://github.com/reduxjs/redux-mock-store I would setup a mocked store provider to the component in the test, then trigger user input and check the mocked store if the correct action have been dispatched, with the correct payload :
describe('<Editor />', () => {
let store: MockStoreEnhanced;
let rtl: RenderResult;
beforeEach(() => {
store = mockStore({ /* your store structure */ });
rtl = render(
<Provider store={store}>
<Editor type={...} />
</Provider>
);
});
test('dispatch action correctly', () => {
fireEvent.change(rtl.container.querySelector('input'), { target: {value: 'test' } });
// check actions that were dispatched by change event
// getActions return an array avec redux actions (type, payload)
expect(store.getActions()).toEqual(...);
}
}
Beside of this you should test your store reducer and action creators, to ensure that the action modifies the store correctly.
How to test if Preview component can render value reading from store?
Same principle, setup your test with a mocked store, init this store with relevant value, and just test that the component displays what's in the store :
describe('<Preview />', () => {
let store: MockStoreEnhanced;
let rtl: RenderResult;
beforeEach(() => {
store = mockStore({ name: 'This is name from store' });
rtl = render(
<Provider store={store}>
<Preview type={...} />
</Provider>
);
});
test('displays store content correctly', () => {
expect(rtl.getByText('This is name from store')).toBeTruthy();
}
}
How to test if Container component passes props correctly to its children?
Here you should test Container + Preview + Editor together, and find something visual to test (as a real user would perceive it), involving the props passed by Container to its children.
For example, if Preview displays props.type, which comes from Container editorType props :
describe('<Container />', () => {
// mocked store may not be necessary here, depending on your use case
let store: MockStoreEnhanced;
let rtl: RenderResult;
beforeEach(() => {
store = mockStore({ name: 'This is name from store' });
rtl = render(
<Provider store={store}>
<Container editorType={'toTest'} />
</Provider>
);
});
test('displays store content correctly', () => {
// check that 'toTest' exists in DOM because it is rendered by Preview component, a child of Container
expect(rtl.getByText('toTest')).toBeTruthy();
}
}
To sum it up, my approach would be :
Test actions and reducers alone (very simple to test, state machines, just plain Jest tests)
Test that components dispatch correct actions with correct payloads on correct user inputs
Mock store to setup relevant use cases (init store value) for components reading data from store
For the last 2 items I use https://github.com/reduxjs/redux-mock-store.
I was under the impression that when my Redux store gets updated (via dispatching an action to the reducer), my Provider should make the new store available to all it's child components. So when I connect a container component to the store by using mapStateToProps(), that component should re-render when it receives the new props that go along with the store being updated, without the need for componentWillReceiveProps(). Am I wrong in thinking that?
After several hours of reading docs and other stack overflow answers, something just isn't clicking for me. I'm pretty sure my reducer is working correctly and is returning a new object, my components have access to the store, and my initial state is rendering just fine. If anyone could give me a better idea about the flow of things, I would be forever grateful.
Basically, I have a header component that imports the store, uses Provider and renders a "FAQ" component:
import React from 'react';
import FAQ from './Faq';
import {Provider} from "react-redux";
import store from '../store/index'
class Header extends React.Component {
render() {
return (
<Provider store = {store}>
<FAQ />
</Provider>
)
}
}
export default Header;
The "FAQ" component is a container that is connected to the store via mapStateToProps(), it imports the "FAQTest" component, which is a presentational component that will get passed this.state.thisFood as props so that it can render the data from the store. There is also an "addFood" function that dispatches my action, which can be seen in mapDispatchToProps() at the bottom.
import React from 'react';
import {connect} from 'react-redux';
import FAQTest from './faqTest';
class FAQ extends React.Component {
constructor(props) {
super(props);
this.state = {
thisFood: props.breakfast
};
}
//adding this makes the component state update, but I think it should work without it
componentWillReceiveProps(nextProps){
this.setState({thisFood: nextProps.breakfast})
}
addFood = () => {
this.props.addFood();
}
render() {
return (
<React.Fragment>
<button onClick={this.addFood}> Add Food </button>
<FAQTest food = {this.state.thisFood} />
</React.Fragment>
)
}
}
const mapStateToProps = function(state) {
return {
breakfast: state.faq.breakfast
}
}
function mapDispatchToProps(dispatch) {
return {
addFood: () => dispatch({type: 'ADD_FOOD', food: 'Waffles'})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FAQ);
When I click the "Add Food" button, my store gets updated, and the props of my FAQ container component get updated, because of mapStateToProps(). I thought this would trigger my FAQ component to update its state, however the state does not get updated unless I use componentWillReceiveProps. Is this working as expected?
Just in case I'm doing something silly, here is my reducer:
const initialState = {
breakfast: ["eggs", "bacon"]
}
export default function faqReducer(state = initialState, action) {
switch (action.type) {
case "ADD_FOOD":
return Object.assign({}, state, {
breakfast: [...state.breakfast, action.food]
})
default:
return state;
}
}
Here is my root reducer with my combineReducers() function:
import { combineReducers } from "redux";
import faq from './faqReducer'
export default combineReducers({
faq: faq
});
The problem is that you're copying data from props to state, but only doing that when the component is mounted, and then expecting the state to somehow be updated when new props arrive.
Copying data from props to state is almost always the wrong approach. Please don't do that. Just use the value from props.
Two additional suggestions for improving the code:
Prefer using the "object shorthand" form of mapDispatch, rather than writing it as a function
We recommend using our new official Redux Starter Kit package as the standard way to write your Redux logic. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once.
some questions about React.js and Redux:
Can functional components also take advantage of the store and the states saved therein? e.g maybe in combination with React hooks like useEffect()?
In general, I can combine multiple reducers to one rootReducer and createStore(rootReducer) with it, and then pass it to a Provider Component that wraps my Component with it, this way, the store should be globally available in my whole app, correct?
For every component that want to use the store / states, do I always have to import the 2 methods mapStateToProps() and mapDispatchToProps() from react-redux for every Component and then connect them? Or can I also do this on some top-level component and make the usage of redux available in all my components globally, like in question 2) with the store provider?
last question: Can I still use the this.state property in my Components or use them in parallel as an addition (e.g for this Component isolated states) and then get the props from this state as usual with this.state.someState or is this not possible anymore when I already use Redux? And in the same way, can I still use / pass props to my components and read them from my Components as well, or is everything managed by state now only? (Or has the passing of props to my children nothing to do with Redux)?
1) Yes functional components can take advantage of the store. Its arguably much cleaner to read since props can be destructured right away.
const MyComponent = ({ auth }) => {
const [display, setDisplay] = useState(false)
useEffect(() => {
if(auth.user){
setDisplay(true)
}
}, [auth.user])
return(
<div>
{ display ? "Content": "Please sign in" }
</div>
)
}
const mapStateToProps = (state) => {
return{
auth: state.auth
}
}
export default connect(mapStateToProps)(MyComponent)
2) That is correct. You can also use combineReducers() which in some ways is cleaner to read.
import { createStore, combineReducers } from "redux"
import authReducer from "./reducers/authReducer"
import postReducer from "./reducers/postReducer"
const store = createStore(combineReducers({
auth: authReducer,
post: postReducer
}))
export default store
Then import store, wrap your App.js in a Provider and give it a prop of that store.
3) Generally, if you want your component to have direct access to the store it is a recognized pattern to use connect() in each one. Whether you decide to use mapStateToProps() or mapDispatchToProps() is entirely dependent on what that component needs to do. It does not required that you use both, you can just define one or the other in the connect().
import React, { useState } from "react"
import { addPost } from "/actions/postActions"
import { connect } from "react-redux"
const Form = ({ addPost }) => {
const [text, setText] = useState("")
const handleSubmit = (e) => {
e.preventDefault()
addPost(text)
}
return(
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)}/>
</form>
)
}
const mapDispatchToProps = (dispatch) => {
return {
addPost: (text) => dispatch(addPost(text))
}
}
export default connect(null, mapDispatchToProps)(Form)
4) You might have noticed by now that in the context of components, redux-state is stored as props. They are entirely different and isolated streams of data. So state remains untouched and controlled by the component itself. You can still freely use methods like this.state.dog even when your component is connected to the store. This is the isolation between component-state and redux-state.
import React, { useState } from "react"
import { connect } from "react-redux"
class MyDogs extends React.Component{
state = {
dog: "Tucker"
}
render(){
return(
<div>
Component State Value: {this.state.dog} //Tucker
Redux State Value: {this.props.dog} //Buddy
</div>
)
}
const mapStateToProps = (state) => {
return {
dog: state.dog
}
}
export default connect(mapStateToProps)(MyDogs)
How can i load data from AsyncStorage to redux store at the time of application's first page loading?
I tried to call a check function in componentWillMount.
componentWillMount(){
debugger;
this.check();
}
and in check i tried to take the value from AsyncStorage and store it to redux store object.
async check(){
await AsyncStorage.getItem('location').then((location) => {
this.props.location[0].city = location;
}
});
this.props.selectLocation(this.props.location);
}
As it is a Asynchronous function , I am unable to get the values and store it view selectLocation action of redux.
How to properly get the data and store it before component gets mounted ?
Update:
In my index.android.js i changed my store like this,
import { persistStore, autoRehydrate } from 'redux-persist'
import combineReducers from './src/Redux/reducers/combineReducers';
// const store = createStore(combineReducers);
const store = compose(autoRehydrate())(createStore)(combineReducers)
persistStore(store, {storage: AsyncStorage}, () => {
console.log('restored');
console.log(store);
})
export default class ReactNavigation extends Component {
render() {
return (
<Provider store = {store}>
<App />
</Provider>
);
}
}
Your component should listen to store through event listeners for changes and should show the data when the data is loaded into the store.
For the time being when the data is not there, you should show something like a spinner to let the user know that it's loading.
Ideally, you don't want to make componentWillMount wait for the data to be loaded.