How To Access Props In a functional components - reactjs

I have some problem with component
When i use props i get same data in all of mates, but in GET_ODD action data is different
1)now how i can fix this porblem ?
2)how i can set interval for odd changes ? if this.props.odd.ad_time < nextProps.odd.ad_time change the odd in curenet props
json data for each odd :
{
"id": "24854545",
"home_od": "1.002",
"draw_od": "51.000",
"away_od": "51.000",
"ss": "3-1",
"time_str": "87",
"add_time": "1537706413"
}
reducer :
const initialState = {
odd: {}
};
function Markets(state = initialState, action) {
switch (action.type) {
case GET_ODD:
return {
...state,
...action.odd
};
default:
return state;
}
}
export default Markets
Markets action :
var marketsRESTRepository = new MarketsRESTRepository();
export const GET_ODD = "GET_ODD";
export function getOdd(odd) {
return {
type: GET_ODD,
odd
}
}
export function getAsyncOdd(eventId) {
return function (dispatch) {
marketsRESTRepository.getById(eventId).then(odd => {
dispatch(getOdd({odd}));
});
}
}
Action Odd data
same results for odds

in the market component try this :
import React, { Component } from "react";
import { connect } from 'react-redux';
import { getAsyncOdd } from "../../actions/markets.actions";
export class Markets extends Component {
constructor(props) {
super(props);
this.state = {
odds: {}
};
}
componentDidMount(){
this.props.getOdd(this.props.eventId);
this.setState({this.props.odd})
}
componentWillRecieveProps(nextProps) {
if(this.state.odds!==nextprops.odd){
this.setState({odds: nextprops.odd})
}
}
render() {
return (
<React.Fragment>
<span>{this.state.odds.home_od}</span>
<span>{this.state.odds.draw_od}</span>
<span>{this.state.odds.away_od}</span>
</React.Fragment>
);
}
}
function mapStateToProps({ markets }) {
return {
odd: markets.odd
}
}
function mapDispatchToProps(dispatch) {
return {
getOdd: (eventId) => dispatch(getAsyncOdd(eventId))
}
}
export default connect(mapStateToProps,
mapDispatchToProps
)(Markets)

Looks like the problem is with your reducer,
Why are you spreading the action? Since you are accessing it this way,
<span>{this.props.odd.home_od}</span>
Change it to this,
return {
...state,
add : action.odd
};

You have this:
dispatch(getOdd({odd}));
and the getOdd sync action will return this object:
{
type: "GET_ODD",
odd: {
odd: { /* data */}
}
}
which in turn will get dumped in the state by the reducer using the spread operator. I think you have to change the approach:
keep you getOdd action as is
when you call getOdd use it as getOdd(odd). No curly braces.
in you reducer, don't use the spread operator for the action. Use a specific key, for example:
return {
...state,
odd: action.odd
}
your async action looks good, just add a catch there and add a console.error so you know if something fails
For you 2 question. You can use one of the React life cycle methods to compare the current props and the next props coming and then perform that logic.
I hope it helps!

Related

React - How to get current state values in custom hooks with redux-toolkit?

