Populating React dropdown asyncrhonously with Redux - javascript

Background
I'm attempting to create a dropdown that retrieves State Codes (AZ, WI, WY, etc.) from a backend API and then populates an on-screen dropdown with the values.
I have a React component that looks like this (an ellipsis representing code that I'm omitting for clarity):
Person.jsx
export class Person extends React.Component {
constructor(props) {
super(props);
...
this.props.getStateCodes();
}
render(){
...
<select
id="personState"
name="personState"
className="form-control dropDownStyle"
onChange={this.handleChange}
value={this.props.person.personState}
>
{this.props.stateCodes && this.props.stateCodes.map((option) => (
<option key={option.id} value={option.data}>{option.data}</option>
))
}
</select>
...
}
}
I then have Redux action creators, including an excerpt like this:
personContract.js
export const actionCreators = {
...
getStateCodes: () => async (dispatch) => {
getStateCodesResponse(dispatch);
},
...
export function getStateCodesResponse(dispatch) {
const endpoint = window.location.origin + '/Home/GetStateCodes';
fetch(endpoint, {
credentials: 'same-origin'
})
.then(function (response) {
if (!response.ok) {
const errors = ['Unable to retrieve state codes.'];
dispatch({ type: "SET_ERROR", errors: errors });
document.body.style.cursor = 'default';
return;
}
return response.json();
}).then(function (data) {
if (data !== undefined) {
const stateCodes = data.stateCodes;
// const stateCodes = result.PayLoad.StateCodes;
document.body.style.cursor = 'default';
dispatch({ type: 'STATECODES', stateCodes });
}
});
}
...
}
Then a reducer that includes:
Contract.js
const initialState ={
...
stateCodes: [],
...
};
export const reducer = (state, action) => {
...
if (action.type == "STATECODES"){
const stateCodes = action.stateCodes;
return {
...state,
errors: [],
stateCodes: stateCodes
}
}
...
}
Problem
Initially, I did not include {this.props.stateCodes && in the Person.jsx file. The issue then, was that I'd get an error that this.props.stateCodes was not defined. I added in {this.props.stateCodes &&, however, now it never runs this.props.stateCodes.map at all. It's almost as if I need to render() to run again after the State Codes have been retrieved, but I don't know how to accomplish that.

Related

ComponentDidMount fires twice calling API

I am new to react.js so troubles caught me. I have small todo-list app connected with mockAPI. Application gets todo list data from API. As required, I call API inside componentDidMount() instead of constructor. However, API is called twice (only after page reloaded, not data manipulation as put\delete data to API). Any errors or warnings in console.
class App extends Component {
todoServ = new TodoServer();
constructor(props) {
super(props);
this.state = { data: [], maxId: 0 };
}
/*
code to add\delete\done todo item;
*/
findCurrentMaxId = (data) => {
const idList = [];
data.forEach(todo => {
idList.push(todo.id);
});
return Math.max(...idList);
}
updateTodoData = (data) => {
const maxId = this.findCurrentMaxId(data);
this.setState({ data, maxId });
}
getTodoData = () => {
this.todoServ
.getTodoList()
.then(this.updateTodoData)
.catch(this.errorTodoData);
}
componentDidMount() {
this.getTodoData();
}
render() {
return (
<div className="app">
<div className="content">
<AddTodoListItem onAddNewTodoItemData={this.onAddNewTodoItemData}/>
<TodoList
data={this.state.data}
onDoneTodoItemData={this.onDoneTodoItemData}
onDeleteTodoItemData={this.onDeleteTodoItemData} />
</div>
</div>
)
}
}
export default App;
Console:
This is the service fetches data.
class TodoService {
#url = `https://*secret*/todoslist/todo`;
async getResource(url) {
let res = await fetch(url);
if (!res.ok) {
throw new Error(`Could not fetch ${url}, status: ${res.status}`);
}
return await res.json();
}
async getTodoList() {
const res = await this.getResource(this.#url);
console.log('GET', res);
return res;
}
}
export default TodoService;
Thanks for the advices.

useSelector does not rerender the component when state changes

I'm working on a news aggregator, and I have a Newsfeed component that maps through the relevant posts and creates a Post component for each one. There's a Sidebar component that shows the user the feeds they are subscribed to, and allows them to subscribe to new ones or unsubscribe from existing ones. What I'd like to happen is:
When a user adds a new feed, Newsfeed rerenders and now shows posts from the new feed.
When a user removes a feed, Newsfeed rerender and no longer shows posts from that particular feed.
As far as retrieving the correct posts - my backend takes care of that, and it works fine. The backend returns posts based on the feeds that the user is subscribed to. The problem is, when the user adds or removes a feed, the Newsfeed component does not rerender, and requires a page reload to show the updated feed. At the same time however, the Redux store IS updated, and I can see the state change every time via the Redux Dev Tools.
In Newsfeed, I'm using the useSelector hook to get a few different pieces of state, yet the component does not rerender when the state changes. I was under the impression that any component that used the useSelector hook would automatically be rerendered when that piece of state changed, but if that's not how the hook works then please correct me.
Newsfeed.tsx:
import React, { useState, useRef, useCallback } from "react";
import usePostFetch from "../hooks/usePostFetch";
import { Post } from "./Post";
import { Tag } from "./Tag";
import { Upvote } from "./Upvote";
import { getDate } from "../services/getDate";
import { useSelector } from "react-redux";
import { InitialState } from "../store/reducers/rootReducer";
export const Newsfeed = (props: any) => {
const userState = useSelector((state: InitialState) => {
return state.auth;
});
const { user } = userState;
const publisherState = useSelector((state: InitialState) => {
return state.publishers.publishers;
});
const [pageNumber, setPageNumber] = useState(1);
const { loading, error, posts, hasMore } = usePostFetch(pageNumber);
const observer: any = useRef();
const lastPostElementRef = useCallback(
(node) => {
if (loading) return;
if (observer && observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
console.log("Visible ");
setPageNumber((prevPageNumber) => prevPageNumber + 1);
}
});
if (node) observer.current.observe(node);
console.log(node);
},
[loading, hasMore]
);
return (
<div className="container mx-auto bg-gray-900" id="newsfeed">
<div className="object-center grid grid-cols-1 gap-8 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 mx-auto pb-6 pt-6">
{posts.map((post, index) => {
return (
<Post
key={post.id}
title={post.title}
url={post.url}
image={post.image}
category={post.category}
postId={post._id}
created={post.created}
publisher={post.publisher}
upvotes={post.upvotes}
/>
);
})}
</div>
<div>{loading && "Loading..."}</div>
<div>{error && "Error"}</div>
</div>
);
};
publisherActions.ts: (Relevant parts)
export const removeFeed = (allFeeds: any, feedName: any, userId: any) => async (
dispatch: Dispatch<PublisherDispatchTypes>
) => {
try {
axios({
method: "PUT",
url: "users/removepublisher",
params: { publisher: feedName, userId },
})
.then((res) => {
let newAllFeeds = allFeeds.filter((feed: any) => {
return feed.name.localeCompare(feedName) !== 0;
});
allFeeds = newAllFeeds;
console.log(`Feed was removed, ${res}`);
dispatch({
type: REMOVE_FEED_SUCCESS,
payload: allFeeds,
});
})
.catch((err) => {
console.log(`Error removing feed, ${err}`);
dispatch({
type: REMOVE_FEED_FAILURE,
});
});
} catch {
dispatch({ type: REMOVE_FEED_FAILURE });
console.log("Caught error while removing feed");
}
};
export const addFeed = (allFeeds: any, feed: any, userId: any) => async (
dispatch: Dispatch<PublisherDispatchTypes>
) => {
console.log("IN THE ADD_FEED FUNCTION");
try {
axios({
method: "PUT",
url: "users/addpublisher",
params: { publisher: feed, userId },
})
.then((res) => {
console.log(`Feed was added, ${res}`);
dispatch({
type: ADD_FEED_SUCCESS,
payload: {
name: feed.name,
url: feed.url,
image: feed.image,
},
});
})
.catch((err) => {
console.log(`Error adding feed, ${err}`);
dispatch({
type: ADD_FEED_FAILURE,
});
});
} catch {
dispatch({ type: ADD_FEED_FAILURE });
console.log("Caught error while adding feed");
}
publisherReducer.ts: (Relevant parts)
import { Reducer } from "react";
import {
PublisherDispatchTypes,
REMOVE_FEED_SUCCESS,
REMOVE_FEED_FAILURE,
ADD_FEED_SUCCESS,
ADD_FEED_FAILURE,
} from "../actions/publisherActionsTypes";
import { Publisher } from "../../../../shared/Publisher";
interface PublisherResponse {
publishers: Publisher[];
}
export interface PublisherState {
publishers: Publisher[] | undefined;
loadedUsersFeeds: boolean;
feedCount: number;
}
const defaultState: PublisherState = {
publishers: undefined,
loadedUsersFeeds: false,
feedCount: 0,
};
const publisherReducer = (
state: PublisherState = defaultState,
action: PublisherDispatchTypes
) => {
switch (action.type) {
case REMOVE_FEED_SUCCESS:
return {
...state,
publishers: action.payload,
};
case REMOVE_FEED_FAILURE:
return state;
case ADD_FEED_SUCCESS:
let pubs = state.publishers || [];
return {
...state,
publishers: [...pubs, action.payload],
};
case ADD_FEED_FAILURE:
return state;
default:
return state;
}
};
export default publisherReducer;

React class Component not re render after props change from redux action

The component does not re render after successfully update state in redux
i have tried to do some condition in componentShouldUpdate end up with loading true without change
reducer.js
import * as types from "./actionsType";
const INITIAL_STATE = {
slide_data: [],
error: null,
loading: false,
};
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties,
};
};
const slideReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.SLIDES_FETCH_START:
return updateObject(state, {
error: null,
loading: true,
});
case types.SLIDES_FETCH_SUCSSES:
return updateObject(state, {
slide_data: action.payload,
error: null,
loading: false,
});
case types.SLIDES_FETCH_FAIL:
return updateObject(state, {
error: action.error,
loading: false,
});
default:
return state;
}
};
export default slideReducer;
actions.js
import * as types from "./actionsType";
import axios from "axios";
import { selectSlides } from "./slides.selectors";
export const slidesStart = () => {
return {
type: types.SLIDES_FETCH_START,
};
};
export const slidesSucces = (slides) => {
return {
type: types.SLIDES_FETCH_SUCSSES,
payload: slides,
};
};
export const slidesFail = (error) => {
return {
type: types.SLIDES_FETCH_FAIL,
error: error,
};
};
export const fetchSlides = () => {
return (dispatch) => {
console.log("fetch Start");
dispatch(slidesStart());
axios
.get("http://127.0.0.1:8000/slides/", {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
dispatch(slidesSucces(res.data));
})
.catch((err) => dispatch(slidesFail(err)));
};
};
component
class IntroPage extends Component {
constructor(props) {
super(props);
this.tlitRef = React.createRef();
this.titlelRef = React.createRef();
this.subTitleRef = React.createRef();
this.showcase = React.createRef();
}
componentDidMount() {
this.props.fetchSlides();
}
render() {
const { slides, loading } = this.props;
if (loading) {
return <h1>Loading</h1>;
}
return (
<div className="intro">
<div className="wrapper">
{slides.map((data) => (
<SwiperSlides data={data} key={data.name} />
))}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.slides.loading,
error: state.slides.error,
slides: state.slides.slide_data,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchSlides: () => dispatch(fetchSlides()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(IntroPage);
Register the redux-logger correctly. The data was returned but nothing changes when I do redux-persist and try to reload data come through
update ::
when I change the size of the browser data it correctly appears what is this !!
update : this problem related to swiperjs
and the solution will be like that:
1 - assign swiper instance to React.CreateRef(null) : this.swiper = React.CreateRef(null)
2 - in componentDidUpdate() make a swiper update : this.swiper.current.update()
4 - use a arrow function syntax in swiper on functions to refer to the outer scope

how to distinguish which component called a callback function?

I'm new to react, sorry if that is newbe question. I have a component Dropdown which returns a value via a callback function. I would like to render that twice to choose two different values and then simply render chosen values below. How can I allow your two different components to send different data to the component. Below is my code.
index.js
import { Dropdown } from './components/dropdown'
class App extends Component {
constructor(props) {
super(props);
this.calculateRate = this.calculateRate.bind(this);
this.callApi = this.callApi.bind(this);
this.state = {
response: "",
currA: 0,
currB: 1
}
}
componentDidMount() {
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => {console.log(err)});
}
callApi = async () => {
const response = await fetch('/main');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
}
calculateRate = (key, val) => {
// if the calling agent sent currA data, update currA,
// else if the calling agent sent currB data, update currB
if (key === 'A') this.setState({currA: val})
if (key === 'B') this.setState({currB: val})
console.log('updated curr' + key + ' to ' + val);
}
render() {
return (
<div className='App'>
<div>
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'A'} val={this.state.currA} />
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'B'} val={this.state.currB} />
</div>
</div>
);
}
}
export default App;
dropdown.js
export class Dropdown extends React.Component {
constructor(props){
super(props);
this.state = {
list: [],
selected: ""
};
}
componentDidMount(){
fetch('https://api.fixer.io/latest')
.then(response => response.json())
.then(myJson => {
this.setState({ list: Object.keys(myJson.rates) });
});
}
render(){
var selectCurr = (curr) =>
<select
onChange={event => props.callbackFromParent(props.stateKey, event.target.value)}
>
{(this.state.list).map(x => <option>{x}</option>)}
</select>;
return (
<div>
{selectCurr()}
</div>
);
}
}
I'm not exactly sure what you're trying to achieve, but hopefully the following shows how you can allow your two different components to send different data to the <App> component.
The important changes are: we need to bind methods to the <App> component in the constructor() function, then we can use the .bind() method in the Dropdown component to specify the data to pass into the callback function:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.calculateRate = this.calculateRate.bind(this);
this.callApi = this.callApi.bind(this);
this.state = {
response: "",
currA: 0,
currB: 1
}
}
componentDidMount() {
/*
this.callApi()
.then(res => this.setState({ response: res.express }))
.catch(err => {console.log(err)});
*/
}
callApi = async () => {
const response = await fetch('/main');
const body = await response.json();
if (response.status !== 200) throw Error(body.message);
return body;
}
calculateRate = (key, val) => {
// if the calling agent sent currA data, update currA,
// else if the calling agent sent currB data, update currB
if (key === 'A') this.setState({currA: val})
if (key === 'B') this.setState({currB: val})
console.log('updated curr' + key + ' to ' + val);
}
render() {
return (
<div className='App'>
<div>
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'A'} val={this.state.currA} />
<Dropdown callbackFromParent={this.calculateRate}
stateKey={'B'} val={this.state.currB} />
</div>
</div>
);
}
}
const Dropdown = props => (
<select onChange={event => props.callbackFromParent(props.stateKey, event.target.value)}>
<option value='cats'>Cats</option>
<option value='dogs'>Dogs</option>
</select>
)
export default App;

