Add multiple parameters to URL API fetch in Redux? - reactjs

I am trying to add multiple parameters to a API fetch using Redux. I am able to get one to work (maxDist you see below in the API call), but I want to add the minStars and maxRes parameters as well, but am unsure how I would add more than one. I tried adding more parameters to the search() function, but it will only use one argument. Here is what I have so far:
API Call:
export default {
search(maxDist) {
const url = `${API_URL}&maxDistance=${maxDist}&minStars=${minStars}&maxResults=${maxRes}`;
console.log(url);
return fetch(url)
.then(response => response.json())
.then(result => {
return result.items;
});
}
};
Redux Side:
const MAX_DIST_CHANGED = "MAX_DIST_CHANGED";
const initialState = {
items: [],
maxDist: "",
minStars: "",
maxRes: ""
};
export const actions = {
maxDistChanged(maxDist) {
return {
type: MAX_DIST_CHANGED,
maxDist
};
getItems(maxDist) {
return {
type: "ITEMS",
payload: API.search(maxDist)
};
}
};
export function reducer(state = initialState, action) {
switch (action.type) {
case MAX_DIST_CHANGED: {
return {
...state,
maxDist: action.maxDist
};
}
case "ITEMS_FULFILLED": {
return {
...state,
loading: false,
items: action.payload
};
}
default:
return state;
}
}
App.js:
handleSubmit = e => {
e.preventDefault();
this.props.onGetItems(
this.props.maxDist
);
};
handleChange = event => this.props.onMaxDistChanged(event.target);
function mapDisatchToProps(dispatch) {
return {
onMaxDistChanged(maxDist) {
dispatch(actions.maxDistChanged(maxDist));
}
onGetItems(maxDist) {
dispatch(actions.getItems(maxDist));
}
};
}
How do I add the minStars and maxRes in the api call? Only one parameter seems to works. Thank you!

Related

redux actions are duplicated when I try to fetch data

I am trying to create load more functionality by fetching only the necessary date i.e. the next one that needs to be added to the existing state that I have in the redux store, but I have a problem my redux actions are duplicated.
Component App.js
function App() {
const dispatch = useDispatch();
const data = useSelector(questionsData);
useEffect(() => {
const fetchQuestions = async () => {
dispatch(fetchQuestionsBegin());
try {
const { data } = await mainUrl(`/questions?last=5`);
return dispatch(fetchQuestionsSuccess(data));
} catch (err) {
return dispatch(fetchQuestionsFailure());
}
};
fetchQuestions();
}, [dispatch]);
return (
<>
<div>TEST</div>
</>
);
}
creating store
const store = configureStore({
reducer: {
questionsStore: questionsReducer,
},
});
export default store;
slice
const initialState = {
loading: false,
questions: [],
error: "",
};
const questionsSlice = createSlice({
name: "questions",
initialState,
reducers: {
fetchQuestionsBegin(state) {
return { ...state, loading: true, error: "" };
},
fetchQuestionsSuccess(state, action) {
return {
...state,
loading: false,
questions: [...state.questions, ...action.payload],
};
},
fetchQuestionsFailure(state, action) {
return { ...state, loading: false, error: action.payload };
},
},
});
export const { reducer: questionsReducer, actions } = questionsSlice;
export const {
fetchQuestionsBegin,
fetchQuestionsSuccess,
fetchQuestionsFailure,
} = actions;
redux
When I exclude <React.StrictMode> everything works fine.
Refer to link. Strict mode can cause multiple methods to invoke multiple times. Its most likely that your redux is ran twice when the component mounts for the first time. You can implement useRef to detect initial mount and then conditionally render after
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
}, [])
useEffect(() => {
if (isMounted.current) {
const fetchQuestions = async () => {
dispatch(fetchQuestionsBegin());
try {
const { data } = await mainUrl(`/questions?last=5`);
return dispatch(fetchQuestionsSuccess(data));
} catch (err) {
return dispatch(fetchQuestionsFailure());
}
};
fetchQuestions();
}
}, [dispatch]);

Redux Thunk - Get Updated Store in Promise

