Redux converting functions to objects - javascript

I was trying to import and use a function which returns a json object. This function uses some redux state variables in it.
But when I call this to return the json, it returns me something else instead of the said json.
My function is something like this:
import React from 'react';
import { connect } from 'react-redux';
const GenerateYaml = (props) => {
let json = {};
json = props.selected;
return json;
}
const mapStateToProps = (state) => {
return {
selected: state.stepBuilder.selected,
};
};
export default connect(mapStateToProps)(GenerateYaml);
When I tried to log what was returned, I got to know that it was an object of the connect function. I cant paste is here since its too big.
Any help is appreciated. Thanks!!

A jsfiddle/codesandbox would be very helpful here,
What I do in such cases is read the expected value in every line of the code, eg use a debugger!
Also GenerateYml should return a React component (html) not json.
import { connect } from 'react-redux';
const GenerateYaml = (props) => {
const json = { ...props.selected }
console.log(json);
return (
<div>{JSON.stringify(json)}</div>
)
}
const mapStateToProps = (state) => {
return {
selected: state.stepBuilder.selected,
};
};
export default connect(mapStateToProps)(GenerateYaml);

Related

Async function in UseEffect

I am trying to get data from AsyncStorage and eventually map this data to a list of buttons on my home screen. The data is saved to AsyncStorage from an API call that is made upon login.
In my async function, I am able to successfully retreive the data from AsyncStorage and parse it into JSON format, and then log it to the console. It looks like this:
{
1 : {title:"Timesheet",component_name:"Timesheet"}
2 : {title:"Personal Info",component_name:"PersonalInfo"}
3 : {title:"Employee Directory",component_name:"EmployeeListing"}
}
The problem I am running into is that I can't save this data to my useState variable and then render it into the component after useState is updated by my async function. Every time I try to access this data, I either get null or a Promise object. How can I access the data after useState is updated? Do I need to use a different React hook to call the Async function?
Here is the code that I am using:
import { Text, View, StyleSheet } from 'react-native';
import { useState, useEffect } from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
export default function HomeScreen() {
const [buttonData, setButtonData] = useState(null);
useEffect (() => {
const loadHomeScreenButtons = async () => {
try {
const buttons = await AsyncStorage.getItem('app_screens').then(screens => {
// Parse the JSON data from its stored string format into an object.
let app_screens_json = JSON.parse(screens);
let app_screens_list = app_screens_json.app_screens;
console.log(app_screens_list); // This outputs the data to the console.
setButtonData(app_screens_list); // Trying to set the button data in useState.
return app_screens_list;
});
}
catch (e) {
console.log(e)
}
}
loadHomeScreenButtons();
}, [])
return (
<View style={home_styles.container}>
<Text>{buttonData[1]["title"]}</Text>
</View>
);
}
You just need to render a loading component until your data is fetched.
{ buttonData?.length?
<Text>{buttonData[1]["title"]}</Text> : <Text>...loading</Text>
}
You are getting an error as you are trying to access a property that does not exist at the render.
Try this way
const loadHomeScreenButtons = useCallback(async () => {
try {
const screens = await AsyncStorage.getItem('app_screens')
const app_screens_json = JSON.parse(screens)
setButtonData(app_screens_json.app_screens)
}
catch (e) {
console.log(e)
}
}, []);
useEffect(() => {
loadHomeScreenButtons();
}, [loadHomeScreenButtons]);

Connecting actions to store outside a component?

So let's suppose I have a store, with a redux-thunk middleware in it. I created the store and exported it like this:
import myOwnCreateStoreMethod from './redux/createStore';
export const store = myOwnCreateStoreMethod();
I can now access it anywhere in my app. But what if I want to dispatch an action from anywhere? I have them declared e.g. in myAction.js:
export const myAction = () => (dispatch, getState) =>
dispatch({ type: 'SOME_TYPE', payload: ... })
Now I can import them and connect to my store/component like this:
import * as actions from './myActions.js';
const MyComponent = () => <div>Hello World</div>;
const mapStateToProps = () => ({});
export default connect(mapStateToProps, actions)(MyComponent);
My question is - what if I do not have a component and still want to dispatch actions declared like the one above?
You can dispatch actions from the store directly
import store from './myStore';
import { myAction } from './myAction';
store.dispatch(myAction());
Redux is a library by itself.
It has nothing to do with React, they just work well together based on React single source of truth and Redux one global store as the state of our application.
You can use redux in every JavaScript application.
Ah, so easy after #Asaf Aviv wrote his simple answer. So I implemented it like this:
import * as yourActions from './redux/actions/yourActions';
import { store } from './path/to/your/store';
const connectActions = (store, actions) => {
const { dispatch } = store;
return Object.keys(actions).reduce((acc, key) => {
const action = props => dispatch(actions[key](props));
acc[key] = action;
return acc;
}, {});
};
const connectedActions = connectActions(store, yourActions);

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!

