Redux - How to get data from store and post it - javascript

Newbie to Redux here, I have tried to follow a couple tutorials and I am not clear of how Redux actually works. It was mentioned that the store of Redux is to store the state of the whole tree. I have created and used actions, reducers, and store for my program and it works.
The question is, how do I retrieve what is in the store? Lets say after updating my component, how can I retrieve the value inside the component and to post it?
How can I know what changed in my dropdown list and to retrieve it?
Full code in Sandbox here https://codesandbox.io/s/elated-goldberg-1pogb
store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './RootReducer';
export default function configureStore() {
return createStore(
rootReducer,
applyMiddleware(thunk)
);
}
ProductsList.js
import React from "react";
import { connect } from "react-redux";
import { fetchProducts } from "./SimpleActions";
class ProductList extends React.Component {
constructor(props)
{
super(props);
this.state = {
selecteditems: '',
unitPrice: 0
}
}
componentDidMount() {
this.props.dispatch(fetchProducts());
}
componentDidUpdate(prevProps, prevState) {
if(prevState.selecteditems !== this.state.selecteditems)
{
this.setState((state, props) => ({
unitPrice: ((state.selecteditems * 1).toFixed(2))
}));
}
}
render() {
const { error, loading, products } = this.props;
if (error) {
return <div>Error! {error.message}</div>;
}
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<select
name="sel"
className="sel"
value={this.state.selecteditems}
onChange={(e) =>
this.setState({selecteditems: e.target.value})}
>
{products.map(item =>
<option key={item.productID} value={item.unitPrice}>
{item.itemName}
</option>
)}
</select>
<p>Unit Price: RM {this.state.unitPrice} </p>
</div>
);
}
}
const mapStateToProps = state => {
const products = state.productsReducer.items;
const loading = state.productsReducer.loading;
const error = state.productsReducer.error;
return {
products,
loading,
error,
}
};
export default connect(mapStateToProps)(ProductList);
SimpleAction.js
export function fetchProducts() {
return dispatch => {
dispatch(fetchProductsBegin());
return fetch('http://localhost:55959/api/products')
.then(handleErrors)
.then(res => res.json())
.then(results => {
dispatch(fetchProductsSuccess(results));
return results;
})
.catch(error => dispatch(fetchProductsFailure(error)));
};
}
function handleErrors(response) {
if(!response.ok) {
throw Error (response.statusText);
}
return response;
}
export const FETCHPRODUCTS_BEGIN = 'FETCHPRODUCTS_BEGIN';
export const FETCHPRODUCTS_SUCCESS = 'FETCHPRODUCTS_SUCCESS';
export const FETCHPRODUCTS_FAILURE = 'FETCHPRODCUTS_FAILURE';
export const fetchProductsBegin = () => ({
type: FETCHPRODUCTS_BEGIN
});
export const fetchProductsSuccess = products => ({
type: FETCHPRODUCTS_SUCCESS,
payload: {products}
});
export const fetchProductsFailure = error => ({
type: FETCHPRODUCTS_FAILURE,
payload: {error}
});
Thanks in advance!

You will need to pass your action handlers to connect function
connect(mapStateToProps,{actions})(ProductList).

how do I retrieve what is in the store? Lets say after updating my component, how can I retrieve the value inside the component and to post it?
if you want to see how is store change, you can add redux-logger to middleware to see that. when store change, it's likely a props change, you can handle this in function componentDidUpdate.
How can I know what changed in my dropdown list and to retrieve it?
values in dropdown is controlled by "const products = state.productsReducer.items;", productsReducer is controlled by actions you passed in dispatch like this: "this.props.dispatch(fetchProducts());".
I think you should add redux-logger to know more how to redux work, it show on console step by step. It will help you learn faster than you think :D

to retrieve it you forgot the selecteditems
const mapStateToProps = state => {
const products = state.productsReducer.items;
const loading = state.productsReducer.loading;
const error = state.productsReducer.error;
const selecteditems = state.prodcuts.selecteditems;
return {
products,
loading,
error,
selecteditems
};
};
To change it you should connect another function like
const mapDispatchToProps = dispatch => {
return {
onChangeDropdownSelection: (selected)=> dispatch(actions.setSelectedDropdown(selected))
}
}

Related

Using redux with react-hooks without the connect HOC

