React Router URL - reactjs

In route.jsx my path looks like:
<Route path='/my-work/:workName' component={WorkShow} />
My link looks like:
<Link to={`/my-work/${props.work.name}`}>
I want to display the name in the url, but pass the id into the params instead so that I can fetch this in my component and pass it into the api.
My api path takes in the ID as a parameter but I want to avoid displaying this in the URL.
How would I store this as a key value pair....
Here is my reducer.js:
import Constants from '../constants';
const initialState = {
projects: [],
project: [],
fetching: true,
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case Constants.PROJECTS_FETCHING:
return Object.assign({}, state, { fetching: true });
case Constants.PROJECTS_RECEIVED:
return Object.assign({}, state, { projects: action.proj, fetching: false });
case Constants.PROJECT_FETCHING:
return Object.assign({}, state, { fetching: true });
case Constants.PROJECT_RECEIVED:
return Object.assign({}, state, { project: action.project, fetching: false });
default:
return state;
}
}
And here is my action.js:
import Request from 'superagent';
import Constants from '../constants';
const Actions = {
fetchProjects: () => (dispatch) => {
dispatch({ type: Constants.PROJECTS_FETCHING });
Request.get('/api/v1/projects')
.then((data) => {
dispatch({
type: Constants.PROJECTS_RECEIVED,
proj: data.body,
});
});
}, fetchProject: projectId => (dispatch) => {
dispatch({ type: Constants.PROJECT_FETCHING });
Request.get('/api/v1/projects/' + projectId)
.then((data) => {
dispatch({
type: Constants.PROJECT_RECEIVED, project: data.body,
});
});
},
};
export default Actions;

Related

Store updates but component does not

I'm trying to update the component after a user inputs new data.
Currently on componetDidMount() I call my reducer to fetch data from an API and return it to the component. That works. But when the user updates add a new form and it gets saved in the API, I call the API and the store updates (both redux and console log confirmed this) but the component does not update.
I'm think this could be an aysnc problem but I'm not certain.
Store:
type KnownAction = RecievedInvoicesAction | RequestInvoicesAction | RefreshInvoices;
export const actionCreators = {
requestInvoices: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
const appState = getState();
if (appState && appState.invoices && appState.invoices.isLoading) {
fetch('https://localhost:44304/api/invoices')
.then((response) => response.json())
.then((data) => {
dispatch({
type: 'RECIEVED_INVOICES',
invoices: data,
isLoading: false,
});
toast.success('Invoices loaded 👍', {
position: "bottom-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
})
});
dispatch({ type: 'REQUEST_INVOICES', isLoading: true});
}
},
refreshInvoices: (): AppThunkAction<KnownAction> => (dispatch) => {
fetch('https://localhost:44304/api/invoices')
.then((response) => response.json())
.then((data) => {
console.log(data);
dispatch({
type: 'REFRESH_INVOICES',
invoices: data,
isLoading: false,
});
});
dispatch({ type: 'REQUEST_INVOICES', isLoading: true});
}
};
// REDUCER
const unloadedState: InvoiceState = { isLoading: true, invoices: [] };
export const reducer: Reducer<InvoiceState> = (
state: InvoiceState | undefined,
incomingAction: Action
): InvoiceState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case 'REQUEST_INVOICES' :
return Object.assign({}, state, {
isLoading: action.isLoading
})
case 'RECIEVED_INVOICES':
return Object.assign({}, state, {
invoices: action.invoices,
isLoading: action.isLoading
})
case 'REFRESH_INVOICES':
return Object.assign({}, state, {
invoices: action.invoices,
isLoading: action.isLoading
})
default:
return state;
}
};
Main Component:
class Home extends React.Component<HomeProps, State> {
constructor(SearchInvoiceProps : HomeProps) {
super(SearchInvoiceProps);
this.state = {
queryText : '',
filterBy : 'all',
orderBy : 'asc',
order : 'invoiceDate',
error : '',
invoicesArr : []
}
}
componentDidMount() {
this.ensureDataFetched();
this.setState({
invoicesArr : this.props.invoices
})
}
ensureDataFetched = () => {
this.props.requestInvoices();
}
...
}
export default connect(
(state: ApplicationState) => state.invoices,
InvoiceStore.actionCreators
)(Home as any);

