I searched a bit about this question but found very vague answers. In redux, we know that the state is stored as an object. But where is this state stored actually? Is it somehow saved as a file which can be accessed by us later on? What I know is that it does not store it in a cookie format or in the browser's local storage.
The state in Redux is stored in memory, in the Redux store.
This means that, if you refresh the page, that state gets wiped out.
You can imagine that store looking something like this:
function createStore(reducer, initialState) {
let state = initialState // <-- state is just stored in a variable that lives in memory
function getState() {
return state
}
function dispatch(action) {
state = reducer(state, action) // <-- state gets updated using the returned value from the reducer
return action
}
return {
getState,
dispatch
}
}
The state in redux is just a variable that persists in memory because it is referenced (via closure) by all redux functions.
Here's a simplified example of what is going on:
function example() {
let variableAvailableViaClosure = 0
function incrementTheClosureVariable() {
variableAvailableViaClosure += 1
}
function getTheClosureVariable() {
return variableAvailableViaClosure
}
return {
incrementTheClosureVariable,
getTheClosureVariable
}
}
let data = example()
// at this point example is finished
// but the functions it returned
// still have access to the (internal) variable via closure
console.log(
data.getTheClosureVariable() // 0
)
data.incrementTheClosureVariable()
console.log(
data.getTheClosureVariable() // 1
)
Furthermore, the statement
In redux, we know that the state is stored as an object.
isn't correct. State in redux can be any valid javascript value, not just an object. It just usually makes the most sense for it to be an object (or a special object like an array) because that allows for a more flexible data structure (but you could make the state just be a number for example, if you wanted to).
Check out the actual Redux implementation for more details.
If you want the state to persist in a cookie or localStorage, you would enhance the store such that, on top of updating the state in memory, it will save to your desired storage as well (and load from that storage when the store is initialized)
States are stored in redux-store. Redux Store is a global store which can be accessed anywhere/any components.
Let consider an example of getting Index of data using third party API. The following snippet uses componentWillMount which will trigger a fetch call using redux action.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchDataFromUrl } from '../actions/index.js';
class Indexdata extends Component {
constructor(props){
super(props);
this.state = {
text: ''
}
}
componentWillMount(){
let thisVal = this;
thisVal.props.fetchIndexofData()
}
componentWillReceiveProps(nextProps){
this.setstate({
text: nextProps.indexData.text
})
}
render(){
return(
<div>
<Navbar />
<h2 className="prescription-index-title">Index of Data</h2>
</div>
)
}
}
function mapStateToProps(state){
return{
indexData: state.fetchedData
}
}
function mapDisptachToProps(dispatch){
return {
fetchIndexofData: () => dispatch(fetchDataFromUrl(access_token))
};
};
export default connect(mapStateToProps, mapDisptachToProps)(IndexData);
The above snippet will fetch index of data using a redux action. The below code is a redux action,
export function fetchDataFromUrl(){
return(dispatch) => {
const base_url = "https://api_serving_url.com"
fetch(base_url, {
method: 'GET'
})
.then(response => response.json())
.then(data => {
dispatch({
type: "INDEX_DATA",
data: data
})
})
}
}
Redux action will dispatch data to reducer, where state will be initialized in redux store. The following code snippet is redux-reducer
export function fetchedData(state = [], action) {
switch(action.type) {
case "INDEX_DATA":
return action.data;
default:
return state;
}
}
State stored in redux store will be mapped using function mapStateToProps, implemented in the above component. Now you can access the state using props in the respective component. Lifecyclehook componentWillReceiveProps will be able to fetch the state stored redux store.
You can access the State by means of using store.getState() in any component.The only drawback of using reducer state, is that it will reset the state when you refresh the component/application. Go through Reducer Store , for more information.
Related
I am trying to write a simple todoList using vue.js and I want to save those todos into cookies before the vue instance is destroyed. But I find it weird that though I wrote callback in beforeDestory hook, the hook is never called.
I checked Vue documents and could not find any hint.
when I tried to
save those todos into cookies by adding callback to window.onbeforeunload and window.onunload, it works.
my code is like
computed: {
todos() {
return this.$store.getters.todos
},
...
},
beforeDestroy() {
myStorage.setTodos(this.todos)
}
todos is a array defined in store.js, which has been imported in main.js, like
import myStorage from '#/utils/storage'
...
const store = new Vuex.Store({
state: {
todos: myStorage.getTodos()
...
},
getters: {
todos: state => state.todos
}
and myStorage is defined as:
import Cookies from 'js-cookie'
const todoKey = 'todo'
const setTodos = (todos) => {
Cookies.set(todoKey, JSON.stringify(todos))
}
const getTodos = () => {
const todoString = Cookies.get(todoKey)
let result = []
if (todoString) {
const todoParsed = JSON.parse(todoString)
if (todoParsed instanceof Array) {
result = todoParsed
}
}
return result
}
export default {
setTodos: setTodos,
getTodos: getTodos
}
I am using vue 2.6.10, and my project is constructed by vue-cli3.
I develop this todolist using Chrome on Window 10.
I expect that after I close the window or after I refresh the window, the todolist can still fetch todo written previously from cookies. But the fact is that the beforeDestory hook is never called.
When you refresh the window, the component's beforeDestroy() is not called, because you are not programmatically destroying the component, but ending the entire browser session.
A better solution would simply to call myStorage.setTodos whenever the todos object in the component is mutated. You can do that by setting up a watcher for the computed property:
computed: {
todos() {
return this.$store.getters.todos
},
},
watch: {
todos() {
myStorage.setTodos(this.todos)
}
}
Altertively, you let the VueX store handle the storage. It is unclear from your question if you are mutating the todos state: if you are mutating it, you can also do myStorage.setTodos in the store. The actual component can be dumb in that sense, so that all it needs to do is to update the store.
I am using react-localize-redux for my multilingual application and MySQL to fetch data. One of my actions needs locale data to pass it to backend as an argument so that backend responds with proper data. But by the time locale is available, action gets called and application crashes, how can I resolve the issue?
Here is code:
import React, { Component } from 'react'
import RestaurantCard from './RestaurantCard';
import {Row} from 'react-bootstrap';
import { connect } from 'react-redux';
import {getAllRestaurants} from "../actions/restaurantActions";
import { withLocalize } from 'react-localize-redux';
class RestaurantCardColumns extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.props.getAllRestaurants(this.props.activeLanguage);
}
render() {
if(this.props.loading || this.props.restaurants === null){
return <p>Loading...</p>
} else {
return (
<Row>
<RestaurantCard data = {this.props.restaurants[0]}/>
<RestaurantCard data = {this.props.restaurants[1]}/>
<RestaurantCard data = {this.props.restaurants[2]}/>
<RestaurantCard data = {this.props.restaurants[3]}/>
</Row>)
}
}
}
const mapStateToProps = (state) =>{
return {
auth: state.auth,
errors: state.errors,
restaurants: state.restaurData.restaurants,
loading: state.restaurData.loading
}
}
export default connect(mapStateToProps, {getAllRestaurants})(withLocalize(RestaurantCardColumns));
My problem is in this particular line:
this.props.getAllRestaurants(this.props.activeLanguage);
When I debug I can see that activeLanguage is available in render() lifecycle.
How can I await for that property before calling getAllRestaurants
Check for availability of this.props.activeLanguage before fetching data. Trigger fetching data once activeLanguage is available. And finally ensure that fetching happening only once (if you need)
class RestaurantCardColumns extends Component {
constructor(props){
super(props);
this.didFetch = false; // store outside of state to not trigger rendering
}
componentDidMount(){
this.fetchAllRestaurants();
}
componentDidUpdate(prevProps) {
if (prevProps.activeLanguage !== this.props.activeLanguage) {
this.fetchAllRestaurants();
}
}
fetchAllRestaurants() {
if (!!this.props.activeLanguage && !this.didFetch) {
this.props.getAllRestaurants(this.props.activeLanguage);
this.didFetch = true;
}
}
Be aware that this approach is entirely relied on the component's existence, i.e. if the component is not in virtual DOM, the API call will not happen. You should consider trigger the call using a redux's middleware, like redux-thunk or redux-saga as other people in here suggest.
Use a store enhancer middleware like Thunk. You seem to be making an async request,and store enhancers enable you to make async calls and retrieve data from backend. Middlewares like Thunk stops default action dispatch, perform async requests ad call the dispatch to pass the action along with the updated payload to the reducer. Using proper async - await in the componentDidMount will handle this as well, but store enhancers actually handle that for you.
Here's an example:
async componentDidMount() {
await this.props.getAllRestaurants(this.props.activeLanguage);
}
ComponentDidMount should be an async function, and you should await for
getAllRestaurants to complete.
In addition to that, you should have a local state variable (e.g. IsLoading), that indicates that data is not ready. After the 'await
getAllRestaurants' statement, you set isLoading to falase.
Render will check this local state in order to display a spinner or the data itself, or an error message, if getAllRestaurants fails (in addition to checking isLoading, you should check the redux store, where you will store not only the data, but also a variable indicating whether getAllRestaurants succeeded or failed).
I am trying to show the data in redux way.
when you click advanced sports search button a drawer opens up in that when you click search attributes it should render history data
so I created this method in the actions fetchHistorySportsDatafromURL
and then called this fetchHistorySportsDatafromURL method in the tabs of the drawer.
but its not displaying the data.
I debugged and found undefined for this const historySports = this.props.historySports;
console.log("historySports--->", historySports);
I am using mapDispatchToProps but still I am not successful.
I am trying to display my data here reading the data from api{historySports}
all my render code is in this file sports-advanced-search.js
providing my code snippet and sandbox below
https://codesandbox.io/s/5x02vjjlqp
actions
export const fetchHistorySportsDatafromURL = data => {
return dispatch => {
if (data != undefined) {
return axios({
method: "get",
url: "https://jsonplaceholder.typicode.com/comments",
data: data,
config: { headers: { "Content-Type": "application/json" } }
})
.then(function(response) {
console.log("fetchSportsHistoryData--->", response);
dispatch(fetchSportsHistoryData(response.data));
})
.catch(function(response) {
dispatch(fetchSportsHistoryData([]));
});
}
};
};
render code
getHistoryData = values => {
this.props.fetchHistorySportsDatafromURL();
};
render() {
const { classes } = this.props;
const { value } = this.state;
const Sports = this.props.Sports;
const historySports = this.props.historySports;
console.log("historySports--->", historySports);
console.log("this.props--->", this.props);
console.log("this.state--->", this.state);
}
const mapDispatchToProps = dispatch => {
return {
fetchHistorySportsDatafromURL: () => {
dispatch(fetchHistorySportsDatafromURL());
}
};
};
export default withStyles(styles)(
connect(
null,
mapDispatchToProps
)(ScrollableTabsButtonAuto)
);
There are several problems here. Without fixing them for you, here's what I think is confusing you...
State and props are two different things. You can use Redux to mapStateToProps for easy use in your component. You may want to do that once you successfully update the Redux state.
Redux state and component-level state are also two different things. The this.state that is defined in the constructor is that component's state. You can can't just add another state object and expect setState to update it. Your state object is not bound to the class.
To update the Redux state, your idea to dispatch(fetchSportsHistoryData(response.data) from the response is on the right track. Now you need for that action to return the data to the state. You need to make sure you have a reducer listening for FETCH_HISTORY_Sports, which responds by updating the Redux state.
Now if you have that part together, you can now use mapStateToProps in your component and then you can access it with this.props.historySports.
The title is wordy, however a short / simple example will go a long ways in explaining my question. I have the following start to a component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchGames } from '../../path-to-action';
class TeamsApp extends Component {
constructor(props) {
super(props);
this.state = {
oldGames: [],
newGames: []
};
}
componentDidMount() {
this.props.dispatch(fetchGames('1617'));
this.setState({ oldGames: this.props.teamGameData });
this.props.dispatch(fetchGames('1718'));
this.setState({ newGames: this.props.teamGameData });
}
...
...
}
function mapStateToProps(reduxState) {
return {
teamGameData: reduxState.GamesReducer.sportsData
};
}
export default connect(mapStateToProps)(TeamsApp);
I would like the action / reducer that corresponds with fetchGames() and gamesReducer to be called twice when the component mounts. This action / reducer grabs some sports data, and I am trying to grab data for two separate seasons (the '1617' season and the '1718' season). The fetchGames() is built correctly to handle the season parameter.
With the current setup, the states aren't being set, and my linter is throwing an error Do not use setState in componentDidMount.
Can I pass a callback to this.props.dispatch that takes the results of the fetchGames() (the teamGameData prop), and sets the oldGames / newGames states equal to this object?
Any help with this is appreciated!
Edit: if i simply remove the this.setState()'s, then my teamGameData prop simply gets overridden with the second this.props.dispatch() call...
Edit 2: I'm not 100% sure at all if having the 2 state variables (oldGames, newGames) is the best approach. I just need to call this.props.dispatch(fetchGames('seasonid')) twice when the component loads, and have the results as two separate objects that the rest of the component can use.
Edit 3: I have the following part of my action:
export const fetchSportsDataSuccess = (sportsData, season) => ({
type: FETCH_NBA_TEAM_GAME_SUCCESS,
payload: { sportsData, season }
});
and the following case in my reducer:
case FETCH_NBA_TEAM_GAME_SUCCESS:
console.log('payload', action.payload);
return {
...state,
loading: false,
sportsData: action.payload.sportsData
};
and the console.log() looks like this now:
payload
{ sportsData: Array(2624), season: "1718" }
but i am not sure how to use the season ID to create a key in the return with this season's data....
Edit 4: found solution to edit 3 - Use a variable as an object key in reducer - thanks all for help on this, should be able to take it from here!
Copying data from the redux store to one's component state is an anti-pattern
Instead, you should modify your redux store, for example using an object to store data, so you'll be able to store datas for multiples seasons :
sportsData: {
'1617': { ... },
'1718': { ... },
}
This way you'll be able to fetch both seasons in the same time :
componentDidMount() {
const seasons = ['1718', '1617'];
const promises = seasons.map(fetchGames);
Promise.all(promises).catch(…);
}
And connect them both :
// you can use props here too
const mapStateToProps = (reduxState, props) => ({
// hardcoded like you did
oldGames: reduxState.GamesReducer.sportsData['1617'],
// or using some props value, why not
newGames: reduxState.GamesReducer.sportsData[props.newSeason],
};
Or connect the store as usual and go for the keys:
const mapStateToProps = (reduxState, props) => ({
games: reduxState.GamesReducer.sportsData,
};
…
render() {
const oldGame = this.props.games[1718];
const newGame = this.props.games[1718];
…
}
Redux is you single source of truth, always find a way to put everything you need in Redux instead of copying data in components
My app has a user control panel and when the page is loaded it fetch data from the server using Redux.
In construction the component create an initial state like:
const { profile } = this.props;
this.state = {
prop1: profile.prop1 || '',
prop2: profile.prop2 || '',
nested: {
nestedProp1: profile.nested.nestedProp1 || '',
}
...etc...
}
On componentWillMount I have this:
componentWillMount() {
const { user, getProfile } = this.props;
if (user.profile_id) {
getProfile(user.profile_id);
}
}
What I don't understand are 2 things:
Is the approach correct? I'm using state to handle form inputs.
How can I update the state when fetched? There are plenty of properties in this profile object and I was wondering to update all the states in a very simple way, and not one by one...
1.If you are using redux,I think there is no need to use state to manage date, instead you can use props(redux) to handle all the date in your project.
Then,if you want to update the date, you should create action to update the globally unique date that stored in redux.
2.About how to handle the input, when the user have input value, you can create an action, create a copy with the initial state then update state with your input action.
function updateInput(state = initialState, action) {
switch (action.type) {
case 'INPUT':
return Object.assign({}, state, {
profile_id: action.profile
})
return state;
}
}
You could use static getDerivedStateFromProps(props, state) -method to update your component state from props whenever your Redux store updates. It has two params props and state.
class App extends React.Component {
// https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
static getDerivedStateFromProps(props, state){
// Compare and update state only if props differs from state
if(JSON.stringify(state) !== JSON.stringify(props.profile)){
return { ...props.profile }
}
// If it doesn't differ do not update state
return null
}
// Do an state initialization
state = { ...this.props.profile }
// Prefer componentDidMount -for doing fetch and updating component state
componentDidMount(){
const { user, getProfile } = this.props;
if (user.profile_id) {
getProfile(user.profile_id);
}
}
render(){
return (
<div className="App">
{/** Render content */}
</div>
);
}
}
Rest spread operator, what is used to fill up state is ES6 syntax.
If you use Babel you might need to add rest spread operator -plugin to your .babelrc -config. https://babeljs.io/docs/en/babel-plugin-proposal-object-rest-spread