Currently, I'm using functional components with hooks but still dispatching my actions with the connect HOC.
I read through the documentation with useDispatch but I'm unsure how to incorporate it in my code. From the examples, they are passing the the action types and payloads inside the component. Would I have to move myOfferActions functions back to the component in order to useDispatch?
MyOffers component
import React, { useEffect } from "react";
import { connect, useSelector } from "react-redux";
import "./MyOffers.scss";
import MyOfferCard from "../../components/MyOfferCard/MyOfferCard";
import { fetchMyOffers } from "../../store/actions/myOffersActions";
const MyOffers = (props) => {
const myOffers = useSelector((state) => state.myOffers.myOffers);
useEffect(() => {
props.fetchMyOffers();
}, []);
return (
<div className="my-offers-main">
<h1>My offers</h1>
{myOffers && (
<div className="my-offer-container">
{myOffers.map((offer) => (
<MyOfferCard key={offer.id} offer={offer} />
))}
</div>
)}
</div>
);
};
export default connect(null, { fetchMyOffers })(MyOffers);
offerActions
export const fetchMyOffers = () => async (dispatch) => {
const userId = localStorage.getItem("userId");
try {
const result = await axiosWithAuth().get(`/offers/${userId}`);
let updatedData = result.data.map((offer) => {
//doing some stuff
};
});
dispatch(updateAction(FETCH_MY_OFFERS, updatedData));
} catch (error) {
console.log(error);
}
};
offerReducer
import * as types from "../actions/myOffersActions";
const initialState = {
offerForm: {},
myOffers: [],
};
function myOffersReducer(state = initialState, action) {
switch (action.type) {
case types.FETCH_MY_OFFERS:
return {
...state,
myOffers: action.payload,
};
default:
return state;
}
}
export default myOffersReducer;
I don't think you need connect when using the redux hooks.
You just need to call useDispatch like:
const dispatch = useDispatch();
And use the function by providing the object identifying the action:
dispatch({ type: 'SOME_ACTION', payload: 'my payload'});
It should be working with redux-thunk too (I guess this is what you're using): dispatch(fetchMyOffers())

Add value to React context from child components

I am trying to use React context as a state manager in my React Native app.
Here's the context:
import React, { createContext, useState, useEffect } from "react";
import axios from "axios";
export const GlobalContext = createContext();
export const Provider = ({ children }) => {
const [tracksList, setTracksList] = useState([
{
track_list: []
}
]);
useEffect(() => {
axios
.get(
`https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?page=1&page_size=10&country=us&f_has_lyrics=1&apikey=${
process.env.REACT_APP_MM_KEY
}`
)
.then(res => {
setTracksList([
{
track_list: res.data.message.body.track_list
}
]);
})
.catch(err => console.log(err));
}, []);
return (
<GlobalContext.Provider value={[tracksList, setTracksList]}>
{children}
</GlobalContext.Provider>
);
};
export const Consumer = GlobalContext.Consumer;
Child component. Here I'd like to make an API call to get users and set this users field to global context. I can get context value from consumer, but how to set the new one?
import React, { useContext } from "react";
import { GlobalContext } from "../../context/context";
const Demo = () => {
const contextValue = useContext(GlobalContext);
console.log(contextValue, "Context outside from JSX");
return <div>Content</div>;
};
export default Demo;
So, is it possible to add new value to React context from every child component, like in Redux? Thanks in advance!
You could use the useReducer effect to achieve Redux reducers:
// Create context
export const ApiContext = React.createContext();
// Create some reducer function
const reducer = (state, action) => {
if (action.type === 'some action name') {
return {
...state,
report: action.payload,
};
}
return state;
};
// Overwrite a context provider
const Provider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, {});
return (
<ApiContext.Provider
value={{
...state,
dispatch,
}}
>
{children}
</ApiContext.Provider>
);
};
Then you could use in components as follows:
const Component = () => {
const { dispatch, report } = React.useContext(ApiContext);
React.useEffect(() => {
const asyncPost = async () => {
const response = await fetch('some endpoint', {
method: 'POST',
});
const payload = await response.json();
// This will call a reducer action and update a state
dispatch({
type: 'some action name',
payload,
});
}
}, []);
...
};
So when Component is mounted, the state would be an empty object. Then when you update the state using the some action name action, the state becomes { report: some data from fetch }.

redux connect mapStateToProps not called when action is dispatched