I'm having an issue trying to use Redux Thunk for implementing JWT Authentication in my app: when I perform login and retrieve the promise from the action, I do not get the updated store (I still have the previous value).
Here is my code:
const Login = ({ doLogin, token }) => {
const submitForm = (e) => {
doLogin(email, password).then(function () {
console.log(token);
});
}
};
return (
// Some JSX that calls submitForm()
);
};
Login.propTypes = {
token: PropTypes.string.isRequired,
doLogin: PropTypes.func.isRequired,
};
function mapStateToProps(state) {
console.log(state);
return {
token: state.auth.access_token,
};
}
const mapDispatchToProps = { doLogin };
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Here is my action creator:
export function doLogin(email, password) {
return (dispatch) => {
dispatch(start());
return axios
.post(config.api.url + "/login", {
email,
password,
})
.then((success_rsp) => {
if (success_rsp.data.success) {
dispatch(success(success_rsp.data.access_token));
} else {
dispatch(error());
}
})
.catch((error_rsp) => dispatch(error()));
};
function start() {
return { type: types.AUTH_LOGIN };
}
function success(access_token) {
return {
type: types.AUTH_LOGIN_SUCCESS,
is_authenticated: true,
access_token: access_token
};
}
function error() {
return {
type: types.AUTH_LOGIN_ERROR,
};
}
}
And here is my reducer:
const authReducer = (state = initialState.auth, action) => {
switch (action.type) {
case types.AUTH_LOGIN:
return {
...state,
is_loading: true,
};
case types.AUTH_LOGIN_SUCCESS:
return {
...state,
is_loading: false,
is_authenticated: action.is_authenticated,
access_token: action.access_token
};
case types.AUTH_LOGIN_ERROR:
return {
...state,
is_loading: false,
is_authenticated: false,
access_token: "",
};
default:
return state;
}
};
The problem is that when I get in the Promise of my Login component, it logs the previous value of the token, and not the one received by the API.
However, when I log the state in the mapStateToProps function, I see that the function is called 3 times in total (one when arriving on the page, one when starting the doLogin, and one on the success), and it logs the right value for the token.
Do you know why the token I get is not updated? And how I could get an updated one?
Thank you!

How to work with API call action in redux?