Actions must be plain objects while using redux-thunk

I am implementing asynchronous action creators using react-redux and redux-thunk. However, I am getting the following error message: Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
I know that actions are supposed to be plain objects, and that middleware like thunk is supposed to take care of the cases when they are not. I have read several tutorials and looked at every SO question I could find on this, but I still can't figure out where I'm going wrong. Am I setting up thunk incorrectly, or am I using action creators in a bad way? Might this be an error with webpack or something?
Below I've included the code snippets I believe are relevant. Please let me know if additional info is needed.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Route } from 'react-router';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import Layout from './components/Layout.js';
import OrderPage from './containers/order-page';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
ReactDOM.render(
<Provider store={store}>
<App>
<Route exact path="/" component={Layout}/>
</App>
</Provider>,
document.querySelector('.app'));
reducers/order.js
import { FETCH_ERR, FETCH_SUCCESS, START_FETCH } from '../actions/types';
const initialState = {
fetching: false
};
export default (state=initialState, action)=>{
switch (action.type) {
case START_FETCH:
return {
fetching: true
}
case FETCH_ERR:
return {
err: action.payload.err,
fetching: false
}
case FETCH_SUCCESS:
return {
price: action.payload,
fetching: false
}
default:
return state
}
}
actions/price-fetch.js
import axios from 'axios'
const FETCH_ERR = 'FETCH_ERR'
const FETCH_SUCCESS = 'FETCH_SUCCESS'
const START_FETCH = 'START_FETCH'
const fetchSucc = (data)=>{
return{
type:FETCH_SUCCESS,
payload:data
}
}
const fetchFail = (message)=>{
return{
type:FETCH_ERR,
payload:message
}
}
const startFetch = () =>{
return{
type: START_FETCH,
payload:null
}
}
const fetchPrices = () =>{
return async (dispatch) =>{
try {
dispatch(startFetch())
let data = await axios.get('mybackendurl')
dispatch(fetchSucc(data))
} catch (error) {
dispatch(fetchFail({err:'failed to get shit'}))
}
}
}
export {
FETCH_ERR,
FETCH_SUCCESS,
fetchPrices,
START_FETCH
}
Relevant pieces of containers/order.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPrices } from '../actions/price-fetch';
class Order extends Component {
...
render() {
this.props.fetchPrices();
return ...
}
const mapDispatchToProps = dispatch => {
return {
fetchPrice: () => {
dispatch(fetchPrices())
}
}
}
function mapStateToProps(state){
return {
prices: state.order
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Order);
Thanks in advance for any help!
In case anyone comes across the same issue. The problem was not in the code shown above, or how I was dispatching actions. I had a duplicate definition of the redux-store in a different file, which overwrote the definition with the middleware.
In my case, I had the action declaration like below, due to which it was throwing such error.
export const withdrawMoney = (amount) => {
return (dispath) => {
dispath({
type: "withdraw",
payload: amount
})
}};
What I did was just changed my action definition to be an object type
export const depositMoney = (amount) => ({
type: "deposit",
payload: amount
});
And it jsut worked fine!
If anyone is here grasping at straws when using ImmutableJS + Typescript, turns out that you HAVE to define the "initialState" for the middleware to actually apply.
export const store = createStore(
combineReducers({APIReducer}),
{},
applyMiddleware(thunk.withExtraArgument(api))
);
I suspect it may be because you have an async (dispatch) function. That would cause it to return a Promise, which may be even confusing thunk.
In normal scenarios, the function itself would return another function, which thunk would inject the dispatch and call again and you would call dispatch inside the function:
arg => dispatch => dispatch({ type: arg });
When you add async, it basically becomes the same as this:
arg => dispatch => Promise.resolve(dispatch({ type: arg }));
You may have to ditch async/await inside of there and just use axios as a normal Promise, or add something extra to ensure it returns a nothing instead of a Promise.
const fetchPrices = () =>{`
return async (dispatch) =>{`
try {
dispatch(startFetch())
let data = await axios.get('mybackendurl')
dispatch(fetchSucc(data))
} catch (error) {
dispatch(fetchFail({err:'failed to get shit'}))
}
}
}
is returning a promise so when you do
const mapDispatchToProps = dispatch => {
return {
fetchPrice: () => {
dispatch(fetchPrices())
}
}
}
dispatch(fetchPrices()) is getting a promise not a plain object
the way i do these things is leave the heavy weight to my action; call async, when resolved dispatch data to store and in your component listen for and handle data(prices list) change.
const mapDispatchToProps = dispatch => {
return {
fetchPrice
}
}
you can thus show "loading please wait" while price list is empty and promise is not resolved/rejected

Why is my React component render called twice, once without data and then later with data, but too late exception?

I have a component TreeNav whose data comes from api call. I have setup reducer/action/promise and all the plumbing, but in component render when I call map() over the data, getting "Uncaught TypeError: Cannot read property 'map' of undefined".
Troubleshooting revealed TreeNav render() is called twice. 2nd time is after data comes back from api. But due to 1st render() error, 2nd render() never runs.
Here are my code files:
-------- reducers/index.js ---------
import { combineReducers } from 'redux';
import TreeDataReducer from './reducer_treedata';
const rootReducer = combineReducers({
treedata: TreeDataReducer
});
export default rootReducer;
-------- reducers/reducer_treedata.js ---------
import {FETCH_TREE_DATA} from '../actions/index';
export default function (state=[], action) {
switch (action.type) {
case FETCH_TREE_DATA: {
return [action.payload.data, ...state];
}
}
return state;
}
-------- actions/index.js --------
import axios from 'axios';
const ROOT_URL = 'http://localhost:8080/api';
export const FETCH_TREE_DATA = 'FETCH_TREE_DATA';
export function fetchTreeData () {
const url = `${ROOT_URL}/treedata`;
const request = axios.get(url);
return {
type: FETCH_TREE_DATA,
payload: request
};
}
-------- components/tree_nav.js --------
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {fetchTreeData} from '../actions/index';
class TreeNav extends Component {
constructor (props) {
super(props);
this.state = {treedata: null};
this.getTreeData();
}
getTreeData () {
this.props.fetchTreeData();
}
renderTreeData (treeNodeData) {
const text = treeNodeData.text;
return (
<div>
{text}
</div>
);
}
render () {
return (
<div className="tree-nav">
{this.props.treedata.children.map(this.renderTreeData)}
</div>
);
}
}
function mapStateToProps ({treedata}) {
return {treedata};
}
// anything returned from this function will end up as props
// on the tree nav
function mapDispatchToProps (dispatch) {
// whenever selectBook is called the result should be passed to all our reducers
return bindActionCreators({fetchTreeData}, dispatch);
}
// Promote tree_nav from a component to a container. Needs to know about
// this new dispatch method, fetchTreeData. Make it available as a prop.
export default connect(mapStateToProps, mapDispatchToProps)(TreeNav);
In terms of the error with your second render, the state must be getting overridden in a way you're not expecting. So in your reducer, you're returning array that contains whatever data is, and a splat of the current state. With arrays that does a concat.
var a = [1,2,3]
var b = [a, ...[2,3,4]]
Compiles to:
var a = [1, 2, 3];
var b = [a].concat([2, 3, 4]);
So given you're expecting a children property, what i think you actually want is a reducer that returns an object, not an array, and do something like this instead:
return Object.assign({}, state, { children: action.payload.data });
From there be sure to update the initial state to be an object, with an empty children array.
Get rid of this line since you're using props instead of state. State can be helpful if you need to manage changes just internally to the component. But you're leveraging connect, so that's not needed here.
this.state = {treedata: null};
Solved this by checking for the presence of this.props.treedata, etc. and if not available yet, just should div "loading...".
render () {
if (this.props.treedata && this.props.treedata[0] && this.props.treedata[0].children) {
console.dir(this.props.treedata[0].children);
return (
<div className="tree-nav">
{this.props.treedata[0].children.map(this.renderTreeData)}
</div>
);
} else {
return <div>Loading...</div>;
}
}

Categories

Resources