React/Redux firing action->render before update a store

A have a simply react/redux app. I Fetch data from API async but component not waiting for data and firing render.
class RestaurantList extends React.Component {
componentWillMount() {
this.props.getRestaurantList();
}
render() {
console.log("render");
let {translation} = store.getState().app;
//------------I NEED DATA ON THIS LET (restaurantList)
let {restaurantList} = this.props.restaurants;
return (
<div>
<TableContainer data={restaurantList}/>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
restaurants: state.restaurants
};
};
const mapDispatchToProps = (dispatch) => {
return {
getRestaurantList() {
dispatch(ACTIONS.getRestaurantList());
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(RestaurantList);
On my action i fetching data using axios :
export function getRestaurantList() {
console.log("action");
return dispatch => {
axios({
method: "GET",
url: URLS.BASE_URL + URLS.URL_RESTAURANT_LIST
}).then((response) => {
console.log(response);
dispatch({
type: CONST.GET_RESTAURANT_LIST,
payload: response.data
})
})
}
}
And my component fired method ComponenWillMount after that render () and next store which update store and set good data to my variable. Maybe u give me advice how to do that to have on my render my fetching data because now on my table I transfer undefined on start. Maybe you give me an example to using another framework like redux-saga or other.
You could try conditionally rendering your TableContainer component so the table will only be rendered once there is data available:
renderTable() {
let { restaurantList } = this.props.restaurants
if (restaurantList) {
return <TableContainer data={ restaurantList } />
} else {
return <div></div>
}
}
render() {
return (
<div>
{ this.renderTable() }
</div>
)
}

Categories