I've got a question. I have a custom hook like this:
import { isNullOrUndefined } from '../../../../../../utils/utils';
import { useSelector } from 'react-redux';
export const useGoodsSetupWizardController = () => {
const productSetupWizard = useSelector((state) => state.productSetupWizard.value);
function isNextButtonDisabled() {
let returnValue = false;
switch(productSetupWizard?.step) {
case 2:
if(isNullOrUndefined(productSetupWizard?.product?.productName) || productSetupWizard?.product?.productName.trim().length === 0) {
returnValue = true;
}
}
return returnValue;
}
return {
isNextButtonDisabled,
};
}
As you can see I'm using useSelector to get the current value of productSetupWizard.
In my other components and hooks all is working fine using this way.
The slice is this one here:
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
value: {
step: 1,
product: null
},
};
export const productSetupWizardSlice = createSlice({
name: 'productSetupWizard',
initialState,
reducers: {
setProductSetupWizard: (state, action) => {
state.value = action.payload
},
increaseStep: (state, action) => {
state.value.step++;
},
decreaseStep: (state, action) => {
state.value.step--;
}
},
});
export const { setProductSetupWizard, increaseStep, decreaseStep } = productSetupWizardSlice.actions;
export default productSetupWizardSlice.reducer;
I don't know if you need to see the store file here to answer my question. If so, please let me know. I will add them to my question then.
The question I have, it seems not be working in a custom hook like the code above.
It gives me always the initial value of productSetupWizard and not updates on it.
Is it because the custom hook is initialized once on the component body with the initial value of productSetupWizard?
So my question is, is there any special usage in custom hooks necessary to get the state values from React-redux (toolkit)?
So the value of productSetupWizard in method isNextButtonDisabled() is always the initial value:
value: {
step: 1,
product: null
},
Here is the complete way of importing the different custom hooks I use in my project.
I have a hook called ProductSetupWizard. This is my UI. This ProductSetupWizard is importing the function getController from the custom hook called useProductTypeSetupWizard:
import { useProductTypeSetupWizard } from '../../../../factories/productSetup.factory';
function ProductSetupWizard(props) {
const [productSetupController, setProductSetupController] = useState(null);
...
const { getController } = useProductTypeSetupWizard();
...
useEffect(() => {
setProductSetupController(getController(productTypeId));
},[productTypeId]);
return (
<>
...
</>
);
}
The useProductTypeSetupWizard hook is loading and returning the useGoodsSetupWizardController depending on the given productTypeId:
import { isNullOrUndefined } from "../utils/utils";
import { useGoodsSetupWizardController } from '../components/managementInterface/shop/tabs/productSetupWizard/setups/goodsSetupWizard.controller';
export const useProductTypeSetupWizard = () => {
const {
getComponentGoodsController,
isNextButtonDisabledGoodsController,
needsNextStepToBeSkippedGoodsController
} = useGoodsSetupWizardController();
function getController(productTypeId) {
if(isNaN(parseInt(productTypeId))) {
return null;
}
switch(parseInt(productTypeId)) {
case 1:
return {
getComponent: getComponentGoodsController,
isNextButtonDisabled: isNextButtonDisabledGoodsController,
needsNextStepToBeSkipped: needsNextStepToBeSkippedGoodsController
};
case 3:
//apply like useGoodsSetupWizardController
}
}
return {
getController
};
}

Redux , state.concat is not a function at rootReducer. And being forced to reRender an element for it to see the state change

So I have this sidebar component where I load my store and my dispatcher
//select
const mapStateToProps = state => {
return { renderedEl: state.renderedEl }
}
function mapDispatchToProps(dispatch) {
return{
renderLayoutElement: element => dispatch(renderLayoutElement(element))
}
}
Then inside the same component this Is how I trigger the dispatcher
renderEl = (el) => {
var elementName = el.target.getAttribute('id');
var renderedElements = this.props.renderedEl; //this is data from the store
for (let key in renderedElements) {
if (key == elementName) {
renderedElements[key] = true
}
}
this.props.renderLayoutElement({renderedElements});
}
Then as I understand it gets sent to the reducer
import {RENDER_LAYOUT_ELEMENT} from "../constants/action-types"
const initialState = {
renderedEl: {
heimdall: false,
skadi: false,
mercator: false
}
}
function rootReducer(state = initialState, action){
if(action.type === RENDER_LAYOUT_ELEMENT){
return Object.assign({},state,{
renderedEl: state.renderedEl.concat(action.payload)
})
}
return state
}
export default rootReducer;
This is its action
import {RENDER_LAYOUT_ELEMENT} from "../constants/action-types"
export function renderLayoutElement(payload) {
return { type: RENDER_LAYOUT_ELEMENT, payload }
};
Now the thing is. Im receiving a
state.renderedEl.concat is not a function at rootreducer / at dispatch
I dont understand why does that happen.
Becuase, actually the store gets updated as I can see, but the console returns that error. And I have to reload the render that uses the props of that store (with an onhover) in order to be able to see the changes. It doesnt happen automatically as it would happen with a state
if(action.type === RENDER_LAYOUT_ELEMENT){
return { ...state, renderedEl: { ...state.renderedEl, ...action.payload } };
}
Duplicate from comments maybe it can be helpful to someone else :)

array.map from redux won't return data on the first load of react component

