I built an input interface in React and stored the data in a Redux state.
Then, I created a new action for a Post API call. If the "data" parameter is a constant (that I created as a test), everything works fine.
In reality, I'd like to use a key from the Redux state as the data parameter.
In the example, I'm getting an error because props are not defined.
Does it makes sense to connect this action to the state? If yes, how to do it? Thanks a lot!
import axios from 'axios';
export const show_query_action = () => {
return dispatch => {
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: this.props.reducer_components
})
.then(res => {
dispatch(show_query_success_action(res));
})
.catch(err => {
dispatch(show_query_failure_action(err.message));
});
};
};
const show_query_started_action = () => ({
type: 'ADD_SHOW_QUERY_STARTED'
});
const show_query_success_action = todo => ({
type: 'ADD_SHOW_QUERY_SUCCESS',
payload: {
...todo
}
});
const show_query_failure_action = error => ({
type: 'ADD_SHOW_QUERY_FAILURE',
payload: {
error
}
});
If it needs to be callable with different parameters from a React component you can put the data parameter as a parameter to your action creator:
export const show_query_action = (data) => {
return dispatch => {
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: data
})
This is also easily testable. If you only call this action with parameters from the redux store, you can use the second parameter from redux-thunk (wich i presume you are using here):
export const show_query_action = () => {
return (dispatch, getState) => {
const data = getState().data; -> you would specify in wich part of the state your data lives, this is just an example
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: data
})
Hope this helps.
Related
I'm new to react and tying in the back end but after I make the fetch requests, I have to reload the page to see any changes. The database is updated as soon as the functions are called but the component doesn't re-render. I know setState works asynchronously, so I tried calling my functions in the callback of setState but that did not work.
This happens on both my handleSubmit and handleDelete functions. My initial get request is in my componentDidMount so I'm including that in case it helps.
I couldn't find the answer that I needed on the site, maybe the recommendations were just off but here I am, lol. Thanks in advance.
componentDidMount() {
// todos is the data we get back
// setting the state to newly aquired data
fetch("/api/todos")`enter code here`
.then(res => res.json())
.then(todos => this.setState({ todos }, () =>
console.log("Todos fetched...", todos)))
.catch(err => console.log(err))
}
// onClick for submit button
handleSubmit = (e) => {
e.preventDefault();
const data = this.state;
fetch("/api/todos", {
method: "post",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
};
// onClick for delete button
handleDelete = (e) => {
e.preventDefault();
let uniqueId = e.target.getAttribute("id")
fetch(`/api/todos/${uniqueId}`, {
method: "delete",
headers: { 'Content-Type': 'application/json' }
})
};
// Some of the JSX if needed
<DeleteBtn
id={todo._id}
onClick={this.handleDelete}
>X</DeleteBtn>
<Form onSubmit={this.handleSubmit} id="myForm"></Form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The result I'm looking for is once I add a todo, for it to render on my list immediately, rather than only upon page reload.
Return the details from the back-end in the requests, use that values to update the state,
Currently you just perform the operation on the back-end and front-end doesn't know that it happened in the back-end.
The Best way is to either pass the full data(list or object) back to front-end after operation performed on the DB and link the values to a state,
if the data is bulk then send a success message(200 is enough) back from back-end to front-end and if success change the value(list) in front-end,
Link the value(list) to a state in front-end to have a re rendering of the component.
you've to update your state, and once you'll update the state your component will re-render and it'll shows the latest changes.
Here i am assuming "todos" you've set in your state is an array, then just update it on deleting and adding.
i.e:
// onClick for submit button
handleSubmit = (e) => {
e.preventDefault();
const data = this.state;
const currentTodos = [...this.state.todos]
fetch("/api/todos", {
method: "post",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(()=>{
currentTodos.push(data);
this.setState({todos:currentTodos})
})
};
// similarly for delete you can do
// onClick for delete button
handleDelete = (e) => {
e.preventDefault();
let uniqueId = e.target.getAttribute("id")
let currentTodos = [...this.state.todos];
fetch(`/api/todos/${uniqueId}`, {
method: "delete",
headers: { 'Content-Type': 'application/json' }
}).then(()=>{
let updatedTodos = currentTodos.filter(todo=>todo._id !==uniqueId);
this.setState({todos:updatedTodos})
})
};
You are probably not changing your state "todos" that is why it doesn't render. You could fetch todos after every change (after remove, update, add...) or change the state yourself.
Methode 1:
componentDidMount() {
this.getTodos();
}
getTodos = () => {
//fetch todos, setState
}
handleSubmit = () => {
fetch(...).then(this.getTodos);
}
handleDelete = () => {
fetch(...).then(this.getTodos);
}
Methode 2:
componentDidMount() {
this.getTodos();
}
getTodos = () => {
//fetch todos, setState
}
handleSubmit = () => {
fetch(...);
let todos = this.state.todos;
todos.push(newTodo);
this.setState({todos});
}
handleDelete = () => {
fetch(...);
let todos = this.state.todos;
//remove todo from todos
this.setState({todos});
}
I need to access store in headlessJS in react native to dispatch an action.
I tried
AppRegistry.registerHeadlessTask('didEnterRegion', (beacon) => Util.didEnterRegion.bind(beacon, store));
and in Util.js
const didEnterRegion = async (beacons, store) => {
console.log(beacons);
store.dispatch({
type: FOUND_PLACES_FAIL,
payload: err
});
});
but now I receive the beacons object as an empty object. How do I pass that object too along with the store.
Sorry, so stupid of me.
I just had to pass the params in headlessJS.
AppRegistry.registerHeadlessTask('didEnterRegion', (beacon) => Util.didEnterRegion.bind(beacon, store));
const didEnterRegion = async (store, beacon) => {
console.log(beacon);
store.dispatch({
type: FOUND_PLACES_FAIL,
payload: err
});
});
export function editPost(props){
const request = axios.put(`http://www.example.com/posts`, props) ;
return{
type: EDIT_POST,
payload: request
};
}
hi,, is this what a proper "Update" action should look like in Redux?
using axios to make the request
the type was created in another file and then imported
Thanks
axios is promise-based, so currently you're returning a payload that doesn't exist. Look for redux-thunk and use it in the following way:
actionCreator() {
return (dispatch) => {
return axios.put('url').then((res) => dispatch({ type: EDIT_POST, payload: res }))
}
}
I have an action that returns dispatch. Right now i am posting props... but i'd like to add a few peices like (uname, role, etc) to the request. What is he easiest way to do so?
I thought something like:
const addlFields = { username: 'newTester', role: 'moderator' }
Any good advice on how to get this done? Im assuming it is not an axios issue since axios and accepting the props obj.
My request
const sendMessageRequest = axios.post(`${POST_MSG_URL}`, props)
.then(response => {
return dispatch => {
dispatch(
{ type: POST_MESSAGE,
payload: sendMessageRequest
}
);
}
});`
I'm stuck in a wierd behaviour that I can't really debug.
The store dispatch the action that perform the login request passing username and password. Then when the response is ready I store the credentials in the redux store. When I need to perform an authorized request I set those parameters in the header request. When I receive the response I update the credentials in the store with the new ones that I get from the response.
When I try to perform the third request it will respond unauthorized. I figured out that this is because all the parameters passed to my action generator setCredentials are null. I can't understand why also because if I add a debugger before the return statement of my setCredentials function and I wait some seconds before restart the execution I found out that the parameters aren't null anymore. I was thinking about the fact that the request is async but being inside a then statement the response should be ready right? I've also notice that fetch sent two request for each one.
Here the code for more clarity.
import { combineReducers } from 'redux'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const initialState = {
currentUser: {
credentials: {},
user: {}
},
test: {},
users: []
}
export const SUBMIT_LOGIN = 'SUBMIT_LOGIN'
export const SET_USER = 'SET_USER'
export const TEST = 'TEST'
export const SET_USERS = 'SET_USERS'
export const SET_CREDENTIALS = 'SET_CREDENTIALS'
//actions
const submitLogin = () => (dispatch) => {
return postLoginRequest()
.then(response => {
dispatch(setCredentials(
response.headers.get('access-token'),
response.headers.get('client'),
response.headers.get('expiry'),
response.headers.get('token-type'),
response.headers.get('uid')
));
return response
})
.then(response => {
return response.json();
})
.then(
(user) => dispatch(setUser(user.data)),
);
}
const performRequest = (api) => (dispatch) => {
return api()
.then(response => {
dispatch(setCredentials(
response.headers.get('access-token'),
response.headers.get('client'),
response.headers.get('expiry'),
response.headers.get('token-type'),
response.headers.get('uid')
));
return response
})
.then(response => {return response.json()})
.then(
(users) => {
dispatch(setUsers(users.data))
},
);
}
const setUsers = (users) => {
return {
type: SET_USERS,
users
}
}
const setUser = (user) => {
return {
type: SET_USER,
user
}
}
const setCredentials = (
access_token,
client,
expiry,
token_type,
uid
) => {
debugger
return {
type: SET_CREDENTIALS,
credentials: {
'access-token': access_token,
client,
expiry,
'token-type': token_type,
uid
}
}
}
//////////////
const currentUserInitialState = {
credentials: {},
user: {}
}
const currentUser = (state = currentUserInitialState, action) => {
switch (action.type) {
case SET_USER:
return Object.assign({}, state, {user: action.user})
case SET_CREDENTIALS:
return Object.assign({}, state, {credentials: action.credentials})
default:
return state
}
}
const rootReducer = combineReducers({
currentUser,
test
})
const getAuthorizedHeader = (store) => {
const credentials = store.getState().currentUser.credentials
const headers = new Headers(credentials)
return headers
}
//store creation
const createStoreWithMiddleware = applyMiddleware(
thunk
)(createStore);
const store = createStoreWithMiddleware(rootReducer);
const postLoginRequest = () => {
return fetch('http://localhost:3000/auth/sign_in', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'test#test.com',
password: 'password',
})
})
}
const getUsers = () => {
const autorizedHeader = getAuthorizedHeader(store)
return fetch('http://localhost:3000/users',
{
method: 'GET',
headers : autorizedHeader
}
)
}
const getWorks = () => {
const autorizedHeader = getAuthorizedHeader(store)
return fetch('http://localhost:3000/work_offers',
{
method: 'GET',
headers : autorizedHeader
}
)
}
// this request works fine
store.dispatch(submitLogin())
// this request works fine
setTimeout(() => {
store.dispatch(performRequest(getUsers))
}, 3000)
// this fails
setTimeout(() => {
store.dispatch(performRequest(getWorks))
}, 5000)
I should have clarified that when I asked
Have you verified that all your endpoints return those headers and not just the login one? Maybe when you performRequest(getUsers), it comes back with empty headers.
I didn’t just mean the server logic. I meant opening the Network tab in DevTools and actually verifying whether your responses contain the headers you expect. It turns out getUsers() headers do not always contain the credentials:
Now that we confirmed this happens, let’s see why.
You dispatch submitLogin() and performRequest(getUsers) roughly at the same time. In the cases when the error is reproduced, the problem is in the following sequence of steps:
You fire off submitLogin()
You fire off performRequest(getUsers) before submitLogin() comes back
submitLogin() comes back and stores the credentials from the response headers
performRequest(getUsers) comes back but since it started before credentials were available, the server responds with empty headers, and those empty credentials are stored instead of the existing ones
performRequest(getWorks) is now requested without the credentials
There are several fixes for this problem.
Don’t Let Old Unauthorized Requests Overwrite the Credentials
I don’t think it really makes sense to overwrite existing good credentials with the empty ones, does it? You can either check that they are non-empty in performRequest before dispatching:
const performRequest = (api) => (dispatch, getState) => {
return api()
.then(response => {
if (response.headers.get('access-token')) {
dispatch(setCredentials(
response.headers.get('access-token'),
response.headers.get('client'),
response.headers.get('expiry'),
response.headers.get('token-type'),
response.headers.get('uid')
));
}
return response
})
.then(response => {return response.json()})
.then(
(users) => {
dispatch(setUsers(users.data))
},
);
}
Alternatively, you can do ignore invalid credentials in the reducer itself:
case SET_CREDENTIALS:
if (action.credentials['access-token']) {
return Object.assign({}, state, {credentials: action.credentials})
} else {
return state
}
Both ways are fine and depend on the conventions that make more sense to you.
Wait Before Performing Requests
In any case, do you really want to fire getUsers() before you have the credentials? If not, fire off the requests only until the credentials are available. Something like this:
store.dispatch(submitLogin()).then(() => {
store.dispatch(performRequest(getUsers))
store.dispatch(performRequest(getWorks))
})
If it’s not always feasible or you would like more sophisticated logic like retrying failed requests, I suggest you to look at Redux Saga which lets you use powerful concurrency primitives to schedule this kind of work.