Solution(updated):
I thought any action would cause react-redux-connect to call the mapState functions but when an action doesn't change anything then this is not the case.
I have a localStorage module that dispatches actions but don't change state, instead thy will write to localStorage. The module has selectors that are used in the containers but they won't get called until the state actually changes so the UI would only show correctly after another action was dispatched that would change the state.
Problem
When I put the store on window (window.store=store), add a console.log in the mapStateToProps, then in the console I dispatch an action: store.dispatch({type:'some action'}) then the console.log of the mapStateToProps does not show.
I do memoize the result but the mapStateToProps should be called see here
Full code is here and running example here (you can open a console clicking on 'console' link in the right bottom of the screen).
package.json
store.js:
import { createStore } from 'redux';
export default (initialState, reducer) => {
const store = createStore(
reducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
window.store = store;
return store;
};
app.js
import React from 'react';
import { connect } from 'react-redux';
import './App.css';
import createStore from './store';
import { Provider } from 'react-redux';
import initCounter from './components/Counter';
import {
createWrapper,
memoize,
} from './components/#common';
const COUNTER = 'COUNTER';
const selectCounterState = state => state.myCounter;
const counter = initCounter({
actionWrapper: createWrapper(COUNTER, 'counter1'),
selectors: { myState: selectCounterState },
connect,
memoize,
});
const initialState = {
myCounter: counter.initialState,
};
const reducer = (state = initialState, action) => {
if (action.emittedBy === COUNTER) {
return {
...state,
myCounter: counter.reducer(
selectCounterState(state),
action.payload
),
};
}
return state;
};
const store = createStore(initialState, reducer);
const Counter = counter.container;
const App = () => (
<Provider store={store}>
<Counter id="counter1" parentId={[]} />
</Provider>
);
export default App;
component/Counter/index:
import component from './component';
const INCREASE = 'INCREASE';
const reducer = (state, action) => {
if (action.type === INCREASE) {
return { ...state, count: state.count + 1 };
}
return state;
};
const makeState = memoize =>
memoize((id, parentId, { count }) => ({
id: parentId.concat(id),
parentId,
count,
}));
const mapStateToProps = ({ myState }, memoize) => () => {
const newState = makeState(memoize);
return (state, ownProps) =>
console.log('in map state to props', new Date()) ||
newState(
ownProps.id,
ownProps.parentId,
myState(state)
);
};
export default ({
actionWrapper,
selectors,
connect,
memoize,
}) => {
const actions = {
increase: ({ id }) =>
actionWrapper({
type: INCREASE,
id,
}),
};
const container = connect(
mapStateToProps(selectors, memoize),
actions
)(component);
return {
container,
reducer,
initialState: { count: 0 },
};
};
components/counter/component.js:
import React from 'react';
export default props => (
<div>
<button onClick={() => props.increase(props)}>
add
</button>
{props.count}
</div>
);
This problem was caused because I had a localStorage module that did dispatch actions but did not change the state, instead it would write to localStorage.
The module had selectors that would get the right data and the containers would use them to build the correct state but since the dispatched action did not change the state in the redux store react-redux would skip calling my mapState functions (probably memoizing state in Provider).
The solution is to let the root reducer return a new state reference {...state} so any action would cause the mapState functions to be called.
Your example codepen works just fine, you just have to trigger an action that gets past your top level guard and is of the expected structure, as to not cause any followup errors:
Post this into the console of your codepen:
store.dispatch({emittedBy: "COUNTER", type: "COUNTER -> INCREASE", id: "counter1", payload: {type: "INCREASE", id: ["counter1"]}})

Cannot access data request Axios, React-Redux

I am trying to make an API request using Axios in React-Redux environment. On the console everything seems to be fine, however if I try to access any of the data I either get undefined or empty array.
This is my component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { discoverMovie } from '../actions'
//Home component
class Home extends Component {
//make request before the render method is invoked
componentWillMount(){
this.props.discoverMovie();
}
//render
render() {
console.log('movie res ',this.props.movies.movies.res);
console.log('movie ',this.props.movies);
return (
<div>
Home
movie
</div>
)
}
};
const mapStateToProps = (state) => {
return{
movies : state.movies
}
}
export default connect(mapStateToProps, { discoverMovie })(Home);
This is my action
import { DISCOVER_MOVIE } from '../constants';
import axios from 'axios';
//fetch movie
const fetchMovie = () => {
const url = 'https://api.themoviedb.org/3/discover/movie?year=2018&primary_release_year=2018&page=1&include_video=false&include_adult=false&sort_by=vote_average.desc&language=en-US&api_key=72049b7019c79f226fad8eec6e1ee889';
let result = {
res : [],
status : ''
};
//make a get request to get the movies
axios.get(url).
then((res) => {
result.res = res.data.results;
result.status = res.status;
return result;
});
//return the result after the request
return result;
}
//main action
const discoverMovie = () =>{
const result = fetchMovie();
//return the action
return {
type : DISCOVER_MOVIE,
payload : result
}
}
export default discoverMovie;
This is the reducer
import { DISCOVER_MOVIE } from '../constants';
//initial state
const initialState = {
movies : {},
query : '',
};
//export module
export default (state = initialState, actions) =>{
switch(actions.type){
case DISCOVER_MOVIE :
return {
...state,
movies : actions.payload
};
default :
return state;
}
}
this is the log that I get from the console
as you can see if I log the entire object I see all data, however if go deep and try to access the result I either get an undefined or an empty array and using redux-dev-tools I noticed that the state does not contain any value.
I read on internet including this portal similar issue but could not find any solution for my issue.
Solution
From official docs:
You may use a dedicated status field in your actions
Basically you need to dispatch action for each state to make an async action to work properly.
const searchQuery = () => {
return dispatch => {
dispatch({
type : 'START',
})
//make a get request to get the movies
axios.get(url)
.then((res) => {
dispatch({type : 'PASS', payload : res.data});
})
.catch((err) => {
dispatch({type : 'FAILED', payload : res.error});
});
}
With redux-thunk it's pretty simple to set up. You just have to make some changes to your store. Out the box, I'm pretty sure redux isn't the most friendly with async and that's why thunk is there.
import { ..., applyMiddleware } from "redux";
import thunk from "redux-thunk";
...
const store = createStore(reducer, applyMiddleware(thunk));
...
Then in your action you'll need to return dispatch which will handle your logic for your axios call.
const fetchMovie = () => {
return dispatch => {
const url = //Your url string here;
axios.get(url).then(res => {
dispatch(discoverMovie(res.data.results, res.status);
}).catch(err => {
//handle error if you want
});
};
};
export const discoverMovie = (results, status) => {
return {
type: DISCOVER_MOVIE,
payload: results,
status: status
};
};
Your reducer looks fine, though with the way my code is typed you'll have status separately. You can combine them into it's own object before returning in discoverMovie, if you need status with the results.
This is my first answer on stack so let me know if I can clarify anything better!

React Redux - Actions must be plain objects. Use custom middleware for async actions

I try to deal with ajax data using axom in my learning react,redux project and I have no idea how to dispatch an action and set the state inside a component
In component will mount
componentWillMount(){
this.props.actions.addPerson();
}
Store
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers";
import thunk from "redux-thunk";
export default function configureStore() {
return createStore(rootReducer, applyMiddleware(thunk));
}
In Action :
import * as types from "./action-types";
import axios from "axios";
export const addPerson = person => {
var response = [];
axios
.get(`&&&&&&&&&&&`)
.then(res => {
response = res.data;
return {
type: types.ADD_PERSON,
response
};
});
};
In reducer
import * as types from "../actions/action-types";
export default (state = [], action) => {
console.log("action======>", action);
switch (action.type) {
case types.ADD_PERSON:
console.log("here in action", action);
return [...state, action.person];
default:
return state;
}
};
I am getting Actions must be plain objects. Use custom middleware for async actions.
You should use dispatch for async function. Take a look of the redux-thunk's documentation: https://github.com/gaearon/redux-thunk
In Action:
import * as types from "./action-types";
import axios from "axios";
export const startAddPerson = person => {
return (dispatch) => {
return axios
.get(`https://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => {
dispatch(addPersons(res.data));
});
}
};
export const addPersons = personList => {
return {
type: types.ADD_PERSON,
personList
};
}
In PersonComponent:
class Person extends Component {
constructor(props){
super(props);
}
componentWillMount() {
this.props.dispatch(startAddPerson())
}
render() {
return (
<div>
<h1>Person List</h1>
</div>
);
}
}
export default Redux.connect()(Person);
You need two actions here: postPerson and addPerson.
postPerson will perform the API request and addPerson will update the store:
const addPerson = person => {
return {
type: types.ADD_PERSON,
person,
}
}
const postPerson = () => {
return (dispatch, getState) => {
return axios.get(`http://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => dispatch(addPerson(res.data)))
}
}
in your component, call postPerson()
You use the redux-thunk library which gives you access to the "getState" and "dispatch" methods. I see that that has been added by Chenxi to your question. Run your async operation first within your action and then call "dispatch" with your simple action action creator, which will return the simple object that redux is looking for.
Here is what your async action creator and your simple action creator(broken out into two action creators) will look like:
export const addPersonAsync = (person) => {
return (dispatch) => {
var response = [];
axios
.get(`http://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => {
response = res.data;
dispatch(addPerson(response));
});
};
};
export const addPerson = (response) => ({
type: types.ADD_PERSON,
response
});
From your component, you'll now call the "addPersonAsync" action creator.

Categories

Resources