Hi I've been trying out a bit of react and electron and I'm just trying to make a media playlist type of application but I am having a bit of trouble with Redux
So I have setup the actions and they do kind of work but for some reason on the first initial load of a component the array.map that I am using to display all the results in a list won't actually render.
I have got the console out putting the results and when the component renders the first pass of the render fucntion the initial state is null then on the second pass the console logs the correct output but the array.map is still not outputting anything.
Then when I save a file in my editor (hot reload is on) I will then get an item to render on from the array.map
I can't work out if I have made astupid mistake somewhere or if I am just completly doing the wrong thing. So I'm hoping maybe someone might be able to shed some light on the situation.
Here is my component file with this array.map function that isn't working
interface Props {
songs?: any;
receiveMedia?: Function;
}
interface SongState {}
export default class Songs extends React.Component<Props, SongState> {
private initLoad = 0;
constructor(props: Props) {
super(props);
this.state = {};
this.getThumbnailRender = this.getThumbnailRender.bind(this);
}
componentDidMount() {
this.props.receiveMedia && this.props.receiveMedia();
}
getThumbnailRender() {
console.log(this.props.songs);
if (this.props.songs.Media.songs !== null) {
return this.props.songs.Media.songs.map((media: any) => {
if (media.extension !== "mp4") {
return (
<li
className={css.thumbNail}
id={media.id}
key={`${"media_thumb_"}${media.id}`}
>
<img src={mp3} />
<span className={css.floatingText}>{media.fileName}</span>
</li>
);
} else {
return (
<li
className={css.thumbNail}
id={media.id}
key={`${"media_thumb_"}${media.id}`}
>
<img src={media.filePath} />
<span className={css.floatingText}>{media.fileName}</span>
</li>
);
}
});
}
return <div>You haven't added any songs</div>;
}
render() {
return (
<div className={css.container}>
<h1>Songs</h1>
<div className={css.songHolder}>
<ul>{this.getThumbnailRender()}</ul>
</div>
</div>
);
}
}
I'm pretty sure that the Action and the reducer files are fine as they work later on but I will include them just incase I have made a stupid mistake
ACTIONS.ts
import { Songs } from "../../Models";
var fs = require("fs");
export enum ActionTypes {
MEDIA_RECEIVED = "[Media] MEDIA_RECEIVED"
}
export interface MediaReceived {
type: ActionTypes.MEDIA_RECEIVED;
payload: {
globals: Songs;
};
}
export function mediaReceived(json: any): MediaReceived {
return {
type: ActionTypes.MEDIA_RECEIVED,
payload: {
globals: json
}
};
}
function loadInCurrentSongList() {
var obj: Songs = {
//#ts-ignore
songs: []
};
fs.readFile("saveFile.json", "utf-8", (err: any, data: any) => {
if (err) {
alert("An error ocurred reading the file :" + err.message);
return;
}
const newData = JSON.parse(data);
if (newData.songs.length > 0) {
newData.songs.map((song: any) => {
obj.songs.push(song);
});
}
});
return obj;
}
export function receiveMedia() {
return (dispatch: Function) => {
dispatch(mediaReceived(loadInCurrentSongList()));
};
}
export type Action = MediaReceived;
REDUCER.ts
import { Songs } from "../../Models";
import { Action, ActionTypes } from "./Actions";
export interface State {
songs: Songs | null;
}
export const initialState: State = {
songs: null
};
export function reducer(state: State = initialState, action: Action) {
if (action.type === ActionTypes.MEDIA_RECEIVED) {
return Object.assign({}, state, action.payload.globals);
} else {
return state;
}
}
Thank you very much :)
Because fs.readFile is async and the callback passed to it is executed at a later time when the fs operation completes, obj is being returned from loadInCurrentSongList before it is populated with the songs, and therefore when mediaReceived is dispatched songs is still empty. Your debugger is fooling you a bit because it displays the updated value of obj after it gets populated in the fs.readFile callback.
The hot reload works because it forces a re-render without destroying state, at which point obj has been mutated inside of the fs.readFile callback.
Here's one option to manage the async nature of fs.readFile with a Promise such that you wait for it complete instead of mutating the obj returned from loanInCurrentSongList. Not super familiar with typescript so you'll have to update the types probably:
function loadInCurrentSongList() {
return new Promise(resolve => {
fs.readFile("saveFile.json", "utf-8", (err: any, data: any) => {
var obj: Songs = {
//#ts-ignore
songs: []
};
if (err) {
alert("An error ocurred reading the file :" + err.message);
resolve(obj);
return;
}
const newData = JSON.parse(data);
if (newData.songs.length > 0) {
newData.songs.map((song: any) => {
obj.songs.push(song);
});
}
resolve(obj);
});
}
export function receiveMedia() {
return (dispatch: Function) => {
loadInCurrentSongList().then(songList => {
dispatch(mediaReceived(songList));
}
};
}

ReactJS, componentWillReceiveProps can get data, but render cannot

I am working on a ReactJS 15 project using sagas and reselect as middle ware to fetch data. I can successfully get data in componentWillReceiveProps and set the data in the state, however there still not data in the render function first run when I take the data from the state. Anyone knows what's going on here? BTW, I used json-server as mock data server.Below is part of my code:
Component:
constructor(props) {
super(props);
this.state = {
timelineData: [],
};
}
componentDidMount() {
// use react redux to make the api call here
this.props.fetchTimelineData({
id: parse(this.props.location.search.substr(1)).id,
});
}
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps);
// Successfully take the data from the nextProps (timelineData is not [])
const { timelineData } = nextProps;
this.setState({
timelineData,
});
}
render() {
// the first render called timelineData is an empty array, which will not able to populate the UI
// which RaiseTimeline is empty
const { timelineData } = this.state;
return (
<RaiseTimelineStyled>
<RaiseDetailsGrid>
<Accordion
title={
<RaiseAccordionHeader image={image} title={'Timeline'} />
}>
<Timeline.wrapper>
<RaiseTimelineStyled.divider>
<RaiseTimelineStyled.container>
<RaiseTimeline timelineEvents={timelineData} />
action.js (works fine):
export const setTimelineData = timelineData => console.log('actions.js', timelineData) || ({
type: ACTIONS.SET_TIMELINE_DATA,
timelineData,
});
Api.js (works fine):
class TimelineAPI {
// payload will be used after backend done
static fetchTimelineData(payload) {
return http.get(`${baseURI}/timeline`).then(result => console.log('api', result.data) || result.data);
}
}
Reducers: (works fine)
function TimelineDataReducer(state = initialState, action) {
switch (action.type) {
case ACTIONS.SET_TIMELINE_DATA:
console.log('reducer', action.timelineData);
return state.set('numbers', action.timelineData);
default:
return state;
}
}
Sagas: (works fine)
export function* fetchTimelineData(action) {
yield put(togglePendingScreen(true));
const { result, error } = yield call(TimelineAPI.fetchTimelineData, action.payload);
if (error) {
yield put(
toggleErrorModal({
isOpen: true,
text: error.code,
source: 'Fetch Timeline Data',
}),
);
} else {
console.log('Sagas', result.timeline);
yield put(ACTIONS.setTimelineData(result.timeline));
}
yield put(togglePendingScreen(false));
}
Selectors(works fine):
import { createSelector } from 'reselect';
const selectTimelineData = state => state.get('TimelinePageData').toJS();
const selectTimeline = () =>
createSelector(selectTimelineData, TimelineDataState => TimelineDataState.numbers);
export { selectTimeline };
To me it seems logical that you have no data on the first run.
The reason is there render() function is called once before the componentDidMount() in the react life cycle. (V15)
Look here for the react 15 life cycle : https://gist.github.com/bvaughn/923dffb2cd9504ee440791fade8db5f9
I got the answer, everything is correct, but another person name but another person name same component and set its state in side the constructor, so it's not able to render in the first time

Object passed into Redux store is not reflecting all key/values after mapStateToProps

I have a component where toggle buttons are dynamically generated. Right now, I am just trying to get it working at a basic level so you click on a button and it adds a key/value pair to the cuts = {}.
After clicking on multiple buttons the cuts should have several key/value pairs: it does in the component where cuts resides, it does in the action, and it does in the Redux store via console.log(state.cuts).
However, after mapStateToProps it is only showing the first value and I am not sure why.
Anyway, here is my code and the flow as it is initiated by the user:
// bq_cuts.js component
constructor(props) {
super(props);
this.state = {
cuts: {}
}
}
onCutSelect(cut) {
const { bqResults } = this.props;
const { cuts } = this.state;
let key = cut.name;
let value = cut.value;
cuts[key] = value;
this.setState({
cuts
})
console.log(cuts); // shows all of the selected cuts here
bqResults(cuts);
}
// results.js actions
export function bqResults(results) {
console.log(results); // shows all of the selected cuts here
return function(dispatch) {
dispatch({
type: FILTER_RESULTS,
payload: results
})
}
}
// results.js reducer
import {
FILTER_RESULTS
} from '../actions/results';
export default function(state = {}, action) {
switch(action.type) {
case FILTER_RESULTS:
console.log(action.payload); //prints out all the cuts
return {
...state,
filter_results: action.payload
}
default:
return state;
}
return state;
}
const rootReducer = combineReducers({
results: resultsReducer,
});
export default rootReducer;
// bq_results.js component where the FILTER_RESULTS is accessed
render() {
console.log(this.props.filter_results); // only shows the first result
return (<div>...</div>)
}
function mapStateToProps(state) {
console.log(state.results.filter_results); // shows all selected cuts here
return {
filter_results: state.results.filter_results,
}
}
Maybe a better way of putting it is it seems like after the initial state is mapped to props, it is no longer receiving changes to state and mapping it to props.
Came across this article and used Approach #2:
https://medium.freecodecamp.org/handling-state-in-react-four-immutable-approaches-to-consider-d1f5c00249d5
Ended up with:
onCutSelect(cut) {
let cuts = {...this.state.cuts, [cut]: cut}
this.setState({
cuts
}, () => this.props.bqResults(this.state.cuts));
}

Resources