I am new to redux and I am trying to make it work with my application, but I have problems with understanding how to work with async actions in it. I have action that is api call. This action should be called as soon as my other state is not empty. I do not get any mistakes but do not think that my action is called since the data is empty. Can anybody help to understand what I am doing wrong?
Here is my actions.js. The wordsFetchData is the action I need to call:
export function wordsFetchDataSuccess(items){
return{
type: 'WORDS_FETCH_DATA_SUCCESS',
items
};
}
export function wordsAreFetching(bool){
return{
type: 'WORDS_ARE_FETCHING',
areFetching: bool
}
}
export function wordsHasErrored(bool) {
return {
type: 'WORDS_HAS_ERRORED',
hasErrored: bool
};
}
export function wordsFetchData(parsed) {
return (dispatch) => {
dispatch(wordsAreFetching(true));
fetch('URL', {
method: "POST",
headers: {
"Content-type": "application/json"
},body: JSON.stringify({
words: parsed
})
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(wordsAreFetching(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(wordsFetchDataSuccess(items)))
.catch(() => dispatch(wordsHasErrored(true)));
};
}
Here are my reducers:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
export function wordsAreFetching(state = false, action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return action.areFetching;
default:
return state;
}
}
export function wordsFetchHasErrored(state = false, action) {
switch (action.type) {
case 'WORDS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
This is my componentDidMount function:
componentDidMount = (state) => {
this.props.fetchData(state);
};
This is the function after terminating which the action should be called:
parseInput = async () => {
console.log(this.state.textInput);
let tempArray = this.state.textInput.split(" "); // `convert
string into array`
let newArray = tempArray.filter(word => word.endsWith("*"));
let filterArray = newArray.map(word => word.replace('*', ''));
await this.setState({filterArray: filterArray});
await this.props.updateData(this.state.filterArray);
if (this.state.projectID === "" && this.state.entity === "")
this.dialog.current.handleClickOpen();
else
if (this.state.filterArray.length !== 0)
this.componentDidMount(this.state.filterArray);
};
These are the mapStateToProps and mapDispatchToProps functions.
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.wordsFetchHasErrored,
areFetching: state.wordsAreFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: wordsFetchData
};
};
You only need one action for executing fetching (i.e WORDS_ARE_FETCHING), the rest of the cases (i.e WORDS_HAS_ERRORED & WORDS_FETCH_DATA_SUCCESS) can be handled inside your reducer.
Your action:
export function wordsAreFetching(){
return{
type: 'WORDS_ARE_FETCHING',
}
}
Your new reducer:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return {...state, error: false, areFetching: true};
case 'WORDS_FETCH_DATA_SUCCESS':
return {...state, items: action.payload , areFetching: false};
case 'WORDS_HAS_ERRORED':
return {...state, error: true, areFetching: false};
default:
return state;
}
Then you can trigger WORDS_FETCH_DATA_SUCCESS after you get the data from here:
export function wordsFetchData() {
try {
const response = await axios.get(YOUR_URL);
return dispatch({ type: WORDS_FETCH_DATA_SUCCESS, payload: response.data });
} catch (err) {
return dispatch({ type: WORDS_HAS_ERRORED });
}
}
Take a look at this example, it uses axios that can help you with async calls.
A couple of things:
No need to pass state into your componentDidMount, your mapDispatchToProps is not using it.
Here is a suggestion to structure those functions. They are a bit more concise and readable.
const mapStateToProps = ({items, wordsAreFetching, wordsFetchHasError}) => ({
items,
hasErrored: wordsFetchHasErrored,
areFetching: wordsAreFetching,
});
const mapDispatchToProps = () => ({
fetchData: wordsFetchData(),
});
Other notes and helpful things:
If you're using thunk, you'll have access to your entire redux store in here as a second argument. For example:
return (dispatch, getState) => {
dispatch(wordsAreFetching(true));
console.log('getState', getState());
const { words } = getState().items;
// This is a great place to do some checks to see if you _need_ to fetch any data!
// Maybe you already have it in your state?
if (!words.length) {
fetch('URL', {
method: "POST",
headers: {
......
}
})
I hope this helps, if you need anything else feel free to ask.

Call multiple actions jointly in React Redux

I am learning React Redux. My Action is like below
import Axios from 'axios';
export const getAddress = valueModal => dispatch => {
return Axios.get('/api/address')
.then(response => {
var addressData = response.data;
dispatch({
type: 'getAddresses',
payload: { addressData, valueModal }
});
})
.catch(function(error) {
console.log(error);
});
};
export const uploadImage = (formData, id, config) => dispatch => {
return Axios.post('/api/address/upload', formData, config)
.then(response => {
dispatch({
type: 'uploadImage',
payload: response.data
});
})
.catch(function(error) {
console.log(error);
});
};
export default { getAddress, addAddress, uploadImage };
My Reducer is like below
const initialState = {
address: {}
};
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
uploadImage: action.payload
};
}
default:
return state;
}
};
export default addressReducer;
I would like to call getAddresses and uploadImage jointly. How can I do that ?
const initialState = {
address: {}
};
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
...state,
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
...state,
uploadImage: action.payload
};
}
default:
return state;
}
};
export default addressReducer;
You need to spread the object state out otherwise there is never a reference to the state before the update.
The object spread syntax lets you use the spread ... operator to copy enumerable properties from one object to another in a more succinct way.

React component does not re-render reactively (Redux Observable + rxjs + rx-http-requst)

I have an API endpoint that returns a list of users in an 'application/stream+json' type response. The items are separated by a new line character.
Example data can be seen here.
Component
class UserList extends Component {
componentDidMount() {
const { fetchUsers } = this.props;
fetchUsers();
}
render() {
const { isFetching = false, users = [] } = this.props;
if (isFetching) {
return <Loader message="Users are loading..." />;
}
if (!users || users.length === 0) {
return 'No users found.';
}
const children = users
.map(user => <UserListItem key={user.id} user={user} />);
return (
<div className="UserList">
<Paper>
<List>
<Subheader>Users</Subheader>
{children}
</List>
</Paper>
</div>
);
}
}
UserList.propTypes = {
users: PropTypes.arrayOf(PropTypes.any),
isFetching: PropTypes.bool.isRequired,
fetchUsers: PropTypes.func.isRequired,
};
UserList.defaultProps = {
users: [],
};
function mapStateToProps(state) {
const { users, isFetching } = state.users;
return {
users,
isFetching,
};
}
function mapDispatchToProps(dispatch) {
return {
fetchUsers: bindActionCreators(actions.fetchUsers, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(UserList);
Reducer
const initialState = {
users: [],
isFetching: false,
};
function fetchUsers(state) {
return {
...state,
isFetching: true,
};
}
function fetchUsersItemReceived(state, action) {
const { user } = action;
return {
...state,
users: [...state.users, user],
isFetching: false,
};
}
export default function (state = initialState, action) {
switch (action.type) {
case actionTypes.FETCH_USERS_REQUEST:
return fetchUsers(state);
case actionTypes.FETCH_USERS_ITEM_RECEIVED:
return fetchUsersItemReceived(state, action);
default:
return state;
}
}
Action (the parser is the Streaming JSON Parser found here)
export function fetchUsers() {
return {
type: actionTypes.FETCH_USERS_REQUEST,
};
}
function fetchUsersItemReceived(user) {
return {
type: actionTypes.FETCH_USERS_ITEM_RECEIVED,
user,
};
}
function fetchUsersSuccess() {
return {
type: actionTypes.FETCH_USERS_SUCCESS,
};
}
function fetchUsersFailure(error) {
return {
type: actionTypes.FETCH_USERS_FAILURE,
error,
};
}
function getJsonStream(url) {
const emitter = new Subject();
const req$ = RxHR
.get(url)
.flatMap(resp => resp.body)
.subscribe(
(data) => {
parser.write(data);
parser.onValue = (value) => {
if (!parser.key) {
emitter.next(value);
}
};
},
err => emitter.error(err),
() => emitter.complete(),
);
return emitter;
}
export const fetchUsersEpic = action$ =>
action$.ofType(actionTypes.FETCH_USERS_REQUEST)
.concatMap(() => getJsonStream(`${api.API_BASE_URL}/user`))
.map(user => fetchUsersItemReceived(user));
configureStore.js
const logger = createLogger();
const epicMiddleware = createEpicMiddleware(rootEpic);
const createStoreWithMiddleware = applyMiddleware(epicMiddleware, logger)(createStore);
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}
While the list component should be refreshed after EACH item is received, it is refreshed AFTER the whole list is received. Can someone point me to the blocking point in the code?
This isn't a solution to your particular issue (unless by accident lol) but I think a custom Observable is a better fit in this situation instead of a Subject. You can also hook into the Parser's error callback too.
Figured that others searching for streaming JSON with rxjs later might find this handy (untested)
function streamingJsonParse(data$) {
return new Observable((observer) => {
const parser = new Parser();
parser.onError = (err) => observer.error(err);
parser.onValue = (value) => {
if (!parser.key) {
observer.next(value);
}
};
// return the subscription so it's correctly
// unsubscribed for us
return data$
.subscribe({
next: (data) => parser.write(data),
error: (e) => observer.error(e),
complete: () => observer.complete()
});
});
}
function getJsonStream(url) {
return RxHR
.get(url)
.mergeMap(resp => streamingJsonParse(resp.body));
}
When you've had a chance to put together that jsbin let me know!
Turns out the problem was with the jsonParse lib. Switching to oboe.js
fixed it. Using the "!" node selector to select multiple root JSON elements i was able to transform the character stream to a user object stream.
Action
function getJsonStream(url) {
const emitter = new Subject();
const emitter = new Subject();
oboe(url)
.node('!', (item) => {
emitter.next(item);
})
.fail((error) => {
emitter.error(error);
});
return emitter;
}
export const fetchUsersEpic = action$ =>
action$.ofType(actionTypes.FETCH_USERS_REQUEST)
.switchMap(() => getJsonStream(`${api.API_BASE_URL}/user`))
.map(user => fetchUsersItemReceived(user));

Resources