Loading state before display new content

I'm trying to achieve optimistic UI loading with react and redux. This is my reducer:
const initState = {
user: {},
loading: true,
};
export default function (state = initState, action) {
switch (action.type) {
case GET_CURRENT_USER_BY_PROFILEID_LOADING:
return {
...state,
loading: true,
};
case GET_CURRENT_USER_BY_PROFILEID:
return {
...state,
loading: false,
user: action.payload.item,
};
default:
return state;
}
}
This is action creator:
export const getCurrentUserByProfileId = (profileId) => {
return (dispatch) => {
dispatch({ type: GET_CURRENT_USER_BY_PROFILEID_LOADING });
baseUrl
.get(
Constants.RELATIVEPATH +
Constants.USERS +
'/' +
profileId +
Constants.PROFILEID,
{ headers }
)
.then((response) => {
const data = response.data;
dispatch({
type: GET_CURRENT_USER_BY_PROFILEID,
payload: { item: data },
});
})
.catch((error) => {
console.log(error);
onError(error);
});
};
};
The ideal scenario would be: Loading --> New state and I am getting this only on the first load, after that it's: Flash of old state --> Loading --> New state
You could dispatch the GET_CURRENT_USER_BY_PROFILEID_LOADING in a different function, before displaying the component to the user.
That way you would have:
old state (not displayed)
GET_CURRENT_USER_BY_PROFILEID_LOADING
navigation, or whatever you do display the component
loading (displayed)
GET_CURRENT_USER_BY_PROFILEID
new state (displayed)

React/Redux: Why can data not be rendered when working with isFetching flag?

I'm trying to implement an isFetching flag that indicates when my data is ready for rendering. But even if the flag works, i.e. jumps from isFetching = true to isFetching = false after the data has been successfully requested, there is still an error when I try to access data: cannot read property 'username' of null
Profile Component
class Profile extends React.Component {
render() {
const (isFetching, profile) = this.props.profile
console.log (isFetching)
console.log (profile)
return <h1>Hello, {isFetching = "false"? profile[0].username : null}</h1>;
}
}
function mapStateToProps(state, ownProps) {
const profile= state.profile
return { profile }
};
export default connect(
mapStateToProps,
{ logout }
)(Profile);
Action
export const getProfile = () => (dispatch, getState) => {
// Profile Loading
dispatch({ type: GET_PROFILE_REQUEST });
axios
.get(apiBase + "/profile/", tokenConfig(getState))
.then(res => {
dispatch({
type: GET_PROFILE_SUCCESS,
payload: res.data
});
})
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
dispatch({
type: GET_PROFILE_FAILURE,
});
});
};
Reducer
const initialState = {
isFetching: false,
profile: null
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_PROFILE_REQUEST:
return {
...state,
isFetching: true
};
case GET_PROFILE_SUCCESS:
return {
...state,
profile: action.payload,
isFetching: false
};
case GET_PROFILE_FAILURE:
return {
...state,
profile: action.payload,
isFetching: false
};
default:
return state;
}
}
Redux log for GET_PROFILE_SUCCESS
profile
isFetching: false
profile[
{
"username": "Daniel",
"id": 1,
"profile": {
"image": "Test",
"bio": "Test"
}
}
]
I'm happy for every clarification.
You have a small error in your code.
return <h1>Hello, {isFetching = "false"? profile.username : null}</h1>;
You are not checking for the value of isFetching but rather setting it again. Also, since profile is an array, you need to get the first element.Replace it with
return <h1>Hello, {!isFetching? profile[0].username : null}</h1>;
and it should work.

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.

Redux: Should i clear state on unmount

Having a strange bug/issue with redux. I have a component in an app that displays data in a table. this table is used across numerous routes and i'm passing in a url for the end point.
When i click between the routes they work fine but some fields in the table have a button to open a slide out menu. when i do the redux actions is dispatched and it fires it for all routes i have been to and not the one i'm on.
Action
export const clearTableData = () => dispatch => {
dispatch({
type: TYPES.CLEAR_TABLE_DATA,
});
};
export const getTableData = (url, limit, skip, where, sort, current) => async dispatch => {
try {
dispatch({ type: TYPES.FETCH_TABLE_DATA_LOADING });
const response = await axios.post(url, {
limit,
skip,
where,
sort
});
await dispatch({
type: TYPES.FETCH_TABLE_DATA,
payload: {
url: url,
data: response.data,
limit: limit,
skip: skip,
where: where,
sort: sort,
pagination: {
total: response.data.meta.total,
current: current,
pageSizeOptions: ["10", "20", "50", "100"],
showSizeChanger: true,
showQuickJumper: true,
position: "both"
}
}
});
dispatch({ type: TYPES.FETCH_TABLE_DATA_FINISHED });
} catch (err) {
dispatch({ type: TYPES.INSERT_ERROR, payload: err.response });
}
};
Reducer
import * as TYPES from '../actions/types';
export default (state = { loading: true, data: [], pagination: [] }, action) => {
switch (action.type) {
case TYPES.FETCH_TABLE_DATA:
return { ...state, ...action.payload };
case TYPES.FETCH_TABLE_DATA_LOADING:
return { ...state, loading: true };
case TYPES.FETCH_TABLE_DATA_FINISHED:
return { ...state, loading: false };
case TYPES.CLEAR_TABLE_DATA:
return {};
default:
return state;
}
};
component
componentDidMount() {
this.fetch();
websocket(this.props.websocketRoute, this.props.websocketEvent, this.fetch);
}
fetch = () => {
// Fetch from redux store
this.props.getTableData(
this.props.apiUrl,
this.state.limit,
this.state.skip,
{ ...this.filters, ...this.props.defaultWhere },
`${this.state.sortField} ${this.state.sortOrder}`,
this.state.current)
}
const mapStateToProps = ({ tableData }) => ({
tableData,
});
const mapDispatchToProps = dispatch => (
bindActionCreators({ getTableData }, dispatch)
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchableTable);
Websocket
import socketIOClient from 'socket.io-client';
import sailsIOClient from 'sails.io.js';
export const websocket = (websocketRoute, websocketEvent, callback) => {
if (websocketRoute) {
let io;
if (socketIOClient.sails) {
io = socketIOClient;
} else {
io = sailsIOClient(socketIOClient);
}
io.sails.transports = ['websocket'];
io.sails.reconnection = true;
io.sails.url = process.env.REACT_APP_WEBSOCKECTS_URL
io.socket.on('connect', () => {
io.socket.get(websocketRoute, (data, jwres) => {
console.log("connect data sss", data)
console.log("connect jwres sss", jwres)
});
});
io.socket.on(websocketEvent, (data, jwres) => {
console.log("websocket", callback)
callback();
})
io.socket.on('disconnect', () => {
io.socket._raw.io._reconnection = true;
});
}
}
So for e.g if i'm on a route for cars i'll pass in api/cars as url, and for trucks api/trucks. if i've been to both these pages they get fired.
should i be doing something to unmount and reset state to blank?
edit to add render
render() {
const { filters, columns, expandedRowRender, rowClassName, style } = this.props;
return (
<Table
bordered
columns={columns}
rowKey={record => record.id}
dataSource={this.props.tableData.data.items}
pagination={this.props.tableData.pagination}
loading={this.props.tableData.loading}
onChange={this.handleTableChange}
expandedRowRender={expandedRowRender}
rowClassName={rowClassName} />
);
Basic idea is, define a new action type in reducer file to clear the table data, and before unmount dispatch that action.
In Component:
componentDidMount() {
this.fetch();
}
componentWillUnmount() {
this.props.clearTableData();
}
const mapDispatchToProps = dispatch => (
bindActionCreators({ getTableData, clearTableData }, dispatch)
)
Action:
export const clearTableData = () => {
return { type: TYPES.CLEAR_TABLE_DATA };
};
Reducer:
case TYPES.CLEAR_TABLE_DATA: {
// reset the table data here, and return
}

Resources